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;
1469 if (appData.debugMode) {
1471 fprintf(debugFP, "<ICS: ");
1472 show_bytes(debugFP, data, count);
1473 fprintf(debugFP, "\n");
1479 /* If last read ended with a partial line that we couldn't parse,
1480 prepend it to the new read and try again. */
1481 if (leftover_len > 0) {
1482 for (i=0; i<leftover_len; i++)
1483 buf[i] = buf[leftover_start + i];
1486 /* Copy in new characters, removing nulls and \r's */
1487 buf_len = leftover_len;
1488 for (i = 0; i < count; i++) {
1489 if (data[i] != NULLCHAR && data[i] != '\r')
1490 buf[buf_len++] = data[i];
1493 buf[buf_len] = NULLCHAR;
1494 next_out = leftover_len;
1498 while (i < buf_len) {
1499 /* Deal with part of the TELNET option negotiation
1500 protocol. We refuse to do anything beyond the
1501 defaults, except that we allow the WILL ECHO option,
1502 which ICS uses to turn off password echoing when we are
1503 directly connected to it. We reject this option
1504 if localLineEditing mode is on (always on in xboard)
1505 and we are talking to port 23, which might be a real
1506 telnet server that will try to keep WILL ECHO on permanently.
1508 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1509 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1510 unsigned char option;
1512 switch ((unsigned char) buf[++i]) {
1514 if (appData.debugMode)
1515 fprintf(debugFP, "\n<WILL ");
1516 switch (option = (unsigned char) buf[++i]) {
1518 if (appData.debugMode)
1519 fprintf(debugFP, "ECHO ");
1520 /* Reply only if this is a change, according
1521 to the protocol rules. */
1522 if (remoteEchoOption) break;
1523 if (appData.localLineEditing &&
1524 atoi(appData.icsPort) == TN_PORT) {
1525 TelnetRequest(TN_DONT, TN_ECHO);
1528 TelnetRequest(TN_DO, TN_ECHO);
1529 remoteEchoOption = TRUE;
1533 if (appData.debugMode)
1534 fprintf(debugFP, "%d ", option);
1535 /* Whatever this is, we don't want it. */
1536 TelnetRequest(TN_DONT, option);
1541 if (appData.debugMode)
1542 fprintf(debugFP, "\n<WONT ");
1543 switch (option = (unsigned char) buf[++i]) {
1545 if (appData.debugMode)
1546 fprintf(debugFP, "ECHO ");
1547 /* Reply only if this is a change, according
1548 to the protocol rules. */
1549 if (!remoteEchoOption) break;
1551 TelnetRequest(TN_DONT, TN_ECHO);
1552 remoteEchoOption = FALSE;
1555 if (appData.debugMode)
1556 fprintf(debugFP, "%d ", (unsigned char) option);
1557 /* Whatever this is, it must already be turned
1558 off, because we never agree to turn on
1559 anything non-default, so according to the
1560 protocol rules, we don't reply. */
1565 if (appData.debugMode)
1566 fprintf(debugFP, "\n<DO ");
1567 switch (option = (unsigned char) buf[++i]) {
1569 /* Whatever this is, we refuse to do it. */
1570 if (appData.debugMode)
1571 fprintf(debugFP, "%d ", option);
1572 TelnetRequest(TN_WONT, option);
1577 if (appData.debugMode)
1578 fprintf(debugFP, "\n<DONT ");
1579 switch (option = (unsigned char) buf[++i]) {
1581 if (appData.debugMode)
1582 fprintf(debugFP, "%d ", option);
1583 /* Whatever this is, we are already not doing
1584 it, because we never agree to do anything
1585 non-default, so according to the protocol
1586 rules, we don't reply. */
1591 if (appData.debugMode)
1592 fprintf(debugFP, "\n<IAC ");
1593 /* Doubled IAC; pass it through */
1597 if (appData.debugMode)
1598 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1599 /* Drop all other telnet commands on the floor */
1602 if (oldi > next_out)
1603 SendToPlayer(&buf[next_out], oldi - next_out);
1609 /* OK, this at least will *usually* work */
1610 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1614 if (loggedOn && !intfSet) {
1615 if (ics_type == ICS_ICC) {
1617 "/set-quietly interface %s\n/set-quietly style 12\n",
1620 } else if (ics_type == ICS_CHESSNET) {
1621 sprintf(str, "/style 12\n");
1623 strcpy(str, "alias $ @\n$set interface ");
1624 strcat(str, programVersion);
1625 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1627 strcat(str, "$iset nohighlight 1\n");
1629 strcat(str, "$iset lock 1\n$style 12\n");
1635 if (started == STARTED_COMMENT) {
1636 /* Accumulate characters in comment */
1637 parse[parse_pos++] = buf[i];
1638 if (buf[i] == '\n') {
1639 parse[parse_pos] = NULLCHAR;
1640 AppendComment(forwardMostMove, StripHighlight(parse));
1641 started = STARTED_NONE;
1643 /* Don't match patterns against characters in chatter */
1648 if (started == STARTED_CHATTER) {
1649 if (buf[i] != '\n') {
1650 /* Don't match patterns against characters in chatter */
1654 started = STARTED_NONE;
1657 /* Kludge to deal with rcmd protocol */
1658 if (firstTime && looking_at(buf, &i, "\001*")) {
1659 DisplayFatalError(&buf[1], 0, 1);
1665 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1668 if (appData.debugMode)
1669 fprintf(debugFP, "ics_type %d\n", ics_type);
1672 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1673 ics_type = ICS_FICS;
1675 if (appData.debugMode)
1676 fprintf(debugFP, "ics_type %d\n", ics_type);
1679 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1680 ics_type = ICS_CHESSNET;
1682 if (appData.debugMode)
1683 fprintf(debugFP, "ics_type %d\n", ics_type);
1688 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1689 looking_at(buf, &i, "Logging you in as \"*\"") ||
1690 looking_at(buf, &i, "will be \"*\""))) {
1691 strcpy(ics_handle, star_match[0]);
1695 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1697 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1698 DisplayIcsInteractionTitle(buf);
1699 have_set_title = TRUE;
1702 /* skip finger notes */
1703 if (started == STARTED_NONE &&
1704 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1705 (buf[i] == '1' && buf[i+1] == '0')) &&
1706 buf[i+2] == ':' && buf[i+3] == ' ') {
1707 started = STARTED_CHATTER;
1712 /* skip formula vars */
1713 if (started == STARTED_NONE &&
1714 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1715 started = STARTED_CHATTER;
1721 if (appData.zippyTalk || appData.zippyPlay) {
1723 if (ZippyControl(buf, &i) ||
1724 ZippyConverse(buf, &i) ||
1725 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1731 if (/* Don't color "message" or "messages" output */
1732 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1733 looking_at(buf, &i, "*. * at *:*: ") ||
1734 looking_at(buf, &i, "--* (*:*): ") ||
1735 /* Regular tells and says */
1736 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1737 looking_at(buf, &i, "* (your partner) tells you: ") ||
1738 looking_at(buf, &i, "* says: ") ||
1739 /* Message notifications (same color as tells) */
1740 looking_at(buf, &i, "* has left a message ") ||
1741 looking_at(buf, &i, "* just sent you a message:\n") ||
1742 /* Whispers and kibitzes */
1743 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1744 looking_at(buf, &i, "* kibitzes: ") ||
1746 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1748 if (tkind == 1 && strchr(star_match[0], ':')) {
1749 /* Avoid "tells you:" spoofs in channels */
1752 if (star_match[0][0] == NULLCHAR ||
1753 strchr(star_match[0], ' ') ||
1754 (tkind == 3 && strchr(star_match[1], ' '))) {
1755 /* Reject bogus matches */
1758 if (appData.colorize) {
1759 if (oldi > next_out) {
1760 SendToPlayer(&buf[next_out], oldi - next_out);
1765 Colorize(ColorTell, FALSE);
1766 curColor = ColorTell;
1769 Colorize(ColorKibitz, FALSE);
1770 curColor = ColorKibitz;
1773 p = strrchr(star_match[1], '(');
1780 Colorize(ColorChannel1, FALSE);
1781 curColor = ColorChannel1;
1783 Colorize(ColorChannel, FALSE);
1784 curColor = ColorChannel;
1788 curColor = ColorNormal;
1792 if (started == STARTED_NONE && appData.autoComment &&
1793 (gameMode == IcsObserving ||
1794 gameMode == IcsPlayingWhite ||
1795 gameMode == IcsPlayingBlack)) {
1796 parse_pos = i - oldi;
1797 memcpy(parse, &buf[oldi], parse_pos);
1798 parse[parse_pos] = NULLCHAR;
1799 started = STARTED_COMMENT;
1800 savingComment = TRUE;
1802 started = STARTED_CHATTER;
1803 savingComment = FALSE;
1810 if (looking_at(buf, &i, "* s-shouts: ") ||
1811 looking_at(buf, &i, "* c-shouts: ")) {
1812 if (appData.colorize) {
1813 if (oldi > next_out) {
1814 SendToPlayer(&buf[next_out], oldi - next_out);
1817 Colorize(ColorSShout, FALSE);
1818 curColor = ColorSShout;
1821 started = STARTED_CHATTER;
1825 if (looking_at(buf, &i, "--->")) {
1830 if (looking_at(buf, &i, "* shouts: ") ||
1831 looking_at(buf, &i, "--> ")) {
1832 if (appData.colorize) {
1833 if (oldi > next_out) {
1834 SendToPlayer(&buf[next_out], oldi - next_out);
1837 Colorize(ColorShout, FALSE);
1838 curColor = ColorShout;
1841 started = STARTED_CHATTER;
1845 if (looking_at( buf, &i, "Challenge:")) {
1846 if (appData.colorize) {
1847 if (oldi > next_out) {
1848 SendToPlayer(&buf[next_out], oldi - next_out);
1851 Colorize(ColorChallenge, FALSE);
1852 curColor = ColorChallenge;
1858 if (looking_at(buf, &i, "* offers you") ||
1859 looking_at(buf, &i, "* offers to be") ||
1860 looking_at(buf, &i, "* would like to") ||
1861 looking_at(buf, &i, "* requests to") ||
1862 looking_at(buf, &i, "Your opponent offers") ||
1863 looking_at(buf, &i, "Your opponent requests")) {
1865 if (appData.colorize) {
1866 if (oldi > next_out) {
1867 SendToPlayer(&buf[next_out], oldi - next_out);
1870 Colorize(ColorRequest, FALSE);
1871 curColor = ColorRequest;
1876 if (looking_at(buf, &i, "* (*) seeking")) {
1877 if (appData.colorize) {
1878 if (oldi > next_out) {
1879 SendToPlayer(&buf[next_out], oldi - next_out);
1882 Colorize(ColorSeek, FALSE);
1883 curColor = ColorSeek;
1889 if (looking_at(buf, &i, "\\ ")) {
1890 if (prevColor != ColorNormal) {
1891 if (oldi > next_out) {
1892 SendToPlayer(&buf[next_out], oldi - next_out);
1895 Colorize(prevColor, TRUE);
1896 curColor = prevColor;
1898 if (savingComment) {
1899 parse_pos = i - oldi;
1900 memcpy(parse, &buf[oldi], parse_pos);
1901 parse[parse_pos] = NULLCHAR;
1902 started = STARTED_COMMENT;
1904 started = STARTED_CHATTER;
1909 if (looking_at(buf, &i, "Black Strength :") ||
1910 looking_at(buf, &i, "<<< style 10 board >>>") ||
1911 looking_at(buf, &i, "<10>") ||
1912 looking_at(buf, &i, "#@#")) {
1913 /* Wrong board style */
1915 SendToICS(ics_prefix);
1916 SendToICS("set style 12\n");
1917 SendToICS(ics_prefix);
1918 SendToICS("refresh\n");
1922 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1924 have_sent_ICS_logon = 1;
1928 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1929 (looking_at(buf, &i, "\n<12> ") ||
1930 looking_at(buf, &i, "<12> "))) {
1932 if (oldi > next_out) {
1933 SendToPlayer(&buf[next_out], oldi - next_out);
1936 started = STARTED_BOARD;
1941 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1942 looking_at(buf, &i, "<b1> ")) {
1943 if (oldi > next_out) {
1944 SendToPlayer(&buf[next_out], oldi - next_out);
1947 started = STARTED_HOLDINGS;
1952 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1954 /* Header for a move list -- first line */
1956 switch (ics_getting_history) {
1960 case BeginningOfGame:
1961 /* User typed "moves" or "oldmoves" while we
1962 were idle. Pretend we asked for these
1963 moves and soak them up so user can step
1964 through them and/or save them.
1967 gameMode = IcsObserving;
1970 ics_getting_history = H_GOT_UNREQ_HEADER;
1972 case EditGame: /*?*/
1973 case EditPosition: /*?*/
1974 /* Should above feature work in these modes too? */
1975 /* For now it doesn't */
1976 ics_getting_history = H_GOT_UNWANTED_HEADER;
1979 ics_getting_history = H_GOT_UNWANTED_HEADER;
1984 /* Is this the right one? */
1985 if (gameInfo.white && gameInfo.black &&
1986 strcmp(gameInfo.white, star_match[0]) == 0 &&
1987 strcmp(gameInfo.black, star_match[2]) == 0) {
1989 ics_getting_history = H_GOT_REQ_HEADER;
1992 case H_GOT_REQ_HEADER:
1993 case H_GOT_UNREQ_HEADER:
1994 case H_GOT_UNWANTED_HEADER:
1995 case H_GETTING_MOVES:
1996 /* Should not happen */
1997 DisplayError(_("Error gathering move list: two headers"), 0);
1998 ics_getting_history = H_FALSE;
2002 /* Save player ratings into gameInfo if needed */
2003 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2004 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2005 (gameInfo.whiteRating == -1 ||
2006 gameInfo.blackRating == -1)) {
2008 gameInfo.whiteRating = string_to_rating(star_match[1]);
2009 gameInfo.blackRating = string_to_rating(star_match[3]);
2010 if (appData.debugMode)
2011 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2012 gameInfo.whiteRating, gameInfo.blackRating);
2017 if (looking_at(buf, &i,
2018 "* * match, initial time: * minute*, increment: * second")) {
2019 /* Header for a move list -- second line */
2020 /* Initial board will follow if this is a wild game */
2022 if (gameInfo.event != NULL) free(gameInfo.event);
2023 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2024 gameInfo.event = StrSave(str);
2025 gameInfo.variant = StringToVariant(gameInfo.event);
2029 if (looking_at(buf, &i, "Move ")) {
2030 /* Beginning of a move list */
2031 switch (ics_getting_history) {
2033 /* Normally should not happen */
2034 /* Maybe user hit reset while we were parsing */
2037 /* Happens if we are ignoring a move list that is not
2038 * the one we just requested. Common if the user
2039 * tries to observe two games without turning off
2042 case H_GETTING_MOVES:
2043 /* Should not happen */
2044 DisplayError(_("Error gathering move list: nested"), 0);
2045 ics_getting_history = H_FALSE;
2047 case H_GOT_REQ_HEADER:
2048 ics_getting_history = H_GETTING_MOVES;
2049 started = STARTED_MOVES;
2051 if (oldi > next_out) {
2052 SendToPlayer(&buf[next_out], oldi - next_out);
2055 case H_GOT_UNREQ_HEADER:
2056 ics_getting_history = H_GETTING_MOVES;
2057 started = STARTED_MOVES_NOHIDE;
2060 case H_GOT_UNWANTED_HEADER:
2061 ics_getting_history = H_FALSE;
2067 if (looking_at(buf, &i, "% ") ||
2068 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2069 && looking_at(buf, &i, "}*"))) {
2070 savingComment = FALSE;
2073 case STARTED_MOVES_NOHIDE:
2074 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2075 parse[parse_pos + i - oldi] = NULLCHAR;
2076 ParseGameHistory(parse);
2078 if (appData.zippyPlay && first.initDone) {
2079 FeedMovesToProgram(&first, forwardMostMove);
2080 if (gameMode == IcsPlayingWhite) {
2081 if (WhiteOnMove(forwardMostMove)) {
2082 if (first.sendTime) {
2083 if (first.useColors) {
2084 SendToProgram("black\n", &first);
2086 SendTimeRemaining(&first, TRUE);
2088 if (first.useColors) {
2089 SendToProgram("white\ngo\n", &first);
2091 SendToProgram("go\n", &first);
2093 first.maybeThinking = TRUE;
2095 if (first.usePlayother) {
2096 if (first.sendTime) {
2097 SendTimeRemaining(&first, TRUE);
2099 SendToProgram("playother\n", &first);
2105 } else if (gameMode == IcsPlayingBlack) {
2106 if (!WhiteOnMove(forwardMostMove)) {
2107 if (first.sendTime) {
2108 if (first.useColors) {
2109 SendToProgram("white\n", &first);
2111 SendTimeRemaining(&first, FALSE);
2113 if (first.useColors) {
2114 SendToProgram("black\ngo\n", &first);
2116 SendToProgram("go\n", &first);
2118 first.maybeThinking = TRUE;
2120 if (first.usePlayother) {
2121 if (first.sendTime) {
2122 SendTimeRemaining(&first, FALSE);
2124 SendToProgram("playother\n", &first);
2133 if (gameMode == IcsObserving && ics_gamenum == -1) {
2134 /* Moves came from oldmoves or moves command
2135 while we weren't doing anything else.
2137 currentMove = forwardMostMove;
2138 ClearHighlights();/*!!could figure this out*/
2139 flipView = appData.flipView;
2140 DrawPosition(FALSE, boards[currentMove]);
2141 DisplayBothClocks();
2142 sprintf(str, "%s vs. %s",
2143 gameInfo.white, gameInfo.black);
2147 /* Moves were history of an active game */
2148 if (gameInfo.resultDetails != NULL) {
2149 free(gameInfo.resultDetails);
2150 gameInfo.resultDetails = NULL;
2153 HistorySet(parseList, backwardMostMove,
2154 forwardMostMove, currentMove-1);
2155 DisplayMove(currentMove - 1);
2156 if (started == STARTED_MOVES) next_out = i;
2157 started = STARTED_NONE;
2158 ics_getting_history = H_FALSE;
2161 case STARTED_OBSERVE:
2162 started = STARTED_NONE;
2163 SendToICS(ics_prefix);
2164 SendToICS("refresh\n");
2173 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2174 started == STARTED_HOLDINGS ||
2175 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2176 /* Accumulate characters in move list or board */
2177 parse[parse_pos++] = buf[i];
2180 /* Start of game messages. Mostly we detect start of game
2181 when the first board image arrives. On some versions
2182 of the ICS, though, we need to do a "refresh" after starting
2183 to observe in order to get the current board right away. */
2184 if (looking_at(buf, &i, "Adding game * to observation list")) {
2185 started = STARTED_OBSERVE;
2189 /* Handle auto-observe */
2190 if (appData.autoObserve &&
2191 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2192 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2194 /* Choose the player that was highlighted, if any. */
2195 if (star_match[0][0] == '\033' ||
2196 star_match[1][0] != '\033') {
2197 player = star_match[0];
2199 player = star_match[2];
2201 sprintf(str, "%sobserve %s\n",
2202 ics_prefix, StripHighlightAndTitle(player));
2205 /* Save ratings from notify string */
2206 strcpy(player1Name, star_match[0]);
2207 player1Rating = string_to_rating(star_match[1]);
2208 strcpy(player2Name, star_match[2]);
2209 player2Rating = string_to_rating(star_match[3]);
2211 if (appData.debugMode)
2213 "Ratings from 'Game notification:' %s %d, %s %d\n",
2214 player1Name, player1Rating,
2215 player2Name, player2Rating);
2220 /* Deal with automatic examine mode after a game,
2221 and with IcsObserving -> IcsExamining transition */
2222 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2223 looking_at(buf, &i, "has made you an examiner of game *")) {
2225 int gamenum = atoi(star_match[0]);
2226 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2227 gamenum == ics_gamenum) {
2228 /* We were already playing or observing this game;
2229 no need to refetch history */
2230 gameMode = IcsExamining;
2232 pauseExamForwardMostMove = forwardMostMove;
2233 } else if (currentMove < forwardMostMove) {
2234 ForwardInner(forwardMostMove);
2237 /* I don't think this case really can happen */
2238 SendToICS(ics_prefix);
2239 SendToICS("refresh\n");
2244 /* Error messages */
2245 if (ics_user_moved) {
2246 if (looking_at(buf, &i, "Illegal move") ||
2247 looking_at(buf, &i, "Not a legal move") ||
2248 looking_at(buf, &i, "Your king is in check") ||
2249 looking_at(buf, &i, "It isn't your turn") ||
2250 looking_at(buf, &i, "It is not your move")) {
2253 if (forwardMostMove > backwardMostMove) {
2254 currentMove = --forwardMostMove;
2255 DisplayMove(currentMove - 1); /* before DMError */
2256 DisplayMoveError("Illegal move (rejected by ICS)");
2257 DrawPosition(FALSE, boards[currentMove]);
2259 DisplayBothClocks();
2265 if (looking_at(buf, &i, "still have time") ||
2266 looking_at(buf, &i, "not out of time") ||
2267 looking_at(buf, &i, "either player is out of time") ||
2268 looking_at(buf, &i, "has timeseal; checking")) {
2269 /* We must have called his flag a little too soon */
2270 whiteFlag = blackFlag = FALSE;
2274 if (looking_at(buf, &i, "added * seconds to") ||
2275 looking_at(buf, &i, "seconds were added to")) {
2276 /* Update the clocks */
2277 SendToICS(ics_prefix);
2278 SendToICS("refresh\n");
2282 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2283 ics_clock_paused = TRUE;
2288 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2289 ics_clock_paused = FALSE;
2294 /* Grab player ratings from the Creating: message.
2295 Note we have to check for the special case when
2296 the ICS inserts things like [white] or [black]. */
2297 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2298 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2300 0 player 1 name (not necessarily white)
2302 2 empty, white, or black (IGNORED)
2303 3 player 2 name (not necessarily black)
2306 The names/ratings are sorted out when the game
2307 actually starts (below).
2309 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2310 player1Rating = string_to_rating(star_match[1]);
2311 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2312 player2Rating = string_to_rating(star_match[4]);
2314 if (appData.debugMode)
2316 "Ratings from 'Creating:' %s %d, %s %d\n",
2317 player1Name, player1Rating,
2318 player2Name, player2Rating);
2323 /* Improved generic start/end-of-game messages */
2324 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2325 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2326 /* If tkind == 0: */
2327 /* star_match[0] is the game number */
2328 /* [1] is the white player's name */
2329 /* [2] is the black player's name */
2330 /* For end-of-game: */
2331 /* [3] is the reason for the game end */
2332 /* [4] is a PGN end game-token, preceded by " " */
2333 /* For start-of-game: */
2334 /* [3] begins with "Creating" or "Continuing" */
2335 /* [4] is " *" or empty (don't care). */
2336 int gamenum = atoi(star_match[0]);
2337 char *whitename, *blackname, *why, *endtoken;
2338 ChessMove endtype = (ChessMove) 0;
2341 whitename = star_match[1];
2342 blackname = star_match[2];
2343 why = star_match[3];
2344 endtoken = star_match[4];
2346 whitename = star_match[1];
2347 blackname = star_match[3];
2348 why = star_match[5];
2349 endtoken = star_match[6];
2352 /* Game start messages */
2353 if (strncmp(why, "Creating ", 9) == 0 ||
2354 strncmp(why, "Continuing ", 11) == 0) {
2355 gs_gamenum = gamenum;
2356 strcpy(gs_kind, strchr(why, ' ') + 1);
2358 if (appData.zippyPlay) {
2359 ZippyGameStart(whitename, blackname);
2365 /* Game end messages */
2366 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2367 ics_gamenum != gamenum) {
2370 while (endtoken[0] == ' ') endtoken++;
2371 switch (endtoken[0]) {
2374 endtype = GameUnfinished;
2377 endtype = BlackWins;
2380 if (endtoken[1] == '/')
2381 endtype = GameIsDrawn;
2383 endtype = WhiteWins;
2386 GameEnds(endtype, why, GE_ICS);
2388 if (appData.zippyPlay && first.initDone) {
2389 ZippyGameEnd(endtype, why);
2390 if (first.pr == NULL) {
2391 /* Start the next process early so that we'll
2392 be ready for the next challenge */
2393 StartChessProgram(&first);
2395 /* Send "new" early, in case this command takes
2396 a long time to finish, so that we'll be ready
2397 for the next challenge. */
2404 if (looking_at(buf, &i, "Removing game * from observation") ||
2405 looking_at(buf, &i, "no longer observing game *") ||
2406 looking_at(buf, &i, "Game * (*) has no examiners")) {
2407 if (gameMode == IcsObserving &&
2408 atoi(star_match[0]) == ics_gamenum)
2413 ics_user_moved = FALSE;
2418 if (looking_at(buf, &i, "no longer examining game *")) {
2419 if (gameMode == IcsExamining &&
2420 atoi(star_match[0]) == ics_gamenum)
2424 ics_user_moved = FALSE;
2429 /* Advance leftover_start past any newlines we find,
2430 so only partial lines can get reparsed */
2431 if (looking_at(buf, &i, "\n")) {
2432 prevColor = curColor;
2433 if (curColor != ColorNormal) {
2434 if (oldi > next_out) {
2435 SendToPlayer(&buf[next_out], oldi - next_out);
2438 Colorize(ColorNormal, FALSE);
2439 curColor = ColorNormal;
2441 if (started == STARTED_BOARD) {
2442 started = STARTED_NONE;
2443 parse[parse_pos] = NULLCHAR;
2444 ParseBoard12(parse);
2447 /* Send premove here */
2448 if (appData.premove) {
2450 if (currentMove == 0 &&
2451 gameMode == IcsPlayingWhite &&
2452 appData.premoveWhite) {
2453 sprintf(str, "%s%s\n", ics_prefix,
2454 appData.premoveWhiteText);
2455 if (appData.debugMode)
2456 fprintf(debugFP, "Sending premove:\n");
2458 } else if (currentMove == 1 &&
2459 gameMode == IcsPlayingBlack &&
2460 appData.premoveBlack) {
2461 sprintf(str, "%s%s\n", ics_prefix,
2462 appData.premoveBlackText);
2463 if (appData.debugMode)
2464 fprintf(debugFP, "Sending premove:\n");
2466 } else if (gotPremove) {
2468 ClearPremoveHighlights();
2469 if (appData.debugMode)
2470 fprintf(debugFP, "Sending premove:\n");
2471 UserMoveEvent(premoveFromX, premoveFromY,
2472 premoveToX, premoveToY,
2477 /* Usually suppress following prompt */
2478 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2479 if (looking_at(buf, &i, "*% ")) {
2480 savingComment = FALSE;
2484 } else if (started == STARTED_HOLDINGS) {
2486 char new_piece[MSG_SIZ];
2487 started = STARTED_NONE;
2488 parse[parse_pos] = NULLCHAR;
2489 if (appData.debugMode)
2490 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2491 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2492 gamenum == ics_gamenum) {
2493 if (gameInfo.variant == VariantNormal) {
2494 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2495 /* Get a move list just to see the header, which
2496 will tell us whether this is really bug or zh */
2497 if (ics_getting_history == H_FALSE) {
2498 ics_getting_history = H_REQUESTED;
2499 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2503 new_piece[0] = NULLCHAR;
2504 sscanf(parse, "game %d white [%s black [%s <- %s",
2505 &gamenum, white_holding, black_holding,
2507 white_holding[strlen(white_holding)-1] = NULLCHAR;
2508 black_holding[strlen(black_holding)-1] = NULLCHAR;
2510 if (appData.zippyPlay && first.initDone) {
2511 ZippyHoldings(white_holding, black_holding,
2515 if (tinyLayout || smallLayout) {
2516 char wh[16], bh[16];
2517 PackHolding(wh, white_holding);
2518 PackHolding(bh, black_holding);
2519 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2520 gameInfo.white, gameInfo.black);
2522 sprintf(str, "%s [%s] vs. %s [%s]",
2523 gameInfo.white, white_holding,
2524 gameInfo.black, black_holding);
2526 DrawPosition(FALSE, NULL);
2529 /* Suppress following prompt */
2530 if (looking_at(buf, &i, "*% ")) {
2531 savingComment = FALSE;
2538 i++; /* skip unparsed character and loop back */
2541 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2542 started != STARTED_HOLDINGS && i > next_out) {
2543 SendToPlayer(&buf[next_out], i - next_out);
2547 leftover_len = buf_len - leftover_start;
2548 /* if buffer ends with something we couldn't parse,
2549 reparse it after appending the next read */
2551 } else if (count == 0) {
2552 RemoveInputSource(isr);
2553 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2555 DisplayFatalError(_("Error reading from ICS"), error, 1);
2560 /* Board style 12 looks like this:
2562 <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
2564 * The "<12> " is stripped before it gets to this routine. The two
2565 * trailing 0's (flip state and clock ticking) are later addition, and
2566 * some chess servers may not have them, or may have only the first.
2567 * Additional trailing fields may be added in the future.
2570 #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"
2572 #define RELATION_OBSERVING_PLAYED 0
2573 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2574 #define RELATION_PLAYING_MYMOVE 1
2575 #define RELATION_PLAYING_NOTMYMOVE -1
2576 #define RELATION_EXAMINING 2
2577 #define RELATION_ISOLATED_BOARD -3
2578 #define RELATION_STARTING_POSITION -4 /* FICS only */
2581 ParseBoard12(string)
2584 GameMode newGameMode;
2585 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2586 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2587 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2588 char to_play, board_chars[72];
2589 char move_str[500], str[500], elapsed_time[500];
2590 char black[32], white[32];
2592 int prevMove = currentMove;
2595 int fromX, fromY, toX, toY;
2598 fromX = fromY = toX = toY = -1;
2602 if (appData.debugMode)
2603 fprintf(debugFP, _("Parsing board: %s\n"), string);
2605 move_str[0] = NULLCHAR;
2606 elapsed_time[0] = NULLCHAR;
2607 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2608 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2609 &gamenum, white, black, &relation, &basetime, &increment,
2610 &white_stren, &black_stren, &white_time, &black_time,
2611 &moveNum, str, elapsed_time, move_str, &ics_flip,
2615 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2616 DisplayError(str, 0);
2620 /* Convert the move number to internal form */
2621 moveNum = (moveNum - 1) * 2;
2622 if (to_play == 'B') moveNum++;
2623 if (moveNum >= MAX_MOVES) {
2624 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2630 case RELATION_OBSERVING_PLAYED:
2631 case RELATION_OBSERVING_STATIC:
2632 if (gamenum == -1) {
2633 /* Old ICC buglet */
2634 relation = RELATION_OBSERVING_STATIC;
2636 newGameMode = IcsObserving;
2638 case RELATION_PLAYING_MYMOVE:
2639 case RELATION_PLAYING_NOTMYMOVE:
2641 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2642 IcsPlayingWhite : IcsPlayingBlack;
2644 case RELATION_EXAMINING:
2645 newGameMode = IcsExamining;
2647 case RELATION_ISOLATED_BOARD:
2649 /* Just display this board. If user was doing something else,
2650 we will forget about it until the next board comes. */
2651 newGameMode = IcsIdle;
2653 case RELATION_STARTING_POSITION:
2654 newGameMode = gameMode;
2658 /* Modify behavior for initial board display on move listing
2661 switch (ics_getting_history) {
2665 case H_GOT_REQ_HEADER:
2666 case H_GOT_UNREQ_HEADER:
2667 /* This is the initial position of the current game */
2668 gamenum = ics_gamenum;
2669 moveNum = 0; /* old ICS bug workaround */
2670 if (to_play == 'B') {
2671 startedFromSetupPosition = TRUE;
2672 blackPlaysFirst = TRUE;
2674 if (forwardMostMove == 0) forwardMostMove = 1;
2675 if (backwardMostMove == 0) backwardMostMove = 1;
2676 if (currentMove == 0) currentMove = 1;
2678 newGameMode = gameMode;
2679 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2681 case H_GOT_UNWANTED_HEADER:
2682 /* This is an initial board that we don't want */
2684 case H_GETTING_MOVES:
2685 /* Should not happen */
2686 DisplayError(_("Error gathering move list: extra board"), 0);
2687 ics_getting_history = H_FALSE;
2691 /* Take action if this is the first board of a new game, or of a
2692 different game than is currently being displayed. */
2693 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2694 relation == RELATION_ISOLATED_BOARD) {
2696 /* Forget the old game and get the history (if any) of the new one */
2697 if (gameMode != BeginningOfGame) {
2701 if (appData.autoRaiseBoard) BoardToTop();
2703 if (gamenum == -1) {
2704 newGameMode = IcsIdle;
2705 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2706 appData.getMoveList) {
2707 /* Need to get game history */
2708 ics_getting_history = H_REQUESTED;
2709 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2713 /* Initially flip the board to have black on the bottom if playing
2714 black or if the ICS flip flag is set, but let the user change
2715 it with the Flip View button. */
2716 flipView = appData.autoFlipView ?
2717 (newGameMode == IcsPlayingBlack) || ics_flip :
2720 /* Done with values from previous mode; copy in new ones */
2721 gameMode = newGameMode;
2723 ics_gamenum = gamenum;
2724 if (gamenum == gs_gamenum) {
2725 int klen = strlen(gs_kind);
2726 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2727 sprintf(str, "ICS %s", gs_kind);
2728 gameInfo.event = StrSave(str);
2730 gameInfo.event = StrSave("ICS game");
2732 gameInfo.site = StrSave(appData.icsHost);
2733 gameInfo.date = PGNDate();
2734 gameInfo.round = StrSave("-");
2735 gameInfo.white = StrSave(white);
2736 gameInfo.black = StrSave(black);
2737 timeControl = basetime * 60 * 1000;
2738 timeIncrement = increment * 1000;
2739 movesPerSession = 0;
2740 gameInfo.timeControl = TimeControlTagValue();
2741 gameInfo.variant = StringToVariant(gameInfo.event);
2743 /* Do we have the ratings? */
2744 if (strcmp(player1Name, white) == 0 &&
2745 strcmp(player2Name, black) == 0) {
2746 if (appData.debugMode)
2747 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2748 player1Rating, player2Rating);
2749 gameInfo.whiteRating = player1Rating;
2750 gameInfo.blackRating = player2Rating;
2751 } else if (strcmp(player2Name, white) == 0 &&
2752 strcmp(player1Name, black) == 0) {
2753 if (appData.debugMode)
2754 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2755 player2Rating, player1Rating);
2756 gameInfo.whiteRating = player2Rating;
2757 gameInfo.blackRating = player1Rating;
2759 player1Name[0] = player2Name[0] = NULLCHAR;
2761 /* Silence shouts if requested */
2762 if (appData.quietPlay &&
2763 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2764 SendToICS(ics_prefix);
2765 SendToICS("set shout 0\n");
2769 /* Deal with midgame name changes */
2771 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2772 if (gameInfo.white) free(gameInfo.white);
2773 gameInfo.white = StrSave(white);
2775 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2776 if (gameInfo.black) free(gameInfo.black);
2777 gameInfo.black = StrSave(black);
2781 /* Throw away game result if anything actually changes in examine mode */
2782 if (gameMode == IcsExamining && !newGame) {
2783 gameInfo.result = GameUnfinished;
2784 if (gameInfo.resultDetails != NULL) {
2785 free(gameInfo.resultDetails);
2786 gameInfo.resultDetails = NULL;
2790 /* In pausing && IcsExamining mode, we ignore boards coming
2791 in if they are in a different variation than we are. */
2792 if (pauseExamInvalid) return;
2793 if (pausing && gameMode == IcsExamining) {
2794 if (moveNum <= pauseExamForwardMostMove) {
2795 pauseExamInvalid = TRUE;
2796 forwardMostMove = pauseExamForwardMostMove;
2801 /* Parse the board */
2802 for (k = 0; k < 8; k++)
2803 for (j = 0; j < 8; j++)
2804 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2805 CopyBoard(boards[moveNum], board);
2807 startedFromSetupPosition =
2808 !CompareBoards(board, initialPosition);
2811 if (ics_getting_history == H_GOT_REQ_HEADER ||
2812 ics_getting_history == H_GOT_UNREQ_HEADER) {
2813 /* This was an initial position from a move list, not
2814 the current position */
2818 /* Update currentMove and known move number limits */
2819 newMove = newGame || moveNum > forwardMostMove;
2821 forwardMostMove = backwardMostMove = currentMove = moveNum;
2822 if (gameMode == IcsExamining && moveNum == 0) {
2823 /* Workaround for ICS limitation: we are not told the wild
2824 type when starting to examine a game. But if we ask for
2825 the move list, the move list header will tell us */
2826 ics_getting_history = H_REQUESTED;
2827 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2830 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2831 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2832 forwardMostMove = moveNum;
2833 if (!pausing || currentMove > forwardMostMove)
2834 currentMove = forwardMostMove;
2836 /* New part of history that is not contiguous with old part */
2837 if (pausing && gameMode == IcsExamining) {
2838 pauseExamInvalid = TRUE;
2839 forwardMostMove = pauseExamForwardMostMove;
2842 forwardMostMove = backwardMostMove = currentMove = moveNum;
2843 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2844 ics_getting_history = H_REQUESTED;
2845 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2850 /* Update the clocks */
2851 if (strchr(elapsed_time, '.')) {
2853 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2854 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2856 /* Time is in seconds */
2857 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2858 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2863 if (appData.zippyPlay && newGame &&
2864 gameMode != IcsObserving && gameMode != IcsIdle &&
2865 gameMode != IcsExamining)
2866 ZippyFirstBoard(moveNum, basetime, increment);
2869 /* Put the move on the move list, first converting
2870 to canonical algebraic form. */
2872 if (moveNum <= backwardMostMove) {
2873 /* We don't know what the board looked like before
2875 strcpy(parseList[moveNum - 1], move_str);
2876 strcat(parseList[moveNum - 1], " ");
2877 strcat(parseList[moveNum - 1], elapsed_time);
2878 moveList[moveNum - 1][0] = NULLCHAR;
2879 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2880 &fromX, &fromY, &toX, &toY, &promoChar)) {
2881 (void) CoordsToAlgebraic(boards[moveNum - 1],
2882 PosFlags(moveNum - 1), EP_UNKNOWN,
2883 fromY, fromX, toY, toX, promoChar,
2884 parseList[moveNum-1]);
2885 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2891 strcat(parseList[moveNum - 1], "+");
2894 strcat(parseList[moveNum - 1], "#");
2897 strcat(parseList[moveNum - 1], " ");
2898 strcat(parseList[moveNum - 1], elapsed_time);
2899 /* currentMoveString is set as a side-effect of ParseOneMove */
2900 strcpy(moveList[moveNum - 1], currentMoveString);
2901 strcat(moveList[moveNum - 1], "\n");
2902 } else if (strcmp(move_str, "none") == 0) {
2903 /* Again, we don't know what the board looked like;
2904 this is really the start of the game. */
2905 parseList[moveNum - 1][0] = NULLCHAR;
2906 moveList[moveNum - 1][0] = NULLCHAR;
2907 backwardMostMove = moveNum;
2908 startedFromSetupPosition = TRUE;
2909 fromX = fromY = toX = toY = -1;
2911 /* Move from ICS was illegal!? Punt. */
2913 if (appData.testLegality && appData.debugMode) {
2914 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2915 DisplayError(str, 0);
2918 strcpy(parseList[moveNum - 1], move_str);
2919 strcat(parseList[moveNum - 1], " ");
2920 strcat(parseList[moveNum - 1], elapsed_time);
2921 moveList[moveNum - 1][0] = NULLCHAR;
2922 fromX = fromY = toX = toY = -1;
2926 /* Send move to chess program (BEFORE animating it). */
2927 if (appData.zippyPlay && !newGame && newMove &&
2928 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2930 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2931 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2932 if (moveList[moveNum - 1][0] == NULLCHAR) {
2933 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2935 DisplayError(str, 0);
2937 if (first.sendTime) {
2938 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2940 SendMoveToProgram(moveNum - 1, &first);
2943 if (first.useColors) {
2944 SendToProgram(gameMode == IcsPlayingWhite ?
2946 "black\ngo\n", &first);
2948 SendToProgram("go\n", &first);
2950 first.maybeThinking = TRUE;
2953 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2954 if (moveList[moveNum - 1][0] == NULLCHAR) {
2955 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
2956 DisplayError(str, 0);
2958 SendMoveToProgram(moveNum - 1, &first);
2965 if (moveNum > 0 && !gotPremove) {
2966 /* If move comes from a remote source, animate it. If it
2967 isn't remote, it will have already been animated. */
2968 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
2969 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
2971 if (!pausing && appData.highlightLastMove) {
2972 SetHighlights(fromX, fromY, toX, toY);
2976 /* Start the clocks */
2977 whiteFlag = blackFlag = FALSE;
2978 appData.clockMode = !(basetime == 0 && increment == 0);
2980 ics_clock_paused = TRUE;
2982 } else if (ticking == 1) {
2983 ics_clock_paused = FALSE;
2985 if (gameMode == IcsIdle ||
2986 relation == RELATION_OBSERVING_STATIC ||
2987 relation == RELATION_EXAMINING ||
2989 DisplayBothClocks();
2993 /* Display opponents and material strengths */
2994 if (gameInfo.variant != VariantBughouse &&
2995 gameInfo.variant != VariantCrazyhouse) {
2996 if (tinyLayout || smallLayout) {
2997 sprintf(str, "%s(%d) %s(%d) {%d %d}",
2998 gameInfo.white, white_stren, gameInfo.black, black_stren,
2999 basetime, increment);
3001 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3002 gameInfo.white, white_stren, gameInfo.black, black_stren,
3003 basetime, increment);
3009 /* Display the board */
3012 if (appData.premove)
3014 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3015 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3016 ClearPremoveHighlights();
3018 DrawPosition(FALSE, boards[currentMove]);
3019 DisplayMove(moveNum - 1);
3020 if (appData.ringBellAfterMoves && !ics_user_moved)
3024 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3031 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3032 ics_getting_history = H_REQUESTED;
3033 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3039 AnalysisPeriodicEvent(force)
3042 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3043 && !force) || !appData.periodicUpdates)
3046 /* Send . command to Crafty to collect stats */
3047 SendToProgram(".\n", &first);
3049 /* Don't send another until we get a response (this makes
3050 us stop sending to old Crafty's which don't understand
3051 the "." command (sending illegal cmds resets node count & time,
3052 which looks bad)) */
3053 programStats.ok_to_send = 0;
3057 SendMoveToProgram(moveNum, cps)
3059 ChessProgramState *cps;
3062 if (cps->useUsermove) {
3063 SendToProgram("usermove ", cps);
3067 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3068 int len = space - parseList[moveNum];
3069 memcpy(buf, parseList[moveNum], len);
3071 buf[len] = NULLCHAR;
3073 sprintf(buf, "%s\n", parseList[moveNum]);
3075 SendToProgram(buf, cps);
3077 SendToProgram(moveList[moveNum], cps);
3082 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3084 int fromX, fromY, toX, toY;
3086 char user_move[MSG_SIZ];
3090 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3091 (int)moveType, fromX, fromY, toX, toY);
3092 DisplayError(user_move + strlen("say "), 0);
3094 case WhiteKingSideCastle:
3095 case BlackKingSideCastle:
3096 case WhiteQueenSideCastleWild:
3097 case BlackQueenSideCastleWild:
3098 sprintf(user_move, "o-o\n");
3100 case WhiteQueenSideCastle:
3101 case BlackQueenSideCastle:
3102 case WhiteKingSideCastleWild:
3103 case BlackKingSideCastleWild:
3104 sprintf(user_move, "o-o-o\n");
3106 case WhitePromotionQueen:
3107 case BlackPromotionQueen:
3108 case WhitePromotionRook:
3109 case BlackPromotionRook:
3110 case WhitePromotionBishop:
3111 case BlackPromotionBishop:
3112 case WhitePromotionKnight:
3113 case BlackPromotionKnight:
3114 case WhitePromotionKing:
3115 case BlackPromotionKing:
3116 sprintf(user_move, "%c%c%c%c=%c\n",
3117 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3118 PieceToChar(PromoPiece(moveType)));
3122 sprintf(user_move, "%c@%c%c\n",
3123 ToUpper(PieceToChar((ChessSquare) fromX)),
3124 'a' + toX, '1' + toY);
3127 case WhiteCapturesEnPassant:
3128 case BlackCapturesEnPassant:
3129 case IllegalMove: /* could be a variant we don't quite understand */
3130 sprintf(user_move, "%c%c%c%c\n",
3131 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3134 SendToICS(user_move);
3138 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3143 if (rf == DROP_RANK) {
3144 sprintf(move, "%c@%c%c\n",
3145 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3147 if (promoChar == 'x' || promoChar == NULLCHAR) {
3148 sprintf(move, "%c%c%c%c\n",
3149 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3151 sprintf(move, "%c%c%c%c%c\n",
3152 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3158 ProcessICSInitScript(f)
3163 while (fgets(buf, MSG_SIZ, f)) {
3164 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3171 /* Parser for moves from gnuchess, ICS, or user typein box */
3173 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3176 ChessMove *moveType;
3177 int *fromX, *fromY, *toX, *toY;
3180 *moveType = yylexstr(moveNum, move);
3181 switch (*moveType) {
3182 case WhitePromotionQueen:
3183 case BlackPromotionQueen:
3184 case WhitePromotionRook:
3185 case BlackPromotionRook:
3186 case WhitePromotionBishop:
3187 case BlackPromotionBishop:
3188 case WhitePromotionKnight:
3189 case BlackPromotionKnight:
3190 case WhitePromotionKing:
3191 case BlackPromotionKing:
3193 case WhiteCapturesEnPassant:
3194 case BlackCapturesEnPassant:
3195 case WhiteKingSideCastle:
3196 case WhiteQueenSideCastle:
3197 case BlackKingSideCastle:
3198 case BlackQueenSideCastle:
3199 case WhiteKingSideCastleWild:
3200 case WhiteQueenSideCastleWild:
3201 case BlackKingSideCastleWild:
3202 case BlackQueenSideCastleWild:
3203 case IllegalMove: /* bug or odd chess variant */
3204 *fromX = currentMoveString[0] - 'a';
3205 *fromY = currentMoveString[1] - '1';
3206 *toX = currentMoveString[2] - 'a';
3207 *toY = currentMoveString[3] - '1';
3208 *promoChar = currentMoveString[4];
3209 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3210 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3211 *fromX = *fromY = *toX = *toY = 0;
3214 if (appData.testLegality) {
3215 return (*moveType != IllegalMove);
3217 return !(fromX == fromY && toX == toY);
3222 *fromX = *moveType == WhiteDrop ?
3223 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3224 (int) CharToPiece(ToLower(currentMoveString[0]));
3226 *toX = currentMoveString[2] - 'a';
3227 *toY = currentMoveString[3] - '1';
3228 *promoChar = NULLCHAR;
3232 case ImpossibleMove:
3233 case (ChessMove) 0: /* end of file */
3243 *fromX = *fromY = *toX = *toY = 0;
3244 *promoChar = NULLCHAR;
3251 InitPosition(redraw)
3254 currentMove = forwardMostMove = backwardMostMove = 0;
3255 switch (gameInfo.variant) {
3257 CopyBoard(boards[0], initialPosition);
3259 case VariantTwoKings:
3260 CopyBoard(boards[0], twoKingsPosition);
3261 startedFromSetupPosition = TRUE;
3263 case VariantWildCastle:
3264 CopyBoard(boards[0], initialPosition);
3265 /* !!?shuffle with kings guaranteed to be on d or e file */
3267 case VariantNoCastle:
3268 CopyBoard(boards[0], initialPosition);
3269 /* !!?unconstrained back-rank shuffle */
3271 case VariantFischeRandom:
3272 CopyBoard(boards[0], initialPosition);
3273 /* !!shuffle according to FR rules */
3277 DrawPosition(FALSE, boards[currentMove]);
3281 SendBoard(cps, moveNum)
3282 ChessProgramState *cps;
3285 char message[MSG_SIZ];
3287 if (cps->useSetboard) {
3288 char* fen = PositionToFEN(moveNum);
3289 sprintf(message, "setboard %s\n", fen);
3290 SendToProgram(message, cps);
3296 /* Kludge to set black to move, avoiding the troublesome and now
3297 * deprecated "black" command.
3299 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3301 SendToProgram("edit\n", cps);
3302 SendToProgram("#\n", cps);
3303 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3304 bp = &boards[moveNum][i][0];
3305 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3306 if ((int) *bp < (int) BlackPawn) {
3307 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3309 SendToProgram(message, cps);
3314 SendToProgram("c\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) EmptySquare)
3319 && ((int) *bp >= (int) BlackPawn)) {
3320 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3322 SendToProgram(message, cps);
3327 SendToProgram(".\n", cps);
3332 IsPromotion(fromX, fromY, toX, toY)
3333 int fromX, fromY, toX, toY;
3335 return gameMode != EditPosition &&
3336 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3337 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3338 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3343 PieceForSquare (x, y)
3347 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3350 return boards[currentMove][y][x];
3354 OKToStartUserMove(x, y)
3357 ChessSquare from_piece;
3360 if (matchMode) return FALSE;
3361 if (gameMode == EditPosition) return TRUE;
3363 if (x >= 0 && y >= 0)
3364 from_piece = boards[currentMove][y][x];
3366 from_piece = EmptySquare;
3368 if (from_piece == EmptySquare) return FALSE;
3370 white_piece = (int)from_piece >= (int)WhitePawn &&
3371 (int)from_piece <= (int)WhiteKing;
3374 case PlayFromGameFile:
3376 case TwoMachinesPlay:
3384 case MachinePlaysWhite:
3385 case IcsPlayingBlack:
3386 if (appData.zippyPlay) return FALSE;
3388 DisplayMoveError(_("You are playing Black"));
3393 case MachinePlaysBlack:
3394 case IcsPlayingWhite:
3395 if (appData.zippyPlay) return FALSE;
3397 DisplayMoveError(_("You are playing White"));
3403 if (!white_piece && WhiteOnMove(currentMove)) {
3404 DisplayMoveError(_("It is White's turn"));
3407 if (white_piece && !WhiteOnMove(currentMove)) {
3408 DisplayMoveError(_("It is Black's turn"));
3411 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3412 /* Editing correspondence game history */
3413 /* Could disallow this or prompt for confirmation */
3416 if (currentMove < forwardMostMove) {
3417 /* Discarding moves */
3418 /* Could prompt for confirmation here,
3419 but I don't think that's such a good idea */
3420 forwardMostMove = currentMove;
3424 case BeginningOfGame:
3425 if (appData.icsActive) return FALSE;
3426 if (!appData.noChessProgram) {
3428 DisplayMoveError(_("You are playing White"));
3435 if (!white_piece && WhiteOnMove(currentMove)) {
3436 DisplayMoveError(_("It is White's turn"));
3439 if (white_piece && !WhiteOnMove(currentMove)) {
3440 DisplayMoveError(_("It is Black's turn"));
3449 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3450 && gameMode != AnalyzeFile && gameMode != Training) {
3451 DisplayMoveError(_("Displayed position is not current"));
3457 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3458 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3459 int lastLoadGameUseList = FALSE;
3460 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3461 ChessMove lastLoadGameStart = (ChessMove) 0;
3465 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3466 int fromX, fromY, toX, toY;
3471 if (fromX < 0 || fromY < 0) return;
3472 if ((fromX == toX) && (fromY == toY)) {
3476 /* Check if the user is playing in turn. This is complicated because we
3477 let the user "pick up" a piece before it is his turn. So the piece he
3478 tried to pick up may have been captured by the time he puts it down!
3479 Therefore we use the color the user is supposed to be playing in this
3480 test, not the color of the piece that is currently on the starting
3481 square---except in EditGame mode, where the user is playing both
3482 sides; fortunately there the capture race can't happen. (It can
3483 now happen in IcsExamining mode, but that's just too bad. The user
3484 will get a somewhat confusing message in that case.)
3488 case PlayFromGameFile:
3490 case TwoMachinesPlay:
3494 /* We switched into a game mode where moves are not accepted,
3495 perhaps while the mouse button was down. */
3498 case MachinePlaysWhite:
3499 /* User is moving for Black */
3500 if (WhiteOnMove(currentMove)) {
3501 DisplayMoveError(_("It is White's turn"));
3506 case MachinePlaysBlack:
3507 /* User is moving for White */
3508 if (!WhiteOnMove(currentMove)) {
3509 DisplayMoveError(_("It is Black's turn"));
3516 case BeginningOfGame:
3519 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3520 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3521 /* User is moving for Black */
3522 if (WhiteOnMove(currentMove)) {
3523 DisplayMoveError(_("It is White's turn"));
3527 /* User is moving for White */
3528 if (!WhiteOnMove(currentMove)) {
3529 DisplayMoveError(_("It is Black's turn"));
3535 case IcsPlayingBlack:
3536 /* User is moving for Black */
3537 if (WhiteOnMove(currentMove)) {
3538 if (!appData.premove) {
3539 DisplayMoveError(_("It is White's turn"));
3540 } else if (toX >= 0 && toY >= 0) {
3543 premoveFromX = fromX;
3544 premoveFromY = fromY;
3545 premovePromoChar = promoChar;
3547 if (appData.debugMode)
3548 fprintf(debugFP, "Got premove: fromX %d,"
3549 "fromY %d, toX %d, toY %d\n",
3550 fromX, fromY, toX, toY);
3556 case IcsPlayingWhite:
3557 /* User is moving for White */
3558 if (!WhiteOnMove(currentMove)) {
3559 if (!appData.premove) {
3560 DisplayMoveError(_("It is Black's turn"));
3561 } else if (toX >= 0 && toY >= 0) {
3564 premoveFromX = fromX;
3565 premoveFromY = fromY;
3566 premovePromoChar = promoChar;
3568 if (appData.debugMode)
3569 fprintf(debugFP, "Got premove: fromX %d,"
3570 "fromY %d, toX %d, toY %d\n",
3571 fromX, fromY, toX, toY);
3581 if (toX == -2 || toY == -2) {
3582 boards[0][fromY][fromX] = EmptySquare;
3583 DrawPosition(FALSE, boards[currentMove]);
3584 } else if (toX >= 0 && toY >= 0) {
3585 boards[0][toY][toX] = boards[0][fromY][fromX];
3586 boards[0][fromY][fromX] = EmptySquare;
3587 DrawPosition(FALSE, boards[currentMove]);
3592 if (toX < 0 || toY < 0) return;
3593 userOfferedDraw = FALSE;
3595 if (appData.testLegality) {
3596 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3597 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3598 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3599 DisplayMoveError(_("Illegal move"));
3603 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3606 if (gameMode == Training) {
3607 /* compare the move played on the board to the next move in the
3608 * game. If they match, display the move and the opponent's response.
3609 * If they don't match, display an error message.
3613 CopyBoard(testBoard, boards[currentMove]);
3614 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3616 if (CompareBoards(testBoard, boards[currentMove+1])) {
3617 ForwardInner(currentMove+1);
3619 /* Autoplay the opponent's response.
3620 * if appData.animate was TRUE when Training mode was entered,
3621 * the response will be animated.
3623 saveAnimate = appData.animate;
3624 appData.animate = animateTraining;
3625 ForwardInner(currentMove+1);
3626 appData.animate = saveAnimate;
3628 /* check for the end of the game */
3629 if (currentMove >= forwardMostMove) {
3630 gameMode = PlayFromGameFile;
3632 SetTrainingModeOff();
3633 DisplayInformation(_("End of game"));
3636 DisplayError(_("Incorrect move"), 0);
3641 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3644 /* Common tail of UserMoveEvent and DropMenuEvent */
3646 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3648 int fromX, fromY, toX, toY;
3649 /*char*/int promoChar;
3651 /* Ok, now we know that the move is good, so we can kill
3652 the previous line in Analysis Mode */
3653 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3654 forwardMostMove = currentMove;
3657 /* If we need the chess program but it's dead, restart it */
3658 ResurrectChessProgram();
3660 /* A user move restarts a paused game*/
3664 thinkOutput[0] = NULLCHAR;
3666 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3668 if (gameMode == BeginningOfGame) {
3669 if (appData.noChessProgram) {
3670 gameMode = EditGame;
3674 gameMode = MachinePlaysBlack;
3676 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3678 if (first.sendName) {
3679 sprintf(buf, "name %s\n", gameInfo.white);
3680 SendToProgram(buf, &first);
3686 /* Relay move to ICS or chess engine */
3687 if (appData.icsActive) {
3688 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3689 gameMode == IcsExamining) {
3690 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3694 if (first.sendTime && (gameMode == BeginningOfGame ||
3695 gameMode == MachinePlaysWhite ||
3696 gameMode == MachinePlaysBlack)) {
3697 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3699 SendMoveToProgram(forwardMostMove-1, &first);
3700 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3701 first.maybeThinking = TRUE;
3703 if (currentMove == cmailOldMove + 1) {
3704 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3708 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3712 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3718 if (WhiteOnMove(currentMove)) {
3719 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3721 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3725 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3730 case MachinePlaysBlack:
3731 case MachinePlaysWhite:
3732 /* disable certain menu options while machine is thinking */
3733 SetMachineThinkingEnables();
3742 HandleMachineMove(message, cps)
3744 ChessProgramState *cps;
3746 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3747 char realname[MSG_SIZ];
3748 int fromX, fromY, toX, toY;
3755 * Kludge to ignore BEL characters
3757 while (*message == '\007') message++;
3760 * Look for book output
3762 if (cps == &first && bookRequested) {
3763 if (message[0] == '\t' || message[0] == ' ') {
3764 /* Part of the book output is here; append it */
3765 strcat(bookOutput, message);
3766 strcat(bookOutput, " \n");
3768 } else if (bookOutput[0] != NULLCHAR) {
3769 /* All of book output has arrived; display it */
3770 char *p = bookOutput;
3771 while (*p != NULLCHAR) {
3772 if (*p == '\t') *p = ' ';
3775 DisplayInformation(bookOutput);
3776 bookRequested = FALSE;
3777 /* Fall through to parse the current output */
3782 * Look for machine move.
3784 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3785 strcmp(buf2, "...") == 0) ||
3786 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3787 strcmp(buf1, "move") == 0)) {
3789 /* This method is only useful on engines that support ping */
3790 if (cps->lastPing != cps->lastPong) {
3791 if (gameMode == BeginningOfGame) {
3792 /* Extra move from before last new; ignore */
3793 if (appData.debugMode) {
3794 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3797 if (appData.debugMode) {
3798 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3799 cps->which, gameMode);
3801 SendToProgram("undo\n", cps);
3807 case BeginningOfGame:
3808 /* Extra move from before last reset; ignore */
3809 if (appData.debugMode) {
3810 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3817 /* Extra move after we tried to stop. The mode test is
3818 not a reliable way of detecting this problem, but it's
3819 the best we can do on engines that don't support ping.
3821 if (appData.debugMode) {
3822 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3823 cps->which, gameMode);
3825 SendToProgram("undo\n", cps);
3828 case MachinePlaysWhite:
3829 case IcsPlayingWhite:
3830 machineWhite = TRUE;
3833 case MachinePlaysBlack:
3834 case IcsPlayingBlack:
3835 machineWhite = FALSE;
3838 case TwoMachinesPlay:
3839 machineWhite = (cps->twoMachinesColor[0] == 'w');
3842 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3843 if (appData.debugMode) {
3845 "Ignoring move out of turn by %s, gameMode %d"
3846 ", forwardMost %d\n",
3847 cps->which, gameMode, forwardMostMove);
3852 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3853 &fromX, &fromY, &toX, &toY, &promoChar)) {
3854 /* Machine move could not be parsed; ignore it. */
3855 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3856 machineMove, cps->which);
3857 DisplayError(buf1, 0);
3858 if (gameMode == TwoMachinesPlay) {
3859 GameEnds(machineWhite ? BlackWins : WhiteWins,
3860 "Forfeit due to illegal move", GE_XBOARD);
3865 hintRequested = FALSE;
3866 lastHint[0] = NULLCHAR;
3867 bookRequested = FALSE;
3868 /* Program may be pondering now */
3869 cps->maybeThinking = TRUE;
3870 if (cps->sendTime == 2) cps->sendTime = 1;
3871 if (cps->offeredDraw) cps->offeredDraw--;
3874 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3876 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3880 /* currentMoveString is set as a side-effect of ParseOneMove */
3881 strcpy(machineMove, currentMoveString);
3882 strcat(machineMove, "\n");
3883 strcpy(moveList[forwardMostMove], machineMove);
3885 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3887 if (gameMode == TwoMachinesPlay) {
3888 if (cps->other->sendTime) {
3889 SendTimeRemaining(cps->other,
3890 cps->other->twoMachinesColor[0] == 'w');
3892 SendMoveToProgram(forwardMostMove-1, cps->other);
3895 if (cps->other->useColors) {
3896 SendToProgram(cps->other->twoMachinesColor, cps->other);
3898 SendToProgram("go\n", cps->other);
3900 cps->other->maybeThinking = TRUE;
3903 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3904 if (!pausing && appData.ringBellAfterMoves) {
3908 * Reenable menu items that were disabled while
3909 * machine was thinking
3911 if (gameMode != TwoMachinesPlay)
3912 SetUserThinkingEnables();
3916 /* Set special modes for chess engines. Later something general
3917 * could be added here; for now there is just one kludge feature,
3918 * needed because Crafty 15.10 and earlier don't ignore SIGINT
3919 * when "xboard" is given as an interactive command.
3921 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3922 cps->useSigint = FALSE;
3923 cps->useSigterm = FALSE;
3927 * Look for communication commands
3929 if (!strncmp(message, "telluser ", 9)) {
3930 DisplayNote(message + 9);
3933 if (!strncmp(message, "tellusererror ", 14)) {
3934 DisplayError(message + 14, 0);
3937 if (!strncmp(message, "tellopponent ", 13)) {
3938 if (appData.icsActive) {
3940 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3944 DisplayNote(message + 13);
3948 if (!strncmp(message, "tellothers ", 11)) {
3949 if (appData.icsActive) {
3951 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
3957 if (!strncmp(message, "tellall ", 8)) {
3958 if (appData.icsActive) {
3960 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
3964 DisplayNote(message + 8);
3968 if (strncmp(message, "warning", 7) == 0) {
3969 /* Undocumented feature, use tellusererror in new code */
3970 DisplayError(message, 0);
3973 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
3974 strcpy(realname, cps->tidy);
3975 strcat(realname, " query");
3976 AskQuestion(realname, buf2, buf1, cps->pr);
3979 /* Commands from the engine directly to ICS. We don't allow these to be
3980 * sent until we are logged on. Crafty kibitzes have been known to
3981 * interfere with the login process.
3984 if (!strncmp(message, "tellics ", 8)) {
3985 SendToICS(message + 8);
3989 if (!strncmp(message, "tellicsnoalias ", 15)) {
3990 SendToICS(ics_prefix);
3991 SendToICS(message + 15);
3995 /* The following are for backward compatibility only */
3996 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
3997 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
3998 SendToICS(ics_prefix);
4004 if (strncmp(message, "feature ", 8) == 0) {
4005 ParseFeatures(message+8, cps);
4007 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4011 * If the move is illegal, cancel it and redraw the board.
4012 * Also deal with other error cases. Matching is rather loose
4013 * here to accommodate engines written before the spec.
4015 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4016 strncmp(message, "Error", 5) == 0) {
4017 if (StrStr(message, "name") ||
4018 StrStr(message, "rating") || StrStr(message, "?") ||
4019 StrStr(message, "result") || StrStr(message, "board") ||
4020 StrStr(message, "bk") || StrStr(message, "computer") ||
4021 StrStr(message, "variant") || StrStr(message, "hint") ||
4022 StrStr(message, "random") || StrStr(message, "depth") ||
4023 StrStr(message, "accepted")) {
4026 if (StrStr(message, "protover")) {
4027 /* Program is responding to input, so it's apparently done
4028 initializing, and this error message indicates it is
4029 protocol version 1. So we don't need to wait any longer
4030 for it to initialize and send feature commands. */
4031 FeatureDone(cps, 1);
4032 cps->protocolVersion = 1;
4035 cps->maybeThinking = FALSE;
4037 if (StrStr(message, "draw")) {
4038 /* Program doesn't have "draw" command */
4039 cps->sendDrawOffers = 0;
4042 if (cps->sendTime != 1 &&
4043 (StrStr(message, "time") || StrStr(message, "otim"))) {
4044 /* Program apparently doesn't have "time" or "otim" command */
4048 if (StrStr(message, "analyze")) {
4049 cps->analysisSupport = FALSE;
4050 cps->analyzing = FALSE;
4052 sprintf(buf2, "%s does not support analysis", cps->tidy);
4053 DisplayError(buf2, 0);
4056 if (StrStr(message, "(no matching move)st")) {
4057 /* Special kludge for GNU Chess 4 only */
4058 cps->stKludge = TRUE;
4059 SendTimeControl(cps, movesPerSession, timeControl,
4060 timeIncrement, appData.searchDepth,
4064 if (StrStr(message, "(no matching move)sd")) {
4065 /* Special kludge for GNU Chess 4 only */
4066 cps->sdKludge = TRUE;
4067 SendTimeControl(cps, movesPerSession, timeControl,
4068 timeIncrement, appData.searchDepth,
4072 if (!StrStr(message, "llegal")) return;
4073 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4074 gameMode == IcsIdle) return;
4075 if (forwardMostMove <= backwardMostMove) return;
4077 /* Following removed: it caused a bug where a real illegal move
4078 message in analyze mored would be ignored. */
4079 if (cps == &first && programStats.ok_to_send == 0) {
4080 /* Bogus message from Crafty responding to "." This filtering
4081 can miss some of the bad messages, but fortunately the bug
4082 is fixed in current Crafty versions, so it doesn't matter. */
4086 if (pausing) PauseEvent();
4087 if (gameMode == PlayFromGameFile) {
4088 /* Stop reading this game file */
4089 gameMode = EditGame;
4092 currentMove = --forwardMostMove;
4093 DisplayMove(currentMove-1); /* before DisplayMoveError */
4095 DisplayBothClocks();
4096 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
4097 parseList[currentMove], cps->which);
4098 DisplayMoveError(buf1);
4099 DrawPosition(FALSE, boards[currentMove]);
4102 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4103 /* Program has a broken "time" command that
4104 outputs a string not ending in newline.
4110 * If chess program startup fails, exit with an error message.
4111 * Attempts to recover here are futile.
4113 if ((StrStr(message, "unknown host") != NULL)
4114 || (StrStr(message, "No remote directory") != NULL)
4115 || (StrStr(message, "not found") != NULL)
4116 || (StrStr(message, "No such file") != NULL)
4117 || (StrStr(message, "can't alloc") != NULL)
4118 || (StrStr(message, "Permission denied") != NULL)) {
4120 cps->maybeThinking = FALSE;
4121 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
4122 cps->which, cps->program, cps->host, message);
4123 RemoveInputSource(cps->isr);
4124 DisplayFatalError(buf1, 0, 1);
4129 * Look for hint output
4131 if (sscanf(message, "Hint: %s", buf1) == 1) {
4132 if (cps == &first && hintRequested) {
4133 hintRequested = FALSE;
4134 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4135 &fromX, &fromY, &toX, &toY, &promoChar)) {
4136 (void) CoordsToAlgebraic(boards[forwardMostMove],
4137 PosFlags(forwardMostMove), EP_UNKNOWN,
4138 fromY, fromX, toY, toX, promoChar, buf1);
4139 sprintf(buf2, "Hint: %s", buf1);
4140 DisplayInformation(buf2);
4142 /* Hint move could not be parsed!? */
4144 _("Illegal hint move \"%s\"\nfrom %s chess program"),
4146 DisplayError(buf2, 0);
4149 strcpy(lastHint, buf1);
4155 * Ignore other messages if game is not in progress
4157 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4158 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4161 * look for win, lose, draw, or draw offer
4163 if (strncmp(message, "1-0", 3) == 0) {
4164 char *p, *q, *r = "";
4165 p = strchr(message, '{');
4173 GameEnds(WhiteWins, r, GE_ENGINE);
4175 } else if (strncmp(message, "0-1", 3) == 0) {
4176 char *p, *q, *r = "";
4177 p = strchr(message, '{');
4185 /* Kludge for Arasan 4.1 bug */
4186 if (strcmp(r, "Black resigns") == 0) {
4187 GameEnds(WhiteWins, r, GE_ENGINE);
4190 GameEnds(BlackWins, r, GE_ENGINE);
4192 } else if (strncmp(message, "1/2", 3) == 0) {
4193 char *p, *q, *r = "";
4194 p = strchr(message, '{');
4202 GameEnds(GameIsDrawn, r, GE_ENGINE);
4205 } else if (strncmp(message, "White resign", 12) == 0) {
4206 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4208 } else if (strncmp(message, "Black resign", 12) == 0) {
4209 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4211 } else if (strncmp(message, "White", 5) == 0 &&
4212 message[5] != '(' &&
4213 StrStr(message, "Black") == NULL) {
4214 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4216 } else if (strncmp(message, "Black", 5) == 0 &&
4217 message[5] != '(') {
4218 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4220 } else if (strcmp(message, "resign") == 0 ||
4221 strcmp(message, "computer resigns") == 0) {
4223 case MachinePlaysBlack:
4224 case IcsPlayingBlack:
4225 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4227 case MachinePlaysWhite:
4228 case IcsPlayingWhite:
4229 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4231 case TwoMachinesPlay:
4232 if (cps->twoMachinesColor[0] == 'w')
4233 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4235 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4242 } else if (strncmp(message, "opponent mates", 14) == 0) {
4244 case MachinePlaysBlack:
4245 case IcsPlayingBlack:
4246 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4248 case MachinePlaysWhite:
4249 case IcsPlayingWhite:
4250 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4252 case TwoMachinesPlay:
4253 if (cps->twoMachinesColor[0] == 'w')
4254 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4256 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4263 } else if (strncmp(message, "computer mates", 14) == 0) {
4265 case MachinePlaysBlack:
4266 case IcsPlayingBlack:
4267 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4269 case MachinePlaysWhite:
4270 case IcsPlayingWhite:
4271 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4273 case TwoMachinesPlay:
4274 if (cps->twoMachinesColor[0] == 'w')
4275 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4277 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4284 } else if (strncmp(message, "checkmate", 9) == 0) {
4285 if (WhiteOnMove(forwardMostMove)) {
4286 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4288 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4291 } else if (strstr(message, "Draw") != NULL ||
4292 strstr(message, "game is a draw") != NULL) {
4293 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4295 } else if (strstr(message, "offer") != NULL &&
4296 strstr(message, "draw") != NULL) {
4298 if (appData.zippyPlay && first.initDone) {
4299 /* Relay offer to ICS */
4300 SendToICS(ics_prefix);
4301 SendToICS("draw\n");
4304 cps->offeredDraw = 2; /* valid until this engine moves twice */
4305 if (gameMode == TwoMachinesPlay) {
4306 if (cps->other->offeredDraw) {
4307 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4309 if (cps->other->sendDrawOffers) {
4310 SendToProgram("draw\n", cps->other);
4313 } else if (gameMode == MachinePlaysWhite ||
4314 gameMode == MachinePlaysBlack) {
4315 if (userOfferedDraw) {
4316 DisplayInformation(_("Machine accepts your draw offer"));
4317 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4319 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
4326 * Look for thinking output
4328 if (appData.showThinking) {
4329 int plylev, mvleft, mvtot, curscore, time;
4330 char mvname[MOVE_LEN];
4331 unsigned long nodes;
4334 int prefixHint = FALSE;
4335 mvname[0] = NULLCHAR;
4338 case MachinePlaysBlack:
4339 case IcsPlayingBlack:
4340 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4342 case MachinePlaysWhite:
4343 case IcsPlayingWhite:
4344 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4349 case TwoMachinesPlay:
4350 if ((cps->twoMachinesColor[0] == 'w') !=
4351 WhiteOnMove(forwardMostMove)) {
4362 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
4363 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4365 if (plyext != ' ' && plyext != '\t') {
4368 programStats.depth = plylev;
4369 programStats.nodes = nodes;
4370 programStats.time = time;
4371 programStats.score = curscore;
4372 programStats.got_only_move = 0;
4374 /* Buffer overflow protection */
4375 if (buf1[0] != NULLCHAR) {
4376 if (strlen(buf1) >= sizeof(programStats.movelist)
4377 && appData.debugMode) {
4379 "PV is too long; using the first %d bytes.\n",
4380 sizeof(programStats.movelist) - 1);
4382 strncpy(programStats.movelist, buf1,
4383 sizeof(programStats.movelist));
4384 programStats.movelist[sizeof(programStats.movelist) - 1]
4387 sprintf(programStats.movelist, " no PV\n");
4390 if (programStats.seen_stat) {
4391 programStats.ok_to_send = 1;
4394 if (strchr(programStats.movelist, '(') != NULL) {
4395 programStats.line_is_book = 1;
4396 programStats.nr_moves = 0;
4397 programStats.moves_left = 0;
4399 programStats.line_is_book = 0;
4402 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4404 (gameMode == TwoMachinesPlay ?
4405 ToUpper(cps->twoMachinesColor[0]) : ' '),
4406 ((double) curscore) / 100.0,
4407 prefixHint ? lastHint : "",
4408 prefixHint ? " " : "", buf1);
4410 if (currentMove == forwardMostMove ||
4411 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4412 DisplayMove(currentMove - 1);
4417 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4418 /* crafty (9.25+) says "(only move) <move>"
4419 * if there is only 1 legal move
4421 sscanf(p, "(only move) %s", buf1);
4422 sprintf(thinkOutput, "%s (only move)", buf1);
4423 sprintf(programStats.movelist, "%s (only move)", buf1);
4424 programStats.depth = 1;
4425 programStats.nr_moves = 1;
4426 programStats.moves_left = 1;
4427 programStats.nodes = 1;
4428 programStats.time = 1;
4429 programStats.got_only_move = 1;
4431 /* Not really, but we also use this member to
4432 mean "line isn't going to change" (Crafty
4433 isn't searching, so stats won't change) */
4434 programStats.line_is_book = 1;
4436 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4437 gameMode == AnalyzeFile) {
4438 DisplayMove(currentMove - 1);
4442 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4443 &time, &nodes, &plylev, &mvleft,
4444 &mvtot, mvname) >= 5) {
4445 /* The stat01: line is from Crafty (9.29+) in response
4446 to the "." command */
4447 programStats.seen_stat = 1;
4448 cps->maybeThinking = TRUE;
4450 if (programStats.got_only_move || !appData.periodicUpdates)
4453 programStats.depth = plylev;
4454 programStats.time = time;
4455 programStats.nodes = nodes;
4456 programStats.moves_left = mvleft;
4457 programStats.nr_moves = mvtot;
4458 strcpy(programStats.move_name, mvname);
4459 programStats.ok_to_send = 1;
4463 } else if (strncmp(message,"++",2) == 0) {
4464 /* Crafty 9.29+ outputs this */
4465 programStats.got_fail = 2;
4468 } else if (strncmp(message,"--",2) == 0) {
4469 /* Crafty 9.29+ outputs this */
4470 programStats.got_fail = 1;
4473 } else if (thinkOutput[0] != NULLCHAR &&
4474 strncmp(message, " ", 4) == 0) {
4476 while (*p && *p == ' ') p++;
4477 strcat(thinkOutput, " ");
4478 strcat(thinkOutput, p);
4479 strcat(programStats.movelist, " ");
4480 strcat(programStats.movelist, p);
4481 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4482 gameMode == AnalyzeFile) {
4483 DisplayMove(currentMove - 1);
4493 /* Parse a game score from the character string "game", and
4494 record it as the history of the current game. The game
4495 score is NOT assumed to start from the standard position.
4496 The display is not updated in any way.
4499 ParseGameHistory(game)
4503 int fromX, fromY, toX, toY, boardIndex;
4508 if (appData.debugMode)
4509 fprintf(debugFP, "Parsing game history: %s\n", game);
4511 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4512 gameInfo.site = StrSave(appData.icsHost);
4513 gameInfo.date = PGNDate();
4514 gameInfo.round = StrSave("-");
4516 /* Parse out names of players */
4517 while (*game == ' ') game++;
4519 while (*game != ' ') *p++ = *game++;
4521 gameInfo.white = StrSave(buf);
4522 while (*game == ' ') game++;
4524 while (*game != ' ' && *game != '\n') *p++ = *game++;
4526 gameInfo.black = StrSave(buf);
4529 boardIndex = blackPlaysFirst ? 1 : 0;
4532 yyboardindex = boardIndex;
4533 moveType = (ChessMove) yylex();
4535 case WhitePromotionQueen:
4536 case BlackPromotionQueen:
4537 case WhitePromotionRook:
4538 case BlackPromotionRook:
4539 case WhitePromotionBishop:
4540 case BlackPromotionBishop:
4541 case WhitePromotionKnight:
4542 case BlackPromotionKnight:
4543 case WhitePromotionKing:
4544 case BlackPromotionKing:
4546 case WhiteCapturesEnPassant:
4547 case BlackCapturesEnPassant:
4548 case WhiteKingSideCastle:
4549 case WhiteQueenSideCastle:
4550 case BlackKingSideCastle:
4551 case BlackQueenSideCastle:
4552 case WhiteKingSideCastleWild:
4553 case WhiteQueenSideCastleWild:
4554 case BlackKingSideCastleWild:
4555 case BlackQueenSideCastleWild:
4556 case IllegalMove: /* maybe suicide chess, etc. */
4557 fromX = currentMoveString[0] - 'a';
4558 fromY = currentMoveString[1] - '1';
4559 toX = currentMoveString[2] - 'a';
4560 toY = currentMoveString[3] - '1';
4561 promoChar = currentMoveString[4];
4565 fromX = moveType == WhiteDrop ?
4566 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4567 (int) CharToPiece(ToLower(currentMoveString[0]));
4569 toX = currentMoveString[2] - 'a';
4570 toY = currentMoveString[3] - '1';
4571 promoChar = NULLCHAR;
4575 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4576 DisplayError(buf, 0);
4578 case ImpossibleMove:
4580 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4581 DisplayError(buf, 0);
4583 case (ChessMove) 0: /* end of file */
4584 if (boardIndex < backwardMostMove) {
4585 /* Oops, gap. How did that happen? */
4586 DisplayError(_("Gap in move list"), 0);
4589 backwardMostMove = blackPlaysFirst ? 1 : 0;
4590 if (boardIndex > forwardMostMove) {
4591 forwardMostMove = boardIndex;
4595 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4596 strcat(parseList[boardIndex-1], " ");
4597 strcat(parseList[boardIndex-1], yy_text);
4609 case GameUnfinished:
4610 if (gameMode == IcsExamining) {
4611 if (boardIndex < backwardMostMove) {
4612 /* Oops, gap. How did that happen? */
4615 backwardMostMove = blackPlaysFirst ? 1 : 0;
4618 gameInfo.result = moveType;
4619 p = strchr(yy_text, '{');
4620 if (p == NULL) p = strchr(yy_text, '(');
4623 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4625 q = strchr(p, *p == '{' ? '}' : ')');
4626 if (q != NULL) *q = NULLCHAR;
4629 gameInfo.resultDetails = StrSave(p);
4632 if (boardIndex >= forwardMostMove &&
4633 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4634 backwardMostMove = blackPlaysFirst ? 1 : 0;
4637 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4638 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4639 parseList[boardIndex]);
4640 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4641 /* currentMoveString is set as a side-effect of yylex */
4642 strcpy(moveList[boardIndex], currentMoveString);
4643 strcat(moveList[boardIndex], "\n");
4645 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4646 switch (MateTest(boards[boardIndex],
4647 PosFlags(boardIndex), EP_UNKNOWN)) {
4653 strcat(parseList[boardIndex - 1], "+");
4656 strcat(parseList[boardIndex - 1], "#");
4663 /* Apply a move to the given board */
4665 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4666 int fromX, fromY, toX, toY;
4670 ChessSquare captured = board[toY][toX];
4671 if (fromY == DROP_RANK) {
4673 board[toY][toX] = (ChessSquare) fromX;
4674 } else if (fromX == toX && fromY == toY) {
4676 } else if (fromY == 0 && fromX == 4
4677 && board[fromY][fromX] == WhiteKing
4678 && toY == 0 && toX == 6) {
4679 board[fromY][fromX] = EmptySquare;
4680 board[toY][toX] = WhiteKing;
4681 board[fromY][7] = EmptySquare;
4682 board[toY][5] = WhiteRook;
4683 } else if (fromY == 0 && fromX == 4
4684 && board[fromY][fromX] == WhiteKing
4685 && toY == 0 && toX == 2) {
4686 board[fromY][fromX] = EmptySquare;
4687 board[toY][toX] = WhiteKing;
4688 board[fromY][0] = EmptySquare;
4689 board[toY][3] = WhiteRook;
4690 } else if (fromY == 0 && fromX == 3
4691 && board[fromY][fromX] == WhiteKing
4692 && toY == 0 && toX == 5) {
4693 board[fromY][fromX] = EmptySquare;
4694 board[toY][toX] = WhiteKing;
4695 board[fromY][7] = EmptySquare;
4696 board[toY][4] = WhiteRook;
4697 } else if (fromY == 0 && fromX == 3
4698 && board[fromY][fromX] == WhiteKing
4699 && toY == 0 && toX == 1) {
4700 board[fromY][fromX] = EmptySquare;
4701 board[toY][toX] = WhiteKing;
4702 board[fromY][0] = EmptySquare;
4703 board[toY][2] = WhiteRook;
4704 } else if (board[fromY][fromX] == WhitePawn
4706 /* white pawn promotion */
4707 board[7][toX] = CharToPiece(ToUpper(promoChar));
4708 if (board[7][toX] == EmptySquare) {
4709 board[7][toX] = WhiteQueen;
4711 board[fromY][fromX] = EmptySquare;
4712 } else if ((fromY == 4)
4714 && (board[fromY][fromX] == WhitePawn)
4715 && (board[toY][toX] == EmptySquare)) {
4716 board[fromY][fromX] = EmptySquare;
4717 board[toY][toX] = WhitePawn;
4718 captured = board[toY - 1][toX];
4719 board[toY - 1][toX] = EmptySquare;
4720 } else if (fromY == 7 && fromX == 4
4721 && board[fromY][fromX] == BlackKing
4722 && toY == 7 && toX == 6) {
4723 board[fromY][fromX] = EmptySquare;
4724 board[toY][toX] = BlackKing;
4725 board[fromY][7] = EmptySquare;
4726 board[toY][5] = BlackRook;
4727 } else if (fromY == 7 && fromX == 4
4728 && board[fromY][fromX] == BlackKing
4729 && toY == 7 && toX == 2) {
4730 board[fromY][fromX] = EmptySquare;
4731 board[toY][toX] = BlackKing;
4732 board[fromY][0] = EmptySquare;
4733 board[toY][3] = BlackRook;
4734 } else if (fromY == 7 && fromX == 3
4735 && board[fromY][fromX] == BlackKing
4736 && toY == 7 && toX == 5) {
4737 board[fromY][fromX] = EmptySquare;
4738 board[toY][toX] = BlackKing;
4739 board[fromY][7] = EmptySquare;
4740 board[toY][4] = BlackRook;
4741 } else if (fromY == 7 && fromX == 3
4742 && board[fromY][fromX] == BlackKing
4743 && toY == 7 && toX == 1) {
4744 board[fromY][fromX] = EmptySquare;
4745 board[toY][toX] = BlackKing;
4746 board[fromY][0] = EmptySquare;
4747 board[toY][2] = BlackRook;
4748 } else if (board[fromY][fromX] == BlackPawn
4750 /* black pawn promotion */
4751 board[0][toX] = CharToPiece(ToLower(promoChar));
4752 if (board[0][toX] == EmptySquare) {
4753 board[0][toX] = BlackQueen;
4755 board[fromY][fromX] = EmptySquare;
4756 } else if ((fromY == 3)
4758 && (board[fromY][fromX] == BlackPawn)
4759 && (board[toY][toX] == EmptySquare)) {
4760 board[fromY][fromX] = EmptySquare;
4761 board[toY][toX] = BlackPawn;
4762 captured = board[toY + 1][toX];
4763 board[toY + 1][toX] = EmptySquare;
4765 board[toY][toX] = board[fromY][fromX];
4766 board[fromY][fromX] = EmptySquare;
4768 if (gameInfo.variant == VariantCrazyhouse) {
4770 /* !!A lot more code needs to be written to support holdings */
4771 if (fromY == DROP_RANK) {
4772 /* Delete from holdings */
4773 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4775 if (captured != EmptySquare) {
4776 /* Add to holdings */
4777 if (captured < BlackPawn) {
4778 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4780 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4784 } else if (gameInfo.variant == VariantAtomic) {
4785 if (captured != EmptySquare) {
4787 for (y = toY-1; y <= toY+1; y++) {
4788 for (x = toX-1; x <= toX+1; x++) {
4789 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4790 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4791 board[y][x] = EmptySquare;
4795 board[toY][toX] = EmptySquare;
4800 /* Updates forwardMostMove */
4802 MakeMove(fromX, fromY, toX, toY, promoChar)
4803 int fromX, fromY, toX, toY;
4807 if (forwardMostMove >= MAX_MOVES) {
4808 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4813 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4814 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4815 if (commentList[forwardMostMove] != NULL) {
4816 free(commentList[forwardMostMove]);
4817 commentList[forwardMostMove] = NULL;
4819 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4820 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4821 gameInfo.result = GameUnfinished;
4822 if (gameInfo.resultDetails != NULL) {
4823 free(gameInfo.resultDetails);
4824 gameInfo.resultDetails = NULL;
4826 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4827 moveList[forwardMostMove - 1]);
4828 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4829 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4830 fromY, fromX, toY, toX, promoChar,
4831 parseList[forwardMostMove - 1]);
4832 switch (MateTest(boards[forwardMostMove],
4833 PosFlags(forwardMostMove), EP_UNKNOWN)){
4839 strcat(parseList[forwardMostMove - 1], "+");
4842 strcat(parseList[forwardMostMove - 1], "#");
4847 /* Updates currentMove if not pausing */
4849 ShowMove(fromX, fromY, toX, toY)
4851 int instant = (gameMode == PlayFromGameFile) ?
4852 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4853 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4855 if (forwardMostMove == currentMove + 1) {
4856 AnimateMove(boards[forwardMostMove - 1],
4857 fromX, fromY, toX, toY);
4859 if (appData.highlightLastMove) {
4860 SetHighlights(fromX, fromY, toX, toY);
4863 currentMove = forwardMostMove;
4866 if (instant) return;
4867 DisplayMove(currentMove - 1);
4868 DrawPosition(FALSE, boards[currentMove]);
4869 DisplayBothClocks();
4870 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4875 InitChessProgram(cps)
4876 ChessProgramState *cps;
4879 if (appData.noChessProgram) return;
4880 hintRequested = FALSE;
4881 bookRequested = FALSE;
4882 SendToProgram(cps->initString, cps);
4883 if (gameInfo.variant != VariantNormal &&
4884 gameInfo.variant != VariantLoadable) {
4885 char *v = VariantName(gameInfo.variant);
4886 if (StrStr(cps->variants, v) == NULL) {
4887 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4888 DisplayFatalError(buf, 0, 1);
4891 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4892 SendToProgram(buf, cps);
4895 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4896 SendToProgram(buf, cps);
4898 cps->maybeThinking = FALSE;
4899 cps->offeredDraw = 0;
4900 if (!appData.icsActive) {
4901 SendTimeControl(cps, movesPerSession, timeControl,
4902 timeIncrement, appData.searchDepth,
4905 if (appData.showThinking) {
4906 SendToProgram("post\n", cps);
4908 SendToProgram("hard\n", cps);
4909 if (!appData.ponderNextMove) {
4910 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4911 it without being sure what state we are in first. "hard"
4912 is not a toggle, so that one is OK.
4914 SendToProgram("easy\n", cps);
4917 sprintf(buf, "ping %d\n", ++cps->lastPing);
4918 SendToProgram(buf, cps);
4920 cps->initDone = TRUE;
4925 StartChessProgram(cps)
4926 ChessProgramState *cps;
4931 if (appData.noChessProgram) return;
4932 cps->initDone = FALSE;
4934 if (strcmp(cps->host, "localhost") == 0) {
4935 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4936 } else if (*appData.remoteShell == NULLCHAR) {
4937 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4939 if (*appData.remoteUser == NULLCHAR) {
4940 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4943 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
4944 cps->host, appData.remoteUser, cps->program);
4946 err = StartChildProcess(buf, "", &cps->pr);
4950 sprintf(buf, "Startup failure on '%s'", cps->program);
4951 DisplayFatalError(buf, err, 1);
4957 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
4958 if (cps->protocolVersion > 1) {
4959 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
4960 SendToProgram(buf, cps);
4962 SendToProgram("xboard\n", cps);
4968 TwoMachinesEventIfReady P((void))
4970 if (first.lastPing != first.lastPong) {
4971 DisplayMessage("", "Waiting for first chess program");
4972 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4975 if (second.lastPing != second.lastPong) {
4976 DisplayMessage("", "Waiting for second chess program");
4977 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4985 NextMatchGame P((void))
4988 if (*appData.loadGameFile != NULLCHAR) {
4989 LoadGameFromFile(appData.loadGameFile,
4990 appData.loadGameIndex,
4991 appData.loadGameFile, FALSE);
4992 } else if (*appData.loadPositionFile != NULLCHAR) {
4993 LoadPositionFromFile(appData.loadPositionFile,
4994 appData.loadPositionIndex,
4995 appData.loadPositionFile);
4997 TwoMachinesEventIfReady();
5001 GameEnds(result, resultDetails, whosays)
5003 char *resultDetails;
5006 GameMode nextGameMode;
5009 if (appData.debugMode) {
5010 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5011 result, resultDetails ? resultDetails : "(null)", whosays);
5014 if (appData.icsActive && whosays == GE_ENGINE) {
5015 /* If we are playing on ICS, the server decides when the
5016 game is over, but the engine can offer to draw, claim
5020 if (appData.zippyPlay && first.initDone) {
5021 if (result == GameIsDrawn) {
5022 /* In case draw still needs to be claimed */
5023 SendToICS(ics_prefix);
5024 SendToICS("draw\n");
5025 } else if (StrCaseStr(resultDetails, "resign")) {
5026 SendToICS(ics_prefix);
5027 SendToICS("resign\n");
5034 /* If we're loading the game from a file, stop */
5035 if (whosays == GE_FILE) {
5036 (void) StopLoadGameTimer();
5040 /* Cancel draw offers */
5041 first.offeredDraw = second.offeredDraw = 0;
5043 /* If this is an ICS game, only ICS can really say it's done;
5044 if not, anyone can. */
5045 isIcsGame = (gameMode == IcsPlayingWhite ||
5046 gameMode == IcsPlayingBlack ||
5047 gameMode == IcsObserving ||
5048 gameMode == IcsExamining);
5050 if (!isIcsGame || whosays == GE_ICS) {
5051 /* OK -- not an ICS game, or ICS said it was done */
5053 if (!isIcsGame && !appData.noChessProgram)
5054 SetUserThinkingEnables();
5056 if (resultDetails != NULL) {
5057 gameInfo.result = result;
5058 gameInfo.resultDetails = StrSave(resultDetails);
5060 /* Tell program how game ended in case it is learning */
5061 if (gameMode == MachinePlaysWhite ||
5062 gameMode == MachinePlaysBlack ||
5063 gameMode == TwoMachinesPlay ||
5064 gameMode == IcsPlayingWhite ||
5065 gameMode == IcsPlayingBlack ||
5066 gameMode == BeginningOfGame) {
5068 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5070 if (first.pr != NoProc) {
5071 SendToProgram(buf, &first);
5073 if (second.pr != NoProc &&
5074 gameMode == TwoMachinesPlay) {
5075 SendToProgram(buf, &second);
5079 /* display last move only if game was not loaded from file */
5080 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5081 DisplayMove(currentMove - 1);
5083 if (forwardMostMove != 0) {
5084 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5085 if (*appData.saveGameFile != NULLCHAR) {
5086 SaveGameToFile(appData.saveGameFile, TRUE);
5087 } else if (appData.autoSaveGames) {
5090 if (*appData.savePositionFile != NULLCHAR) {
5091 SavePositionToFile(appData.savePositionFile);
5097 if (appData.icsActive) {
5098 if (appData.quietPlay &&
5099 (gameMode == IcsPlayingWhite ||
5100 gameMode == IcsPlayingBlack)) {
5101 SendToICS(ics_prefix);
5102 SendToICS("set shout 1\n");
5104 nextGameMode = IcsIdle;
5105 ics_user_moved = FALSE;
5106 /* clean up premove. It's ugly when the game has ended and the
5107 * premove highlights are still on the board.
5111 ClearPremoveHighlights();
5112 DrawPosition(FALSE, boards[currentMove]);
5114 if (whosays == GE_ICS) {
5117 if (gameMode == IcsPlayingWhite)
5119 else if(gameMode == IcsPlayingBlack)
5123 if (gameMode == IcsPlayingBlack)
5125 else if(gameMode == IcsPlayingWhite)
5132 PlayIcsUnfinishedSound();
5135 } else if (gameMode == EditGame ||
5136 gameMode == PlayFromGameFile ||
5137 gameMode == AnalyzeMode ||
5138 gameMode == AnalyzeFile) {
5139 nextGameMode = gameMode;
5141 nextGameMode = EndOfGame;
5146 nextGameMode = gameMode;
5149 if (appData.noChessProgram) {
5150 gameMode = nextGameMode;
5156 /* Put first chess program into idle state */
5157 if (first.pr != NoProc &&
5158 (gameMode == MachinePlaysWhite ||
5159 gameMode == MachinePlaysBlack ||
5160 gameMode == TwoMachinesPlay ||
5161 gameMode == IcsPlayingWhite ||
5162 gameMode == IcsPlayingBlack ||
5163 gameMode == BeginningOfGame)) {
5164 SendToProgram("force\n", &first);
5165 if (first.usePing) {
5167 sprintf(buf, "ping %d\n", ++first.lastPing);
5168 SendToProgram(buf, &first);
5171 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5172 /* Kill off first chess program */
5173 if (first.isr != NULL)
5174 RemoveInputSource(first.isr);
5177 if (first.pr != NoProc) {
5179 SendToProgram("quit\n", &first);
5180 DestroyChildProcess(first.pr, first.useSigterm);
5185 /* Put second chess program into idle state */
5186 if (second.pr != NoProc &&
5187 gameMode == TwoMachinesPlay) {
5188 SendToProgram("force\n", &second);
5189 if (second.usePing) {
5191 sprintf(buf, "ping %d\n", ++second.lastPing);
5192 SendToProgram(buf, &second);
5195 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5196 /* Kill off second chess program */
5197 if (second.isr != NULL)
5198 RemoveInputSource(second.isr);
5201 if (second.pr != NoProc) {
5202 SendToProgram("quit\n", &second);
5203 DestroyChildProcess(second.pr, second.useSigterm);
5208 if (matchMode && gameMode == TwoMachinesPlay) {
5211 if (first.twoMachinesColor[0] == 'w') {
5218 if (first.twoMachinesColor[0] == 'b') {
5227 if (matchGame < appData.matchGames) {
5229 tmp = first.twoMachinesColor;
5230 first.twoMachinesColor = second.twoMachinesColor;
5231 second.twoMachinesColor = tmp;
5232 gameMode = nextGameMode;
5234 ScheduleDelayedEvent(NextMatchGame, 10000);
5238 gameMode = nextGameMode;
5239 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5240 first.tidy, second.tidy,
5241 first.matchWins, second.matchWins,
5242 appData.matchGames - (first.matchWins + second.matchWins));
5243 DisplayFatalError(buf, 0, 0);
5246 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5247 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5249 gameMode = nextGameMode;
5253 /* Assumes program was just initialized (initString sent).
5254 Leaves program in force mode. */
5256 FeedMovesToProgram(cps, upto)
5257 ChessProgramState *cps;
5262 if (appData.debugMode)
5263 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5264 startedFromSetupPosition ? "position and " : "",
5265 backwardMostMove, upto, cps->which);
5266 SendToProgram("force\n", cps);
5267 if (startedFromSetupPosition) {
5268 SendBoard(cps, backwardMostMove);
5270 for (i = backwardMostMove; i < upto; i++) {
5271 SendMoveToProgram(i, cps);
5277 ResurrectChessProgram()
5279 /* The chess program may have exited.
5280 If so, restart it and feed it all the moves made so far. */
5282 if (appData.noChessProgram || first.pr != NoProc) return;
5284 StartChessProgram(&first);
5285 InitChessProgram(&first);
5286 FeedMovesToProgram(&first, currentMove);
5288 if (!first.sendTime) {
5289 /* can't tell gnuchess what its clock should read,
5290 so we bow to its notion. */
5292 timeRemaining[0][currentMove] = whiteTimeRemaining;
5293 timeRemaining[1][currentMove] = blackTimeRemaining;
5296 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5297 first.analysisSupport) {
5298 SendToProgram("analyze\n", &first);
5299 first.analyzing = TRUE;
5312 if (appData.debugMode) {
5313 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5314 redraw, init, gameMode);
5317 pausing = pauseExamInvalid = FALSE;
5318 startedFromSetupPosition = blackPlaysFirst = FALSE;
5320 whiteFlag = blackFlag = FALSE;
5321 userOfferedDraw = FALSE;
5322 hintRequested = bookRequested = FALSE;
5323 first.maybeThinking = FALSE;
5324 second.maybeThinking = FALSE;
5325 thinkOutput[0] = NULLCHAR;
5326 lastHint[0] = NULLCHAR;
5327 ClearGameInfo(&gameInfo);
5328 gameInfo.variant = StringToVariant(appData.variant);
5329 ics_user_moved = ics_clock_paused = FALSE;
5330 ics_getting_history = H_FALSE;
5332 white_holding[0] = black_holding[0] = NULLCHAR;
5333 ClearProgramStats();
5337 flipView = appData.flipView;
5338 ClearPremoveHighlights();
5340 alarmSounded = FALSE;
5342 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5344 gameMode = BeginningOfGame;
5346 InitPosition(redraw);
5347 for (i = 0; i < MAX_MOVES; i++) {
5348 if (commentList[i] != NULL) {
5349 free(commentList[i]);
5350 commentList[i] = NULL;
5354 timeRemaining[0][0] = whiteTimeRemaining;
5355 timeRemaining[1][0] = blackTimeRemaining;
5356 if (first.pr == NULL) {
5357 StartChessProgram(&first);
5359 if (init) InitChessProgram(&first);
5361 DisplayMessage("", "");
5362 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5369 if (!AutoPlayOneMove())
5371 if (matchMode || appData.timeDelay == 0)
5373 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5375 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5384 int fromX, fromY, toX, toY;
5386 if (appData.debugMode) {
5387 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5390 if (gameMode != PlayFromGameFile)
5393 if (currentMove >= forwardMostMove) {
5394 gameMode = EditGame;
5399 toX = moveList[currentMove][2] - 'a';
5400 toY = moveList[currentMove][3] - '1';
5402 if (moveList[currentMove][1] == '@') {
5403 if (appData.highlightLastMove) {
5404 SetHighlights(-1, -1, toX, toY);
5407 fromX = moveList[currentMove][0] - 'a';
5408 fromY = moveList[currentMove][1] - '1';
5409 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5411 if (appData.highlightLastMove) {
5412 SetHighlights(fromX, fromY, toX, toY);
5415 DisplayMove(currentMove);
5416 SendMoveToProgram(currentMove++, &first);
5417 DisplayBothClocks();
5418 DrawPosition(FALSE, boards[currentMove]);
5419 if (commentList[currentMove] != NULL) {
5420 DisplayComment(currentMove - 1, commentList[currentMove]);
5427 LoadGameOneMove(readAhead)
5428 ChessMove readAhead;
5430 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5431 char promoChar = NULLCHAR;
5436 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5437 gameMode != AnalyzeMode && gameMode != Training) {
5442 yyboardindex = forwardMostMove;
5443 if (readAhead != (ChessMove)0) {
5444 moveType = readAhead;
5446 if (gameFileFP == NULL)
5448 moveType = (ChessMove) yylex();
5454 if (appData.debugMode)
5455 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5457 if (*p == '{' || *p == '[' || *p == '(') {
5458 p[strlen(p) - 1] = NULLCHAR;
5462 /* append the comment but don't display it */
5463 while (*p == '\n') p++;
5464 AppendComment(currentMove, p);
5467 case WhiteCapturesEnPassant:
5468 case BlackCapturesEnPassant:
5469 case WhitePromotionQueen:
5470 case BlackPromotionQueen:
5471 case WhitePromotionRook:
5472 case BlackPromotionRook:
5473 case WhitePromotionBishop:
5474 case BlackPromotionBishop:
5475 case WhitePromotionKnight:
5476 case BlackPromotionKnight:
5477 case WhitePromotionKing:
5478 case BlackPromotionKing:
5480 case WhiteKingSideCastle:
5481 case WhiteQueenSideCastle:
5482 case BlackKingSideCastle:
5483 case BlackQueenSideCastle:
5484 case WhiteKingSideCastleWild:
5485 case WhiteQueenSideCastleWild:
5486 case BlackKingSideCastleWild:
5487 case BlackQueenSideCastleWild:
5488 if (appData.debugMode)
5489 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5490 fromX = currentMoveString[0] - 'a';
5491 fromY = currentMoveString[1] - '1';
5492 toX = currentMoveString[2] - 'a';
5493 toY = currentMoveString[3] - '1';
5494 promoChar = currentMoveString[4];
5499 if (appData.debugMode)
5500 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5501 fromX = moveType == WhiteDrop ?
5502 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5503 (int) CharToPiece(ToLower(currentMoveString[0]));
5505 toX = currentMoveString[2] - 'a';
5506 toY = currentMoveString[3] - '1';
5512 case GameUnfinished:
5513 if (appData.debugMode)
5514 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5515 p = strchr(yy_text, '{');
5516 if (p == NULL) p = strchr(yy_text, '(');
5519 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5521 q = strchr(p, *p == '{' ? '}' : ')');
5522 if (q != NULL) *q = NULLCHAR;
5525 GameEnds(moveType, p, GE_FILE);
5527 if (cmailMsgLoaded) {
5529 flipView = WhiteOnMove(currentMove);
5530 if (moveType == GameUnfinished) flipView = !flipView;
5531 if (appData.debugMode)
5532 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5536 case (ChessMove) 0: /* end of file */
5537 if (appData.debugMode)
5538 fprintf(debugFP, "Parser hit end of file\n");
5539 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5545 if (WhiteOnMove(currentMove)) {
5546 GameEnds(BlackWins, "Black mates", GE_FILE);
5548 GameEnds(WhiteWins, "White mates", GE_FILE);
5552 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5559 if (lastLoadGameStart == GNUChessGame) {
5560 /* GNUChessGames have numbers, but they aren't move numbers */
5561 if (appData.debugMode)
5562 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5563 yy_text, (int) moveType);
5564 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5566 /* else fall thru */
5571 /* Reached start of next game in file */
5572 if (appData.debugMode)
5573 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5574 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5580 if (WhiteOnMove(currentMove)) {
5581 GameEnds(BlackWins, "Black mates", GE_FILE);
5583 GameEnds(WhiteWins, "White mates", GE_FILE);
5587 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5593 case PositionDiagram: /* should not happen; ignore */
5594 case ElapsedTime: /* ignore */
5595 case NAG: /* ignore */
5596 if (appData.debugMode)
5597 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5598 yy_text, (int) moveType);
5599 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5602 if (appData.testLegality) {
5603 if (appData.debugMode)
5604 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5605 sprintf(move, _("Illegal move: %d.%s%s"),
5606 (forwardMostMove / 2) + 1,
5607 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5608 DisplayError(move, 0);
5611 if (appData.debugMode)
5612 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5613 yy_text, currentMoveString);
5614 fromX = currentMoveString[0] - 'a';
5615 fromY = currentMoveString[1] - '1';
5616 toX = currentMoveString[2] - 'a';
5617 toY = currentMoveString[3] - '1';
5618 promoChar = currentMoveString[4];
5623 if (appData.debugMode)
5624 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5625 sprintf(move, _("Ambiguous move: %d.%s%s"),
5626 (forwardMostMove / 2) + 1,
5627 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5628 DisplayError(move, 0);
5633 case ImpossibleMove:
5634 if (appData.debugMode)
5635 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5636 sprintf(move, _("Illegal move: %d.%s%s"),
5637 (forwardMostMove / 2) + 1,
5638 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5639 DisplayError(move, 0);
5645 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5646 DrawPosition(FALSE, boards[currentMove]);
5647 DisplayBothClocks();
5648 if (!appData.matchMode && commentList[currentMove] != NULL)
5649 DisplayComment(currentMove - 1, commentList[currentMove]);
5651 (void) StopLoadGameTimer();
5653 cmailOldMove = forwardMostMove;
5656 /* currentMoveString is set as a side-effect of yylex */
5657 strcat(currentMoveString, "\n");
5658 strcpy(moveList[forwardMostMove], currentMoveString);
5660 thinkOutput[0] = NULLCHAR;
5661 MakeMove(fromX, fromY, toX, toY, promoChar);
5662 currentMove = forwardMostMove;
5667 /* Load the nth game from the given file */
5669 LoadGameFromFile(filename, n, title, useList)
5673 /*Boolean*/ int useList;
5678 if (strcmp(filename, "-") == 0) {
5682 f = fopen(filename, "rb");
5684 sprintf(buf, _("Can't open \"%s\""), filename);
5685 DisplayError(buf, errno);
5689 if (fseek(f, 0, 0) == -1) {
5690 /* f is not seekable; probably a pipe */
5693 if (useList && n == 0) {
5694 int error = GameListBuild(f);
5696 DisplayError(_("Cannot build game list"), error);
5697 } else if (!ListEmpty(&gameList) &&
5698 ((ListGame *) gameList.tailPred)->number > 1) {
5699 GameListPopUp(f, title);
5706 return LoadGame(f, n, title, FALSE);
5711 MakeRegisteredMove()
5713 int fromX, fromY, toX, toY;
5715 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5716 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5719 if (appData.debugMode)
5720 fprintf(debugFP, "Restoring %s for game %d\n",
5721 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5723 thinkOutput[0] = NULLCHAR;
5724 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5725 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5726 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5727 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5728 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5729 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5730 MakeMove(fromX, fromY, toX, toY, promoChar);
5731 ShowMove(fromX, fromY, toX, toY);
5733 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5740 if (WhiteOnMove(currentMove)) {
5741 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5743 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5748 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5755 if (WhiteOnMove(currentMove)) {
5756 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5758 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5763 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5774 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5776 CmailLoadGame(f, gameNumber, title, useList)
5784 if (gameNumber > nCmailGames) {
5785 DisplayError(_("No more games in this message"), 0);
5788 if (f == lastLoadGameFP) {
5789 int offset = gameNumber - lastLoadGameNumber;
5791 cmailMsg[0] = NULLCHAR;
5792 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5793 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5794 nCmailMovesRegistered--;
5796 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5797 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5798 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5801 if (! RegisterMove()) return FALSE;
5805 retVal = LoadGame(f, gameNumber, title, useList);
5807 /* Make move registered during previous look at this game, if any */
5808 MakeRegisteredMove();
5810 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5811 commentList[currentMove]
5812 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5813 DisplayComment(currentMove - 1, commentList[currentMove]);
5819 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5824 int gameNumber = lastLoadGameNumber + offset;
5825 if (lastLoadGameFP == NULL) {
5826 DisplayError(_("No game has been loaded yet"), 0);
5829 if (gameNumber <= 0) {
5830 DisplayError(_("Can't back up any further"), 0);
5833 if (cmailMsgLoaded) {
5834 return CmailLoadGame(lastLoadGameFP, gameNumber,
5835 lastLoadGameTitle, lastLoadGameUseList);
5837 return LoadGame(lastLoadGameFP, gameNumber,
5838 lastLoadGameTitle, lastLoadGameUseList);
5844 /* Load the nth game from open file f */
5846 LoadGame(f, gameNumber, title, useList)
5854 int gn = gameNumber;
5855 ListGame *lg = NULL;
5858 GameMode oldGameMode;
5860 if (appData.debugMode)
5861 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5863 if (gameMode == Training )
5864 SetTrainingModeOff();
5866 oldGameMode = gameMode;
5867 if (gameMode != BeginningOfGame) {
5872 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5873 fclose(lastLoadGameFP);
5877 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5880 fseek(f, lg->offset, 0);
5881 GameListHighlight(gameNumber);
5885 DisplayError(_("Game number out of range"), 0);
5890 if (fseek(f, 0, 0) == -1) {
5891 if (f == lastLoadGameFP ?
5892 gameNumber == lastLoadGameNumber + 1 :
5896 DisplayError(_("Can't seek on game file"), 0);
5902 lastLoadGameNumber = gameNumber;
5903 strcpy(lastLoadGameTitle, title);
5904 lastLoadGameUseList = useList;
5909 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5910 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5911 lg->gameInfo.black);
5913 } else if (*title != NULLCHAR) {
5914 if (gameNumber > 1) {
5915 sprintf(buf, "%s %d", title, gameNumber);
5918 DisplayTitle(title);
5922 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5923 gameMode = PlayFromGameFile;
5927 currentMove = forwardMostMove = backwardMostMove = 0;
5928 CopyBoard(boards[0], initialPosition);
5932 * Skip the first gn-1 games in the file.
5933 * Also skip over anything that precedes an identifiable
5934 * start of game marker, to avoid being confused by
5935 * garbage at the start of the file. Currently
5936 * recognized start of game markers are the move number "1",
5937 * the pattern "gnuchess .* game", the pattern
5938 * "^[#;%] [^ ]* game file", and a PGN tag block.
5939 * A game that starts with one of the latter two patterns
5940 * will also have a move number 1, possibly
5941 * following a position diagram.
5942 * 5-4-02: Let's try being more lenient and allowing a game to
5943 * start with an unnumbered move. Does that break anything?
5945 cm = lastLoadGameStart = (ChessMove) 0;
5947 yyboardindex = forwardMostMove;
5948 cm = (ChessMove) yylex();
5951 if (cmailMsgLoaded) {
5952 nCmailGames = CMAIL_MAX_GAMES - gn;
5955 DisplayError(_("Game not found in file"), 0);
5962 lastLoadGameStart = cm;
5966 switch (lastLoadGameStart) {
5973 gn--; /* count this game */
5974 lastLoadGameStart = cm;
5983 switch (lastLoadGameStart) {
5988 gn--; /* count this game */
5989 lastLoadGameStart = cm;
5992 lastLoadGameStart = cm; /* game counted already */
6000 yyboardindex = forwardMostMove;
6001 cm = (ChessMove) yylex();
6002 } while (cm == PGNTag || cm == Comment);
6009 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6010 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6011 != CMAIL_OLD_RESULT) {
6013 cmailResult[ CMAIL_MAX_GAMES
6014 - gn - 1] = CMAIL_OLD_RESULT;
6020 /* Only a NormalMove can be at the start of a game
6021 * without a position diagram. */
6022 if (lastLoadGameStart == (ChessMove) 0) {
6024 lastLoadGameStart = MoveNumberOne;
6033 if (appData.debugMode)
6034 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6036 if (cm == XBoardGame) {
6037 /* Skip any header junk before position diagram and/or move 1 */
6039 yyboardindex = forwardMostMove;
6040 cm = (ChessMove) yylex();
6042 if (cm == (ChessMove) 0 ||
6043 cm == GNUChessGame || cm == XBoardGame) {
6044 /* Empty game; pretend end-of-file and handle later */
6049 if (cm == MoveNumberOne || cm == PositionDiagram ||
6050 cm == PGNTag || cm == Comment)
6053 } else if (cm == GNUChessGame) {
6054 if (gameInfo.event != NULL) {
6055 free(gameInfo.event);
6057 gameInfo.event = StrSave(yy_text);
6060 startedFromSetupPosition = FALSE;
6061 while (cm == PGNTag) {
6062 if (appData.debugMode)
6063 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6064 err = ParsePGNTag(yy_text, &gameInfo);
6065 if (!err) numPGNTags++;
6067 if (gameInfo.fen != NULL) {
6068 Board initial_position;
6069 startedFromSetupPosition = TRUE;
6070 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6072 DisplayError(_("Bad FEN position in file"), 0);
6075 CopyBoard(boards[0], initial_position);
6076 if (blackPlaysFirst) {
6077 currentMove = forwardMostMove = backwardMostMove = 1;
6078 CopyBoard(boards[1], initial_position);
6079 strcpy(moveList[0], "");
6080 strcpy(parseList[0], "");
6081 timeRemaining[0][1] = whiteTimeRemaining;
6082 timeRemaining[1][1] = blackTimeRemaining;
6083 if (commentList[0] != NULL) {
6084 commentList[1] = commentList[0];
6085 commentList[0] = NULL;
6088 currentMove = forwardMostMove = backwardMostMove = 0;
6090 yyboardindex = forwardMostMove;
6092 gameInfo.fen = NULL;
6095 yyboardindex = forwardMostMove;
6096 cm = (ChessMove) yylex();
6098 /* Handle comments interspersed among the tags */
6099 while (cm == Comment) {
6101 if (appData.debugMode)
6102 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6104 if (*p == '{' || *p == '[' || *p == '(') {
6105 p[strlen(p) - 1] = NULLCHAR;
6108 while (*p == '\n') p++;
6109 AppendComment(currentMove, p);
6110 yyboardindex = forwardMostMove;
6111 cm = (ChessMove) yylex();
6115 /* don't rely on existence of Event tag since if game was
6116 * pasted from clipboard the Event tag may not exist
6118 if (numPGNTags > 0){
6120 if (gameInfo.variant == VariantNormal) {
6121 gameInfo.variant = StringToVariant(gameInfo.event);
6124 tags = PGNTags(&gameInfo);
6125 TagsPopUp(tags, CmailMsg());
6129 /* Make something up, but don't display it now */
6134 if (cm == PositionDiagram) {
6137 Board initial_position;
6139 if (appData.debugMode)
6140 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6142 if (!startedFromSetupPosition) {
6144 for (i = BOARD_SIZE - 1; i >= 0; i--)
6145 for (j = 0; j < BOARD_SIZE; p++)
6155 initial_position[i][j++] = CharToPiece(*p);
6158 while (*p == ' ' || *p == '\t' ||
6159 *p == '\n' || *p == '\r') p++;
6161 if (strncmp(p, "black", strlen("black"))==0)
6162 blackPlaysFirst = TRUE;
6164 blackPlaysFirst = FALSE;
6165 startedFromSetupPosition = TRUE;
6167 CopyBoard(boards[0], initial_position);
6168 if (blackPlaysFirst) {
6169 currentMove = forwardMostMove = backwardMostMove = 1;
6170 CopyBoard(boards[1], initial_position);
6171 strcpy(moveList[0], "");
6172 strcpy(parseList[0], "");
6173 timeRemaining[0][1] = whiteTimeRemaining;
6174 timeRemaining[1][1] = blackTimeRemaining;
6175 if (commentList[0] != NULL) {
6176 commentList[1] = commentList[0];
6177 commentList[0] = NULL;
6180 currentMove = forwardMostMove = backwardMostMove = 0;
6183 yyboardindex = forwardMostMove;
6184 cm = (ChessMove) yylex();
6187 if (first.pr == NoProc) {
6188 StartChessProgram(&first);
6190 InitChessProgram(&first);
6191 SendToProgram("force\n", &first);
6192 if (startedFromSetupPosition) {
6193 SendBoard(&first, forwardMostMove);
6194 DisplayBothClocks();
6197 while (cm == Comment) {
6199 if (appData.debugMode)
6200 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6202 if (*p == '{' || *p == '[' || *p == '(') {
6203 p[strlen(p) - 1] = NULLCHAR;
6206 while (*p == '\n') p++;
6207 AppendComment(currentMove, p);
6208 yyboardindex = forwardMostMove;
6209 cm = (ChessMove) yylex();
6212 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6213 cm == WhiteWins || cm == BlackWins ||
6214 cm == GameIsDrawn || cm == GameUnfinished) {
6215 DisplayMessage("", _("No moves in game"));
6216 if (cmailMsgLoaded) {
6217 if (appData.debugMode)
6218 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6222 DrawPosition(FALSE, boards[currentMove]);
6223 DisplayBothClocks();
6224 gameMode = EditGame;
6231 if (commentList[currentMove] != NULL) {
6232 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6233 DisplayComment(currentMove - 1, commentList[currentMove]);
6236 if (!matchMode && appData.timeDelay != 0)
6237 DrawPosition(FALSE, boards[currentMove]);
6239 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6240 programStats.ok_to_send = 1;
6243 /* if the first token after the PGN tags is a move
6244 * and not move number 1, retrieve it from the parser
6246 if (cm != MoveNumberOne)
6247 LoadGameOneMove(cm);
6249 /* load the remaining moves from the file */
6250 while (LoadGameOneMove((ChessMove)0)) {
6251 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6252 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6255 /* rewind to the start of the game */
6256 currentMove = backwardMostMove;
6258 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6260 if (oldGameMode == AnalyzeFile ||
6261 oldGameMode == AnalyzeMode) {
6265 if (matchMode || appData.timeDelay == 0) {
6267 gameMode = EditGame;
6269 } else if (appData.timeDelay > 0) {
6273 if (appData.debugMode)
6274 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6278 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6280 ReloadPosition(offset)
6283 int positionNumber = lastLoadPositionNumber + offset;
6284 if (lastLoadPositionFP == NULL) {
6285 DisplayError(_("No position has been loaded yet"), 0);
6288 if (positionNumber <= 0) {
6289 DisplayError(_("Can't back up any further"), 0);
6292 return LoadPosition(lastLoadPositionFP, positionNumber,
6293 lastLoadPositionTitle);
6296 /* Load the nth position from the given file */
6298 LoadPositionFromFile(filename, n, title)
6306 if (strcmp(filename, "-") == 0) {
6307 return LoadPosition(stdin, n, "stdin");
6309 f = fopen(filename, "rb");
6311 sprintf(buf, _("Can't open \"%s\""), filename);
6312 DisplayError(buf, errno);
6315 return LoadPosition(f, n, title);
6320 /* Load the nth position from the given open file, and close it */
6322 LoadPosition(f, positionNumber, title)
6327 char *p, line[MSG_SIZ];
6328 Board initial_position;
6329 int i, j, fenMode, pn;
6331 if (gameMode == Training )
6332 SetTrainingModeOff();
6334 if (gameMode != BeginningOfGame) {
6337 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6338 fclose(lastLoadPositionFP);
6340 if (positionNumber == 0) positionNumber = 1;
6341 lastLoadPositionFP = f;
6342 lastLoadPositionNumber = positionNumber;
6343 strcpy(lastLoadPositionTitle, title);
6344 if (first.pr == NoProc) {
6345 StartChessProgram(&first);
6346 InitChessProgram(&first);
6348 pn = positionNumber;
6349 if (positionNumber < 0) {
6350 /* Negative position number means to seek to that byte offset */
6351 if (fseek(f, -positionNumber, 0) == -1) {
6352 DisplayError(_("Can't seek on position file"), 0);
6357 if (fseek(f, 0, 0) == -1) {
6358 if (f == lastLoadPositionFP ?
6359 positionNumber == lastLoadPositionNumber + 1 :
6360 positionNumber == 1) {
6363 DisplayError(_("Can't seek on position file"), 0);
6368 /* See if this file is FEN or old-style xboard */
6369 if (fgets(line, MSG_SIZ, f) == NULL) {
6370 DisplayError(_("Position not found in file"), 0);
6378 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6379 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6380 case '1': case '2': case '3': case '4': case '5': case '6':
6387 if (fenMode || line[0] == '#') pn--;
6389 /* skip postions before number pn */
6390 if (fgets(line, MSG_SIZ, f) == NULL) {
6392 DisplayError(_("Position not found in file"), 0);
6395 if (fenMode || line[0] == '#') pn--;
6400 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6401 DisplayError(_("Bad FEN position in file"), 0);
6405 (void) fgets(line, MSG_SIZ, f);
6406 (void) fgets(line, MSG_SIZ, f);
6408 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6409 (void) fgets(line, MSG_SIZ, f);
6410 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6413 initial_position[i][j++] = CharToPiece(*p);
6417 blackPlaysFirst = FALSE;
6419 (void) fgets(line, MSG_SIZ, f);
6420 if (strncmp(line, "black", strlen("black"))==0)
6421 blackPlaysFirst = TRUE;
6424 startedFromSetupPosition = TRUE;
6426 SendToProgram("force\n", &first);
6427 CopyBoard(boards[0], initial_position);
6428 if (blackPlaysFirst) {
6429 currentMove = forwardMostMove = backwardMostMove = 1;
6430 strcpy(moveList[0], "");
6431 strcpy(parseList[0], "");
6432 CopyBoard(boards[1], initial_position);
6433 DisplayMessage("", _("Black to play"));
6435 currentMove = forwardMostMove = backwardMostMove = 0;
6436 DisplayMessage("", _("White to play"));
6438 SendBoard(&first, forwardMostMove);
6440 if (positionNumber > 1) {
6441 sprintf(line, "%s %d", title, positionNumber);
6444 DisplayTitle(title);
6446 gameMode = EditGame;
6449 timeRemaining[0][1] = whiteTimeRemaining;
6450 timeRemaining[1][1] = blackTimeRemaining;
6451 DrawPosition(FALSE, boards[currentMove]);
6458 CopyPlayerNameIntoFileName(dest, src)
6461 while (*src != NULLCHAR && *src != ',') {
6466 *(*dest)++ = *src++;
6471 char *DefaultFileName(ext)
6474 static char def[MSG_SIZ];
6477 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6479 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6481 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6490 /* Save the current game to the given file */
6492 SaveGameToFile(filename, append)
6499 if (strcmp(filename, "-") == 0) {
6500 return SaveGame(stdout, 0, NULL);
6502 f = fopen(filename, append ? "a" : "w");
6504 sprintf(buf, _("Can't open \"%s\""), filename);
6505 DisplayError(buf, errno);
6508 return SaveGame(f, 0, NULL);
6517 static char buf[MSG_SIZ];
6520 p = strchr(str, ' ');
6521 if (p == NULL) return str;
6522 strncpy(buf, str, p - str);
6523 buf[p - str] = NULLCHAR;
6527 #define PGN_MAX_LINE 75
6529 /* Save game in PGN style and close the file */
6534 int i, offset, linelen, newblock;
6538 int movelen, numlen, blank;
6540 tm = time((time_t *) NULL);
6542 PrintPGNTags(f, &gameInfo);
6544 if (backwardMostMove > 0 || startedFromSetupPosition) {
6545 char *fen = PositionToFEN(backwardMostMove);
6546 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6547 fprintf(f, "\n{--------------\n");
6548 PrintPosition(f, backwardMostMove);
6549 fprintf(f, "--------------}\n");
6555 i = backwardMostMove;
6556 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6560 while (i < forwardMostMove) {
6561 /* Print comments preceding this move */
6562 if (commentList[i] != NULL) {
6563 if (linelen > 0) fprintf(f, "\n");
6564 fprintf(f, "{\n%s}\n", commentList[i]);
6569 /* Format move number */
6571 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6574 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6576 numtext[0] = NULLCHAR;
6579 numlen = strlen(numtext);
6582 /* Print move number */
6583 blank = linelen > 0 && numlen > 0;
6584 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6593 fprintf(f, numtext);
6597 movetext = SavePart(parseList[i]);
6598 movelen = strlen(movetext);
6601 blank = linelen > 0 && movelen > 0;
6602 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6611 fprintf(f, movetext);
6617 /* Start a new line */
6618 if (linelen > 0) fprintf(f, "\n");
6620 /* Print comments after last move */
6621 if (commentList[i] != NULL) {
6622 fprintf(f, "{\n%s}\n", commentList[i]);
6626 if (gameInfo.resultDetails != NULL &&
6627 gameInfo.resultDetails[0] != NULLCHAR) {
6628 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6629 PGNResult(gameInfo.result));
6631 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6638 /* Save game in old style and close the file */
6646 tm = time((time_t *) NULL);
6648 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6651 if (backwardMostMove > 0 || startedFromSetupPosition) {
6652 fprintf(f, "\n[--------------\n");
6653 PrintPosition(f, backwardMostMove);
6654 fprintf(f, "--------------]\n");
6659 i = backwardMostMove;
6660 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6662 while (i < forwardMostMove) {
6663 if (commentList[i] != NULL) {
6664 fprintf(f, "[%s]\n", commentList[i]);
6668 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6671 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6673 if (commentList[i] != NULL) {
6677 if (i >= forwardMostMove) {
6681 fprintf(f, "%s\n", parseList[i]);
6686 if (commentList[i] != NULL) {
6687 fprintf(f, "[%s]\n", commentList[i]);
6690 /* This isn't really the old style, but it's close enough */
6691 if (gameInfo.resultDetails != NULL &&
6692 gameInfo.resultDetails[0] != NULLCHAR) {
6693 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6694 gameInfo.resultDetails);
6696 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6703 /* Save the current game to open file f and close the file */
6705 SaveGame(f, dummy, dummy2)
6710 if (gameMode == EditPosition) EditPositionDone();
6711 if (appData.oldSaveStyle)
6712 return SaveGameOldStyle(f);
6714 return SaveGamePGN(f);
6717 /* Save the current position to the given file */
6719 SavePositionToFile(filename)
6725 if (strcmp(filename, "-") == 0) {
6726 return SavePosition(stdout, 0, NULL);
6728 f = fopen(filename, "a");
6730 sprintf(buf, _("Can't open \"%s\""), filename);
6731 DisplayError(buf, errno);
6734 SavePosition(f, 0, NULL);
6740 /* Save the current position to the given open file and close the file */
6742 SavePosition(f, dummy, dummy2)
6750 if (appData.oldSaveStyle) {
6751 tm = time((time_t *) NULL);
6753 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6755 fprintf(f, "[--------------\n");
6756 PrintPosition(f, currentMove);
6757 fprintf(f, "--------------]\n");
6759 fen = PositionToFEN(currentMove);
6760 fprintf(f, "%s\n", fen);
6768 ReloadCmailMsgEvent(unregister)
6772 static char *inFilename = NULL;
6773 static char *outFilename;
6775 struct stat inbuf, outbuf;
6778 /* Any registered moves are unregistered if unregister is set, */
6779 /* i.e. invoked by the signal handler */
6781 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6782 cmailMoveRegistered[i] = FALSE;
6783 if (cmailCommentList[i] != NULL) {
6784 free(cmailCommentList[i]);
6785 cmailCommentList[i] = NULL;
6788 nCmailMovesRegistered = 0;
6791 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6792 cmailResult[i] = CMAIL_NOT_RESULT;
6796 if (inFilename == NULL) {
6797 /* Because the filenames are static they only get malloced once */
6798 /* and they never get freed */
6799 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6800 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6802 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6803 sprintf(outFilename, "%s.out", appData.cmailGameName);
6806 status = stat(outFilename, &outbuf);
6808 cmailMailedMove = FALSE;
6810 status = stat(inFilename, &inbuf);
6811 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6814 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6815 counts the games, notes how each one terminated, etc.
6817 It would be nice to remove this kludge and instead gather all
6818 the information while building the game list. (And to keep it
6819 in the game list nodes instead of having a bunch of fixed-size
6820 parallel arrays.) Note this will require getting each game's
6821 termination from the PGN tags, as the game list builder does
6822 not process the game moves. --mann
6824 cmailMsgLoaded = TRUE;
6825 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6827 /* Load first game in the file or popup game menu */
6828 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6838 char string[MSG_SIZ];
6840 if ( cmailMailedMove
6841 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6842 return TRUE; /* Allow free viewing */
6845 /* Unregister move to ensure that we don't leave RegisterMove */
6846 /* with the move registered when the conditions for registering no */
6848 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6849 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6850 nCmailMovesRegistered --;
6852 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6854 free(cmailCommentList[lastLoadGameNumber - 1]);
6855 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6859 if (cmailOldMove == -1) {
6860 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6864 if (currentMove > cmailOldMove + 1) {
6865 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6869 if (currentMove < cmailOldMove) {
6870 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6874 if (forwardMostMove > currentMove) {
6875 /* Silently truncate extra moves */
6879 if ( (currentMove == cmailOldMove + 1)
6880 || ( (currentMove == cmailOldMove)
6881 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6882 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6883 if (gameInfo.result != GameUnfinished) {
6884 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6887 if (commentList[currentMove] != NULL) {
6888 cmailCommentList[lastLoadGameNumber - 1]
6889 = StrSave(commentList[currentMove]);
6891 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6893 if (appData.debugMode)
6894 fprintf(debugFP, "Saving %s for game %d\n",
6895 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6898 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6900 f = fopen(string, "w");
6901 if (appData.oldSaveStyle) {
6902 SaveGameOldStyle(f); /* also closes the file */
6904 sprintf(string, "%s.pos.out", appData.cmailGameName);
6905 f = fopen(string, "w");
6906 SavePosition(f, 0, NULL); /* also closes the file */
6908 fprintf(f, "{--------------\n");
6909 PrintPosition(f, currentMove);
6910 fprintf(f, "--------------}\n\n");
6912 SaveGame(f, 0, NULL); /* also closes the file*/
6915 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6916 nCmailMovesRegistered ++;
6917 } else if (nCmailGames == 1) {
6918 DisplayError(_("You have not made a move yet"), 0);
6929 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6930 FILE *commandOutput;
6931 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6932 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6938 if (! cmailMsgLoaded) {
6939 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
6943 if (nCmailGames == nCmailResults) {
6944 DisplayError(_("No unfinished games"), 0);
6948 #if CMAIL_PROHIBIT_REMAIL
6949 if (cmailMailedMove) {
6950 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);
6951 DisplayError(msg, 0);
6956 if (! (cmailMailedMove || RegisterMove())) return;
6958 if ( cmailMailedMove
6959 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
6960 sprintf(string, partCommandString,
6961 appData.debugMode ? " -v" : "", appData.cmailGameName);
6962 commandOutput = popen(string, "rb");
6964 if (commandOutput == NULL) {
6965 DisplayError(_("Failed to invoke cmail"), 0);
6967 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
6968 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
6971 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
6972 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
6973 nBytes = MSG_SIZ - 1;
6975 (void) memcpy(msg, buffer, nBytes);
6977 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
6979 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
6980 cmailMailedMove = TRUE; /* Prevent >1 moves */
6983 for (i = 0; i < nCmailGames; i ++) {
6984 if (cmailResult[i] == CMAIL_NOT_RESULT) {
6989 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
6991 sprintf(buffer, "%s/%s.%s.archive",
6993 appData.cmailGameName,
6995 LoadGameFromFile(buffer, 1, buffer, FALSE);
6996 cmailMsgLoaded = FALSE;
7000 DisplayInformation(msg);
7001 pclose(commandOutput);
7004 if ((*cmailMsg) != '\0') {
7005 DisplayInformation(cmailMsg);
7019 int prependComma = 0;
7021 char string[MSG_SIZ]; /* Space for game-list */
7024 if (!cmailMsgLoaded) return "";
7026 if (cmailMailedMove) {
7027 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7029 /* Create a list of games left */
7030 sprintf(string, "[");
7031 for (i = 0; i < nCmailGames; i ++) {
7032 if (! ( cmailMoveRegistered[i]
7033 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7035 sprintf(number, ",%d", i + 1);
7037 sprintf(number, "%d", i + 1);
7041 strcat(string, number);
7044 strcat(string, "]");
7046 if (nCmailMovesRegistered + nCmailResults == 0) {
7047 switch (nCmailGames) {
7050 _("Still need to make move for game\n"));
7055 _("Still need to make moves for both games\n"));
7060 _("Still need to make moves for all %d games\n"),
7065 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7068 _("Still need to make a move for game %s\n"),
7073 if (nCmailResults == nCmailGames) {
7074 sprintf(cmailMsg, _("No unfinished games\n"));
7076 sprintf(cmailMsg, _("Ready to send mail\n"));
7082 _("Still need to make moves for games %s\n"),
7094 if (gameMode == Training)
7095 SetTrainingModeOff();
7098 cmailMsgLoaded = FALSE;
7099 if (appData.icsActive) {
7100 SendToICS(ics_prefix);
7101 SendToICS("refresh\n");
7105 static int exiting = 0;
7113 /* Give up on clean exit */
7117 /* Keep trying for clean exit */
7121 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7123 if (telnetISR != NULL) {
7124 RemoveInputSource(telnetISR);
7126 if (icsPR != NoProc) {
7127 DestroyChildProcess(icsPR, TRUE);
7129 /* Save game if resource set and not already saved by GameEnds() */
7130 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7131 if (*appData.saveGameFile != NULLCHAR) {
7132 SaveGameToFile(appData.saveGameFile, TRUE);
7133 } else if (appData.autoSaveGames) {
7136 if (*appData.savePositionFile != NULLCHAR) {
7137 SavePositionToFile(appData.savePositionFile);
7140 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7142 /* Kill off chess programs */
7143 if (first.pr != NoProc) {
7145 SendToProgram("quit\n", &first);
7146 DestroyChildProcess(first.pr, first.useSigterm);
7148 if (second.pr != NoProc) {
7149 SendToProgram("quit\n", &second);
7150 DestroyChildProcess(second.pr, second.useSigterm);
7152 if (first.isr != NULL) {
7153 RemoveInputSource(first.isr);
7155 if (second.isr != NULL) {
7156 RemoveInputSource(second.isr);
7166 if (appData.debugMode)
7167 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7171 if (gameMode == MachinePlaysWhite ||
7172 gameMode == MachinePlaysBlack) {
7175 DisplayBothClocks();
7177 if (gameMode == PlayFromGameFile) {
7178 if (appData.timeDelay >= 0)
7180 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7182 SendToICS(ics_prefix);
7183 SendToICS("refresh\n");
7184 } else if (currentMove < forwardMostMove) {
7185 ForwardInner(forwardMostMove);
7187 pauseExamInvalid = FALSE;
7193 pauseExamForwardMostMove = forwardMostMove;
7194 pauseExamInvalid = FALSE;
7197 case IcsPlayingWhite:
7198 case IcsPlayingBlack:
7202 case PlayFromGameFile:
7203 (void) StopLoadGameTimer();
7207 case BeginningOfGame:
7208 if (appData.icsActive) return;
7209 /* else fall through */
7210 case MachinePlaysWhite:
7211 case MachinePlaysBlack:
7212 case TwoMachinesPlay:
7213 if (forwardMostMove == 0)
7214 return; /* don't pause if no one has moved */
7215 if ((gameMode == MachinePlaysWhite &&
7216 !WhiteOnMove(forwardMostMove)) ||
7217 (gameMode == MachinePlaysBlack &&
7218 WhiteOnMove(forwardMostMove))) {
7231 char title[MSG_SIZ];
7233 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7234 strcpy(title, _("Edit comment"));
7236 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7237 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7238 parseList[currentMove - 1]);
7241 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7248 char *tags = PGNTags(&gameInfo);
7249 EditTagsPopUp(tags);
7256 if (appData.noChessProgram || gameMode == AnalyzeMode)
7259 if (gameMode != AnalyzeFile) {
7261 if (gameMode != EditGame) return;
7262 ResurrectChessProgram();
7263 SendToProgram("analyze\n", &first);
7264 first.analyzing = TRUE;
7265 /*first.maybeThinking = TRUE;*/
7266 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7267 AnalysisPopUp(_("Analysis"),
7268 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7270 gameMode = AnalyzeMode;
7275 StartAnalysisClock();
7276 GetTimeMark(&lastNodeCountTime);
7283 if (appData.noChessProgram || gameMode == AnalyzeFile)
7286 if (gameMode != AnalyzeMode) {
7288 if (gameMode != EditGame) return;
7289 ResurrectChessProgram();
7290 SendToProgram("analyze\n", &first);
7291 first.analyzing = TRUE;
7292 /*first.maybeThinking = TRUE;*/
7293 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7294 AnalysisPopUp(_("Analysis"),
7295 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7297 gameMode = AnalyzeFile;
7302 StartAnalysisClock();
7303 GetTimeMark(&lastNodeCountTime);
7312 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7316 if (gameMode == PlayFromGameFile ||
7317 gameMode == TwoMachinesPlay ||
7318 gameMode == Training ||
7319 gameMode == AnalyzeMode ||
7320 gameMode == EndOfGame)
7323 if (gameMode == EditPosition)
7326 if (!WhiteOnMove(currentMove)) {
7327 DisplayError(_("It is not White's turn"), 0);
7331 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7334 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7335 gameMode == AnalyzeFile)
7338 ResurrectChessProgram(); /* in case it isn't running */
7339 gameMode = MachinePlaysWhite;
7343 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7345 if (first.sendName) {
7346 sprintf(buf, "name %s\n", gameInfo.black);
7347 SendToProgram(buf, &first);
7349 if (first.sendTime) {
7350 if (first.useColors) {
7351 SendToProgram("black\n", &first); /*gnu kludge*/
7353 SendTimeRemaining(&first, TRUE);
7355 if (first.useColors) {
7356 SendToProgram("white\ngo\n", &first);
7358 SendToProgram("go\n", &first);
7360 SetMachineThinkingEnables();
7361 first.maybeThinking = TRUE;
7364 if (appData.autoFlipView && !flipView) {
7365 flipView = !flipView;
7366 DrawPosition(FALSE, NULL);
7375 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7379 if (gameMode == PlayFromGameFile ||
7380 gameMode == TwoMachinesPlay ||
7381 gameMode == Training ||
7382 gameMode == AnalyzeMode ||
7383 gameMode == EndOfGame)
7386 if (gameMode == EditPosition)
7389 if (WhiteOnMove(currentMove)) {
7390 DisplayError(_("It is not Black's turn"), 0);
7394 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7397 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7398 gameMode == AnalyzeFile)
7401 ResurrectChessProgram(); /* in case it isn't running */
7402 gameMode = MachinePlaysBlack;
7406 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7408 if (first.sendName) {
7409 sprintf(buf, "name %s\n", gameInfo.white);
7410 SendToProgram(buf, &first);
7412 if (first.sendTime) {
7413 if (first.useColors) {
7414 SendToProgram("white\n", &first); /*gnu kludge*/
7416 SendTimeRemaining(&first, FALSE);
7418 if (first.useColors) {
7419 SendToProgram("black\ngo\n", &first);
7421 SendToProgram("go\n", &first);
7423 SetMachineThinkingEnables();
7424 first.maybeThinking = TRUE;
7427 if (appData.autoFlipView && flipView) {
7428 flipView = !flipView;
7429 DrawPosition(FALSE, NULL);
7435 DisplayTwoMachinesTitle()
7438 if (appData.matchGames > 0) {
7439 if (first.twoMachinesColor[0] == 'w') {
7440 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7441 gameInfo.white, gameInfo.black,
7442 first.matchWins, second.matchWins,
7443 matchGame - 1 - (first.matchWins + second.matchWins));
7445 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7446 gameInfo.white, gameInfo.black,
7447 second.matchWins, first.matchWins,
7448 matchGame - 1 - (first.matchWins + second.matchWins));
7451 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7457 TwoMachinesEvent P((void))
7461 ChessProgramState *onmove;
7463 if (appData.noChessProgram) return;
7466 case TwoMachinesPlay:
7468 case MachinePlaysWhite:
7469 case MachinePlaysBlack:
7470 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7471 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7475 case BeginningOfGame:
7476 case PlayFromGameFile:
7479 if (gameMode != EditGame) return;
7493 forwardMostMove = currentMove;
7494 ResurrectChessProgram(); /* in case first program isn't running */
7496 if (second.pr == NULL) {
7497 StartChessProgram(&second);
7498 if (second.protocolVersion == 1) {
7499 TwoMachinesEventIfReady();
7501 /* kludge: allow timeout for initial "feature" command */
7503 DisplayMessage("", _("Starting second chess program"));
7504 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7508 DisplayMessage("", "");
7509 InitChessProgram(&second);
7510 SendToProgram("force\n", &second);
7511 if (startedFromSetupPosition) {
7512 SendBoard(&second, backwardMostMove);
7514 for (i = backwardMostMove; i < forwardMostMove; i++) {
7515 SendMoveToProgram(i, &second);
7518 gameMode = TwoMachinesPlay;
7522 DisplayTwoMachinesTitle();
7524 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7530 SendToProgram(first.computerString, &first);
7531 if (first.sendName) {
7532 sprintf(buf, "name %s\n", second.tidy);
7533 SendToProgram(buf, &first);
7535 SendToProgram(second.computerString, &second);
7536 if (second.sendName) {
7537 sprintf(buf, "name %s\n", first.tidy);
7538 SendToProgram(buf, &second);
7541 if (!first.sendTime || !second.sendTime) {
7543 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7544 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7546 if (onmove->sendTime) {
7547 if (onmove->useColors) {
7548 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7550 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7552 if (onmove->useColors) {
7553 SendToProgram(onmove->twoMachinesColor, onmove);
7555 SendToProgram("go\n", onmove);
7556 onmove->maybeThinking = TRUE;
7557 SetMachineThinkingEnables();
7565 if (gameMode == Training) {
7566 SetTrainingModeOff();
7567 gameMode = PlayFromGameFile;
7568 DisplayMessage("", _("Training mode off"));
7570 gameMode = Training;
7571 animateTraining = appData.animate;
7573 /* make sure we are not already at the end of the game */
7574 if (currentMove < forwardMostMove) {
7575 SetTrainingModeOn();
7576 DisplayMessage("", _("Training mode on"));
7578 gameMode = PlayFromGameFile;
7579 DisplayError(_("Already at end of game"), 0);
7588 if (!appData.icsActive) return;
7590 case IcsPlayingWhite:
7591 case IcsPlayingBlack:
7594 case BeginningOfGame:
7628 SetTrainingModeOff();
7630 case MachinePlaysWhite:
7631 case MachinePlaysBlack:
7632 case BeginningOfGame:
7633 SendToProgram("force\n", &first);
7634 SetUserThinkingEnables();
7636 case PlayFromGameFile:
7637 (void) StopLoadGameTimer();
7638 if (gameFileFP != NULL) {
7648 SendToProgram("force\n", &first);
7650 case TwoMachinesPlay:
7651 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7652 ResurrectChessProgram();
7653 SetUserThinkingEnables();
7656 ResurrectChessProgram();
7658 case IcsPlayingBlack:
7659 case IcsPlayingWhite:
7660 DisplayError(_("Warning: You are still playing a game"), 0);
7663 DisplayError(_("Warning: You are still observing a game"), 0);
7666 DisplayError(_("Warning: You are still examining a game"), 0);
7677 first.offeredDraw = second.offeredDraw = 0;
7679 if (gameMode == PlayFromGameFile) {
7680 whiteTimeRemaining = timeRemaining[0][currentMove];
7681 blackTimeRemaining = timeRemaining[1][currentMove];
7685 if (gameMode == MachinePlaysWhite ||
7686 gameMode == MachinePlaysBlack ||
7687 gameMode == TwoMachinesPlay ||
7688 gameMode == EndOfGame) {
7689 i = forwardMostMove;
7690 while (i > currentMove) {
7691 SendToProgram("undo\n", &first);
7694 whiteTimeRemaining = timeRemaining[0][currentMove];
7695 blackTimeRemaining = timeRemaining[1][currentMove];
7696 DisplayBothClocks();
7697 if (whiteFlag || blackFlag) {
7698 whiteFlag = blackFlag = 0;
7703 gameMode = EditGame;
7712 if (gameMode == EditPosition) {
7718 if (gameMode != EditGame) return;
7720 gameMode = EditPosition;
7723 if (currentMove > 0)
7724 CopyBoard(boards[0], boards[currentMove]);
7726 blackPlaysFirst = !WhiteOnMove(currentMove);
7728 currentMove = forwardMostMove = backwardMostMove = 0;
7729 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7736 if (first.analysisSupport && first.analyzing) {
7737 SendToProgram("exit\n", &first);
7738 first.analyzing = FALSE;
7741 thinkOutput[0] = NULLCHAR;
7747 startedFromSetupPosition = TRUE;
7748 InitChessProgram(&first);
7749 SendToProgram("force\n", &first);
7750 if (blackPlaysFirst) {
7751 strcpy(moveList[0], "");
7752 strcpy(parseList[0], "");
7753 currentMove = forwardMostMove = backwardMostMove = 1;
7754 CopyBoard(boards[1], boards[0]);
7756 currentMove = forwardMostMove = backwardMostMove = 0;
7758 SendBoard(&first, forwardMostMove);
7760 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7761 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7762 gameMode = EditGame;
7764 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7767 /* Pause for `ms' milliseconds */
7768 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7778 } while (SubtractTimeMarks(&m2, &m1) < ms);
7781 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7783 SendMultiLineToICS(buf)
7786 char temp[MSG_SIZ+1], *p;
7793 strncpy(temp, buf, len);
7798 if (*p == '\n' || *p == '\r')
7805 SendToPlayer(temp, strlen(temp));
7809 SetWhiteToPlayEvent()
7811 if (gameMode == EditPosition) {
7812 blackPlaysFirst = FALSE;
7813 DisplayBothClocks(); /* works because currentMove is 0 */
7814 } else if (gameMode == IcsExamining) {
7815 SendToICS(ics_prefix);
7816 SendToICS("tomove white\n");
7821 SetBlackToPlayEvent()
7823 if (gameMode == EditPosition) {
7824 blackPlaysFirst = TRUE;
7825 currentMove = 1; /* kludge */
7826 DisplayBothClocks();
7828 } else if (gameMode == IcsExamining) {
7829 SendToICS(ics_prefix);
7830 SendToICS("tomove black\n");
7835 EditPositionMenuEvent(selection, x, y)
7836 ChessSquare selection;
7841 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7843 switch (selection) {
7845 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7846 SendToICS(ics_prefix);
7847 SendToICS("bsetup clear\n");
7848 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7849 SendToICS(ics_prefix);
7850 SendToICS("clearboard\n");
7852 for (x = 0; x < BOARD_SIZE; x++) {
7853 for (y = 0; y < BOARD_SIZE; y++) {
7854 if (gameMode == IcsExamining) {
7855 if (boards[currentMove][y][x] != EmptySquare) {
7856 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7861 boards[0][y][x] = EmptySquare;
7866 if (gameMode == EditPosition) {
7867 DrawPosition(FALSE, boards[0]);
7872 SetWhiteToPlayEvent();
7876 SetBlackToPlayEvent();
7880 if (gameMode == IcsExamining) {
7881 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7884 boards[0][y][x] = EmptySquare;
7885 DrawPosition(FALSE, boards[0]);
7890 if (gameMode == IcsExamining) {
7891 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7892 PieceToChar(selection), 'a' + x, '1' + y);
7895 boards[0][y][x] = selection;
7896 DrawPosition(FALSE, boards[0]);
7904 DropMenuEvent(selection, x, y)
7905 ChessSquare selection;
7911 case IcsPlayingWhite:
7912 case MachinePlaysBlack:
7913 if (!WhiteOnMove(currentMove)) {
7914 DisplayMoveError(_("It is Black's turn"));
7917 moveType = WhiteDrop;
7919 case IcsPlayingBlack:
7920 case MachinePlaysWhite:
7921 if (WhiteOnMove(currentMove)) {
7922 DisplayMoveError(_("It is White's turn"));
7925 moveType = BlackDrop;
7928 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7934 if (moveType == BlackDrop && selection < BlackPawn) {
7935 selection = (ChessSquare) ((int) selection
7936 + (int) BlackPawn - (int) WhitePawn);
7938 if (boards[currentMove][y][x] != EmptySquare) {
7939 DisplayMoveError(_("That square is occupied"));
7943 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
7949 /* Accept a pending offer of any kind from opponent */
7951 if (appData.icsActive) {
7952 SendToICS(ics_prefix);
7953 SendToICS("accept\n");
7954 } else if (cmailMsgLoaded) {
7955 if (currentMove == cmailOldMove &&
7956 commentList[cmailOldMove] != NULL &&
7957 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7958 "Black offers a draw" : "White offers a draw")) {
7960 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
7961 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
7963 DisplayError(_("There is no pending offer on this move"), 0);
7964 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7967 /* Not used for offers from chess program */
7974 /* Decline a pending offer of any kind from opponent */
7976 if (appData.icsActive) {
7977 SendToICS(ics_prefix);
7978 SendToICS("decline\n");
7979 } else if (cmailMsgLoaded) {
7980 if (currentMove == cmailOldMove &&
7981 commentList[cmailOldMove] != NULL &&
7982 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7983 "Black offers a draw" : "White offers a draw")) {
7985 AppendComment(cmailOldMove, "Draw declined");
7986 DisplayComment(cmailOldMove - 1, "Draw declined");
7989 DisplayError(_("There is no pending offer on this move"), 0);
7992 /* Not used for offers from chess program */
7999 /* Issue ICS rematch command */
8000 if (appData.icsActive) {
8001 SendToICS(ics_prefix);
8002 SendToICS("rematch\n");
8009 /* Call your opponent's flag (claim a win on time) */
8010 if (appData.icsActive) {
8011 SendToICS(ics_prefix);
8012 SendToICS("flag\n");
8017 case MachinePlaysWhite:
8020 GameEnds(GameIsDrawn, "Both players ran out of time",
8023 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8025 DisplayError(_("Your opponent is not out of time"), 0);
8028 case MachinePlaysBlack:
8031 GameEnds(GameIsDrawn, "Both players ran out of time",
8034 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8036 DisplayError(_("Your opponent is not out of time"), 0);
8046 /* Offer draw or accept pending draw offer from opponent */
8048 if (appData.icsActive) {
8049 /* Note: tournament rules require draw offers to be
8050 made after you make your move but before you punch
8051 your clock. Currently ICS doesn't let you do that;
8052 instead, you immediately punch your clock after making
8053 a move, but you can offer a draw at any time. */
8055 SendToICS(ics_prefix);
8056 SendToICS("draw\n");
8057 } else if (cmailMsgLoaded) {
8058 if (currentMove == cmailOldMove &&
8059 commentList[cmailOldMove] != NULL &&
8060 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8061 "Black offers a draw" : "White offers a draw")) {
8062 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8063 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8064 } else if (currentMove == cmailOldMove + 1) {
8065 char *offer = WhiteOnMove(cmailOldMove) ?
8066 "White offers a draw" : "Black offers a draw";
8067 AppendComment(currentMove, offer);
8068 DisplayComment(currentMove - 1, offer);
8069 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8071 DisplayError(_("You must make your move before offering a draw"), 0);
8072 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8074 } else if (first.offeredDraw) {
8075 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8077 if (first.sendDrawOffers) {
8078 SendToProgram("draw\n", &first);
8079 userOfferedDraw = TRUE;
8087 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8089 if (appData.icsActive) {
8090 SendToICS(ics_prefix);
8091 SendToICS("adjourn\n");
8093 /* Currently GNU Chess doesn't offer or accept Adjourns */
8101 /* Offer Abort or accept pending Abort offer from opponent */
8103 if (appData.icsActive) {
8104 SendToICS(ics_prefix);
8105 SendToICS("abort\n");
8107 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8114 /* Resign. You can do this even if it's not your turn. */
8116 if (appData.icsActive) {
8117 SendToICS(ics_prefix);
8118 SendToICS("resign\n");
8121 case MachinePlaysWhite:
8122 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8124 case MachinePlaysBlack:
8125 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8128 if (cmailMsgLoaded) {
8130 if (WhiteOnMove(cmailOldMove)) {
8131 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8133 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8135 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8146 StopObservingEvent()
8148 /* Stop observing current games */
8149 SendToICS(ics_prefix);
8150 SendToICS("unobserve\n");
8154 StopExaminingEvent()
8156 /* Stop observing current game */
8157 SendToICS(ics_prefix);
8158 SendToICS("unexamine\n");
8162 ForwardInner(target)
8167 if (appData.debugMode)
8168 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8169 target, currentMove, forwardMostMove);
8171 if (gameMode == EditPosition)
8174 if (gameMode == PlayFromGameFile && !pausing)
8177 if (gameMode == IcsExamining && pausing)
8178 limit = pauseExamForwardMostMove;
8180 limit = forwardMostMove;
8182 if (target > limit) target = limit;
8184 if (target > 0 && moveList[target - 1][0]) {
8185 int fromX, fromY, toX, toY;
8186 toX = moveList[target - 1][2] - 'a';
8187 toY = moveList[target - 1][3] - '1';
8188 if (moveList[target - 1][1] == '@') {
8189 if (appData.highlightLastMove) {
8190 SetHighlights(-1, -1, toX, toY);
8193 fromX = moveList[target - 1][0] - 'a';
8194 fromY = moveList[target - 1][1] - '1';
8195 if (target == currentMove + 1) {
8196 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8198 if (appData.highlightLastMove) {
8199 SetHighlights(fromX, fromY, toX, toY);
8203 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8204 gameMode == Training || gameMode == PlayFromGameFile ||
8205 gameMode == AnalyzeFile) {
8206 while (currentMove < target) {
8207 SendMoveToProgram(currentMove++, &first);
8210 currentMove = target;
8213 if (gameMode == EditGame || gameMode == EndOfGame) {
8214 whiteTimeRemaining = timeRemaining[0][currentMove];
8215 blackTimeRemaining = timeRemaining[1][currentMove];
8217 DisplayBothClocks();
8218 DisplayMove(currentMove - 1);
8219 DrawPosition(FALSE, boards[currentMove]);
8220 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8221 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8222 DisplayComment(currentMove - 1, commentList[currentMove]);
8230 if (gameMode == IcsExamining && !pausing) {
8231 SendToICS(ics_prefix);
8232 SendToICS("forward\n");
8234 ForwardInner(currentMove + 1);
8241 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8242 /* to optimze, we temporarily turn off analysis mode while we feed
8243 * the remaining moves to the engine. Otherwise we get analysis output
8246 if (first.analysisSupport) {
8247 SendToProgram("exit\nforce\n", &first);
8248 first.analyzing = FALSE;
8252 if (gameMode == IcsExamining && !pausing) {
8253 SendToICS(ics_prefix);
8254 SendToICS("forward 999999\n");
8256 ForwardInner(forwardMostMove);
8259 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8260 /* we have fed all the moves, so reactivate analysis mode */
8261 SendToProgram("analyze\n", &first);
8262 first.analyzing = TRUE;
8263 /*first.maybeThinking = TRUE;*/
8264 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8269 BackwardInner(target)
8272 if (appData.debugMode)
8273 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8274 target, currentMove, forwardMostMove);
8276 if (gameMode == EditPosition) return;
8277 if (currentMove <= backwardMostMove) {
8279 DrawPosition(FALSE, boards[currentMove]);
8282 if (gameMode == PlayFromGameFile && !pausing)
8285 if (moveList[target][0]) {
8286 int fromX, fromY, toX, toY;
8287 toX = moveList[target][2] - 'a';
8288 toY = moveList[target][3] - '1';
8289 if (moveList[target][1] == '@') {
8290 if (appData.highlightLastMove) {
8291 SetHighlights(-1, -1, toX, toY);
8294 fromX = moveList[target][0] - 'a';
8295 fromY = moveList[target][1] - '1';
8296 if (target == currentMove - 1) {
8297 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8299 if (appData.highlightLastMove) {
8300 SetHighlights(fromX, fromY, toX, toY);
8304 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8305 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8306 while (currentMove > target) {
8307 SendToProgram("undo\n", &first);
8311 currentMove = target;
8314 if (gameMode == EditGame || gameMode == EndOfGame) {
8315 whiteTimeRemaining = timeRemaining[0][currentMove];
8316 blackTimeRemaining = timeRemaining[1][currentMove];
8318 DisplayBothClocks();
8319 DisplayMove(currentMove - 1);
8320 DrawPosition(FALSE, boards[currentMove]);
8321 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8322 if (commentList[currentMove] != NULL) {
8323 DisplayComment(currentMove - 1, commentList[currentMove]);
8330 if (gameMode == IcsExamining && !pausing) {
8331 SendToICS(ics_prefix);
8332 SendToICS("backward\n");
8334 BackwardInner(currentMove - 1);
8341 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8342 /* to optimze, we temporarily turn off analysis mode while we undo
8343 * all the moves. Otherwise we get analysis output after each undo.
8345 if (first.analysisSupport) {
8346 SendToProgram("exit\nforce\n", &first);
8347 first.analyzing = FALSE;
8351 if (gameMode == IcsExamining && !pausing) {
8352 SendToICS(ics_prefix);
8353 SendToICS("backward 999999\n");
8355 BackwardInner(backwardMostMove);
8358 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8359 /* we have fed all the moves, so reactivate analysis mode */
8360 SendToProgram("analyze\n", &first);
8361 first.analyzing = TRUE;
8362 /*first.maybeThinking = TRUE;*/
8363 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8370 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8371 if (to >= forwardMostMove) to = forwardMostMove;
8372 if (to <= backwardMostMove) to = backwardMostMove;
8373 if (to < currentMove) {
8383 if (gameMode != IcsExamining) {
8384 DisplayError(_("You are not examining a game"), 0);
8388 DisplayError(_("You can't revert while pausing"), 0);
8391 SendToICS(ics_prefix);
8392 SendToICS("revert\n");
8399 case MachinePlaysWhite:
8400 case MachinePlaysBlack:
8401 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8402 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8405 if (forwardMostMove < 2) return;
8406 currentMove = forwardMostMove = forwardMostMove - 2;
8407 whiteTimeRemaining = timeRemaining[0][currentMove];
8408 blackTimeRemaining = timeRemaining[1][currentMove];
8409 DisplayBothClocks();
8410 DisplayMove(currentMove - 1);
8411 ClearHighlights();/*!! could figure this out*/
8412 DrawPosition(FALSE, boards[currentMove]);
8413 SendToProgram("remove\n", &first);
8414 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8417 case BeginningOfGame:
8421 case IcsPlayingWhite:
8422 case IcsPlayingBlack:
8423 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8424 SendToICS(ics_prefix);
8425 SendToICS("takeback 2\n");
8427 SendToICS(ics_prefix);
8428 SendToICS("takeback 1\n");
8437 ChessProgramState *cps;
8440 case MachinePlaysWhite:
8441 if (!WhiteOnMove(forwardMostMove)) {
8442 DisplayError(_("It is your turn"), 0);
8447 case MachinePlaysBlack:
8448 if (WhiteOnMove(forwardMostMove)) {
8449 DisplayError(_("It is your turn"), 0);
8454 case TwoMachinesPlay:
8455 if (WhiteOnMove(forwardMostMove) ==
8456 (first.twoMachinesColor[0] == 'w')) {
8462 case BeginningOfGame:
8466 SendToProgram("?\n", cps);
8473 if (gameMode != EditGame) return;
8480 if (forwardMostMove > currentMove) {
8481 if (gameInfo.resultDetails != NULL) {
8482 free(gameInfo.resultDetails);
8483 gameInfo.resultDetails = NULL;
8484 gameInfo.result = GameUnfinished;
8486 forwardMostMove = currentMove;
8487 HistorySet(parseList, backwardMostMove, forwardMostMove,
8495 if (appData.noChessProgram) return;
8497 case MachinePlaysWhite:
8498 if (WhiteOnMove(forwardMostMove)) {
8499 DisplayError(_("Wait until your turn"), 0);
8503 case BeginningOfGame:
8504 case MachinePlaysBlack:
8505 if (!WhiteOnMove(forwardMostMove)) {
8506 DisplayError(_("Wait until your turn"), 0);
8511 DisplayError(_("No hint available"), 0);
8514 SendToProgram("hint\n", &first);
8515 hintRequested = TRUE;
8521 if (appData.noChessProgram) return;
8523 case MachinePlaysWhite:
8524 if (WhiteOnMove(forwardMostMove)) {
8525 DisplayError(_("Wait until your turn"), 0);
8529 case BeginningOfGame:
8530 case MachinePlaysBlack:
8531 if (!WhiteOnMove(forwardMostMove)) {
8532 DisplayError(_("Wait until your turn"), 0);
8539 case TwoMachinesPlay:
8544 SendToProgram("bk\n", &first);
8545 bookOutput[0] = NULLCHAR;
8546 bookRequested = TRUE;
8552 char *tags = PGNTags(&gameInfo);
8553 TagsPopUp(tags, CmailMsg());
8557 /* end button procedures */
8560 PrintPosition(fp, move)
8566 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8567 for (j = 0; j < BOARD_SIZE; j++) {
8568 char c = PieceToChar(boards[move][i][j]);
8569 fputc(c == 'x' ? '.' : c, fp);
8570 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8573 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8574 fprintf(fp, "white to play\n");
8576 fprintf(fp, "black to play\n");
8583 if (gameInfo.white != NULL) {
8584 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8590 /* Find last component of program's own name, using some heuristics */
8592 TidyProgramName(prog, host, buf)
8593 char *prog, *host, buf[MSG_SIZ];
8596 int local = (strcmp(host, "localhost") == 0);
8597 while (!local && (p = strchr(prog, ';')) != NULL) {
8599 while (*p == ' ') p++;
8602 if (*prog == '"' || *prog == '\'') {
8603 q = strchr(prog + 1, *prog);
8605 q = strchr(prog, ' ');
8607 if (q == NULL) q = prog + strlen(prog);
8609 while (p >= prog && *p != '/' && *p != '\\') p--;
8611 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8612 memcpy(buf, p, q - p);
8613 buf[q - p] = NULLCHAR;
8621 TimeControlTagValue()
8624 if (!appData.clockMode) {
8626 } else if (movesPerSession > 0) {
8627 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8628 } else if (timeIncrement == 0) {
8629 sprintf(buf, "%ld", timeControl/1000);
8631 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8633 return StrSave(buf);
8639 /* This routine is used only for certain modes */
8640 VariantClass v = gameInfo.variant;
8641 ClearGameInfo(&gameInfo);
8642 gameInfo.variant = v;
8645 case MachinePlaysWhite:
8646 gameInfo.event = StrSave("Computer chess game");
8647 gameInfo.site = StrSave(HostName());
8648 gameInfo.date = PGNDate();
8649 gameInfo.round = StrSave("-");
8650 gameInfo.white = StrSave(first.tidy);
8651 gameInfo.black = StrSave(UserName());
8652 gameInfo.timeControl = TimeControlTagValue();
8655 case MachinePlaysBlack:
8656 gameInfo.event = StrSave("Computer chess game");
8657 gameInfo.site = StrSave(HostName());
8658 gameInfo.date = PGNDate();
8659 gameInfo.round = StrSave("-");
8660 gameInfo.white = StrSave(UserName());
8661 gameInfo.black = StrSave(first.tidy);
8662 gameInfo.timeControl = TimeControlTagValue();
8665 case TwoMachinesPlay:
8666 gameInfo.event = StrSave("Computer chess game");
8667 gameInfo.site = StrSave(HostName());
8668 gameInfo.date = PGNDate();
8669 if (matchGame > 0) {
8671 sprintf(buf, "%d", matchGame);
8672 gameInfo.round = StrSave(buf);
8674 gameInfo.round = StrSave("-");
8676 if (first.twoMachinesColor[0] == 'w') {
8677 gameInfo.white = StrSave(first.tidy);
8678 gameInfo.black = StrSave(second.tidy);
8680 gameInfo.white = StrSave(second.tidy);
8681 gameInfo.black = StrSave(first.tidy);
8683 gameInfo.timeControl = TimeControlTagValue();
8687 gameInfo.event = StrSave("Edited game");
8688 gameInfo.site = StrSave(HostName());
8689 gameInfo.date = PGNDate();
8690 gameInfo.round = StrSave("-");
8691 gameInfo.white = StrSave("-");
8692 gameInfo.black = StrSave("-");
8696 gameInfo.event = StrSave("Edited position");
8697 gameInfo.site = StrSave(HostName());
8698 gameInfo.date = PGNDate();
8699 gameInfo.round = StrSave("-");
8700 gameInfo.white = StrSave("-");
8701 gameInfo.black = StrSave("-");
8704 case IcsPlayingWhite:
8705 case IcsPlayingBlack:
8710 case PlayFromGameFile:
8711 gameInfo.event = StrSave("Game from non-PGN file");
8712 gameInfo.site = StrSave(HostName());
8713 gameInfo.date = PGNDate();
8714 gameInfo.round = StrSave("-");
8715 gameInfo.white = StrSave("?");
8716 gameInfo.black = StrSave("?");
8725 ReplaceComment(index, text)
8731 while (*text == '\n') text++;
8733 while (len > 0 && text[len - 1] == '\n') len--;
8735 if (commentList[index] != NULL)
8736 free(commentList[index]);
8739 commentList[index] = NULL;
8742 commentList[index] = (char *) malloc(len + 2);
8743 strncpy(commentList[index], text, len);
8744 commentList[index][len] = '\n';
8745 commentList[index][len + 1] = NULLCHAR;
8758 if (ch == '\r') continue;
8760 } while (ch != '\0');
8764 AppendComment(index, text)
8772 while (*text == '\n') text++;
8774 while (len > 0 && text[len - 1] == '\n') len--;
8776 if (len == 0) return;
8778 if (commentList[index] != NULL) {
8779 old = commentList[index];
8780 oldlen = strlen(old);
8781 commentList[index] = (char *) malloc(oldlen + len + 2);
8782 strcpy(commentList[index], old);
8784 strncpy(&commentList[index][oldlen], text, len);
8785 commentList[index][oldlen + len] = '\n';
8786 commentList[index][oldlen + len + 1] = NULLCHAR;
8788 commentList[index] = (char *) malloc(len + 2);
8789 strncpy(commentList[index], text, len);
8790 commentList[index][len] = '\n';
8791 commentList[index][len + 1] = NULLCHAR;
8796 SendToProgram(message, cps)
8798 ChessProgramState *cps;
8800 int count, outCount, error;
8803 if (cps->pr == NULL) return;
8806 if (appData.debugMode) {
8809 fprintf(debugFP, "%ld >%-6s: %s",
8810 SubtractTimeMarks(&now, &programStartTime),
8811 cps->which, message);
8814 count = strlen(message);
8815 outCount = OutputToProcess(cps->pr, message, count, &error);
8816 if (outCount < count && !exiting) {
8817 sprintf(buf, _("Error writing to %s chess program"), cps->which);
8818 DisplayFatalError(buf, error, 1);
8823 ReceiveFromProgram(isr, closure, message, count, error)
8832 ChessProgramState *cps = (ChessProgramState *)closure;
8834 if (isr != cps->isr) return; /* Killed intentionally */
8838 _("Error: %s chess program (%s) exited unexpectedly"),
8839 cps->which, cps->program);
8840 RemoveInputSource(cps->isr);
8841 DisplayFatalError(buf, 0, 1);
8844 _("Error reading from %s chess program (%s)"),
8845 cps->which, cps->program);
8846 RemoveInputSource(cps->isr);
8847 DisplayFatalError(buf, error, 1);
8849 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8853 if ((end_str = strchr(message, '\r')) != NULL)
8854 *end_str = NULLCHAR;
8855 if ((end_str = strchr(message, '\n')) != NULL)
8856 *end_str = NULLCHAR;
8858 if (appData.debugMode) {
8861 fprintf(debugFP, "%ld <%-6s: %s\n",
8862 SubtractTimeMarks(&now, &programStartTime),
8863 cps->which, message);
8865 HandleMachineMove(message, cps);
8870 SendTimeControl(cps, mps, tc, inc, sd, st)
8871 ChessProgramState *cps;
8872 int mps, inc, sd, st;
8876 int seconds = (tc / 1000) % 60;
8879 /* Set exact time per move, normally using st command */
8880 if (cps->stKludge) {
8881 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8884 sprintf(buf, "level 1 %d\n", st/60);
8886 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8889 sprintf(buf, "st %d\n", st);
8892 /* Set conventional or incremental time control, using level command */
8894 /* Note old gnuchess bug -- minutes:seconds used to not work.
8895 Fixed in later versions, but still avoid :seconds
8896 when seconds is 0. */
8897 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8899 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8903 SendToProgram(buf, cps);
8905 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8906 /* Orthogonally, limit search to given depth */
8908 if (cps->sdKludge) {
8909 sprintf(buf, "depth\n%d\n", sd);
8911 sprintf(buf, "sd %d\n", sd);
8913 SendToProgram(buf, cps);
8918 SendTimeRemaining(cps, machineWhite)
8919 ChessProgramState *cps;
8920 int /*boolean*/ machineWhite;
8922 char message[MSG_SIZ];
8925 /* Note: this routine must be called when the clocks are stopped
8926 or when they have *just* been set or switched; otherwise
8927 it will be off by the time since the current tick started.
8930 time = whiteTimeRemaining / 10;
8931 otime = blackTimeRemaining / 10;
8933 time = blackTimeRemaining / 10;
8934 otime = whiteTimeRemaining / 10;
8936 if (time <= 0) time = 1;
8937 if (otime <= 0) otime = 1;
8939 sprintf(message, "time %ld\notim %ld\n", time, otime);
8940 SendToProgram(message, cps);
8944 BoolFeature(p, name, loc, cps)
8948 ChessProgramState *cps;
8951 int len = strlen(name);
8953 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8955 sscanf(*p, "%d", &val);
8957 while (**p && **p != ' ') (*p)++;
8958 sprintf(buf, "accepted %s\n", name);
8959 SendToProgram(buf, cps);
8966 IntFeature(p, name, loc, cps)
8970 ChessProgramState *cps;
8973 int len = strlen(name);
8974 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8976 sscanf(*p, "%d", loc);
8977 while (**p && **p != ' ') (*p)++;
8978 sprintf(buf, "accepted %s\n", name);
8979 SendToProgram(buf, cps);
8986 StringFeature(p, name, loc, cps)
8990 ChessProgramState *cps;
8993 int len = strlen(name);
8994 if (strncmp((*p), name, len) == 0
8995 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
8997 sscanf(*p, "%[^\"]", loc);
8998 while (**p && **p != '\"') (*p)++;
8999 if (**p == '\"') (*p)++;
9000 sprintf(buf, "accepted %s\n", name);
9001 SendToProgram(buf, cps);
9008 FeatureDone(cps, val)
9009 ChessProgramState* cps;
9012 DelayedEventCallback cb = GetDelayedEvent();
9013 if ((cb == InitBackEnd3 && cps == &first) ||
9014 (cb == TwoMachinesEventIfReady && cps == &second)) {
9015 CancelDelayedEvent();
9016 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9018 cps->initDone = val;
9021 /* Parse feature command from engine */
9023 ParseFeatures(args, cps)
9025 ChessProgramState *cps;
9033 while (*p == ' ') p++;
9034 if (*p == NULLCHAR) return;
9036 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9037 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9038 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9039 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9040 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9041 if (BoolFeature(&p, "reuse", &val, cps)) {
9042 /* Engine can disable reuse, but can't enable it if user said no */
9043 if (!val) cps->reuse = FALSE;
9046 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9047 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9048 if (gameMode == TwoMachinesPlay) {
9049 DisplayTwoMachinesTitle();
9055 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9056 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9057 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9058 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9059 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9060 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9061 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9062 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9063 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9064 if (IntFeature(&p, "done", &val, cps)) {
9065 FeatureDone(cps, val);
9069 /* unknown feature: complain and skip */
9071 while (*q && *q != '=') q++;
9072 sprintf(buf, "rejected %.*s\n", q-p, p);
9073 SendToProgram(buf, cps);
9079 while (*p && *p != '\"') p++;
9080 if (*p == '\"') p++;
9082 while (*p && *p != ' ') p++;
9090 PeriodicUpdatesEvent(newState)
9093 if (newState == appData.periodicUpdates)
9096 appData.periodicUpdates=newState;
9098 /* Display type changes, so update it now */
9101 /* Get the ball rolling again... */
9103 AnalysisPeriodicEvent(1);
9104 StartAnalysisClock();
9109 PonderNextMoveEvent(newState)
9112 if (newState == appData.ponderNextMove) return;
9113 if (gameMode == EditPosition) EditPositionDone();
9115 SendToProgram("hard\n", &first);
9116 if (gameMode == TwoMachinesPlay) {
9117 SendToProgram("hard\n", &second);
9120 SendToProgram("easy\n", &first);
9121 thinkOutput[0] = NULLCHAR;
9122 if (gameMode == TwoMachinesPlay) {
9123 SendToProgram("easy\n", &second);
9126 appData.ponderNextMove = newState;
9130 ShowThinkingEvent(newState)
9133 if (newState == appData.showThinking) return;
9134 if (gameMode == EditPosition) EditPositionDone();
9136 SendToProgram("post\n", &first);
9137 if (gameMode == TwoMachinesPlay) {
9138 SendToProgram("post\n", &second);
9141 SendToProgram("nopost\n", &first);
9142 thinkOutput[0] = NULLCHAR;
9143 if (gameMode == TwoMachinesPlay) {
9144 SendToProgram("nopost\n", &second);
9147 appData.showThinking = newState;
9151 AskQuestionEvent(title, question, replyPrefix, which)
9152 char *title; char *question; char *replyPrefix; char *which;
9154 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9155 if (pr == NoProc) return;
9156 AskQuestion(title, question, replyPrefix, pr);
9160 DisplayMove(moveNumber)
9163 char message[MSG_SIZ];
9165 char cpThinkOutput[MSG_SIZ];
9167 if (moveNumber == forwardMostMove - 1 ||
9168 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9170 strcpy(cpThinkOutput, thinkOutput);
9171 if (strchr(cpThinkOutput, '\n'))
9172 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9174 *cpThinkOutput = NULLCHAR;
9177 if (moveNumber == forwardMostMove - 1 &&
9178 gameInfo.resultDetails != NULL) {
9179 if (gameInfo.resultDetails[0] == NULLCHAR) {
9180 sprintf(res, " %s", PGNResult(gameInfo.result));
9182 sprintf(res, " {%s} %s",
9183 gameInfo.resultDetails, PGNResult(gameInfo.result));
9189 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9190 DisplayMessage(res, cpThinkOutput);
9192 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9193 WhiteOnMove(moveNumber) ? " " : ".. ",
9194 parseList[moveNumber], res);
9195 DisplayMessage(message, cpThinkOutput);
9200 DisplayAnalysisText(text)
9205 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9206 sprintf(buf, "Analysis (%s)", first.tidy);
9207 AnalysisPopUp(buf, text);
9215 while (*str && isspace(*str)) ++str;
9216 while (*str && !isspace(*str)) ++str;
9217 if (!*str) return 1;
9218 while (*str && isspace(*str)) ++str;
9219 if (!*str) return 1;
9228 static char *xtra[] = { "", " (--)", " (++)" };
9231 if (programStats.time == 0) {
9232 programStats.time = 1;
9235 if (programStats.got_only_move) {
9236 strcpy(buf, programStats.movelist);
9238 nps = (((double)programStats.nodes) /
9239 (((double)programStats.time)/100.0));
9241 cs = programStats.time % 100;
9242 s = programStats.time / 100;
9248 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9249 if (programStats.move_name[0] != NULLCHAR) {
9250 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9252 programStats.nr_moves-programStats.moves_left,
9253 programStats.nr_moves, programStats.move_name,
9254 ((float)programStats.score)/100.0, programStats.movelist,
9255 only_one_move(programStats.movelist)?
9256 xtra[programStats.got_fail] : "",
9257 programStats.nodes, (int)nps, h, m, s, cs);
9259 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9261 programStats.nr_moves-programStats.moves_left,
9262 programStats.nr_moves, ((float)programStats.score)/100.0,
9263 programStats.movelist,
9264 only_one_move(programStats.movelist)?
9265 xtra[programStats.got_fail] : "",
9266 programStats.nodes, (int)nps, h, m, s, cs);
9269 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9271 ((float)programStats.score)/100.0,
9272 programStats.movelist,
9273 only_one_move(programStats.movelist)?
9274 xtra[programStats.got_fail] : "",
9275 programStats.nodes, (int)nps, h, m, s, cs);
9278 DisplayAnalysisText(buf);
9282 DisplayComment(moveNumber, text)
9286 char title[MSG_SIZ];
9288 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9289 strcpy(title, "Comment");
9291 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9292 WhiteOnMove(moveNumber) ? " " : ".. ",
9293 parseList[moveNumber]);
9296 CommentPopUp(title, text);
9299 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9300 * might be busy thinking or pondering. It can be omitted if your
9301 * gnuchess is configured to stop thinking immediately on any user
9302 * input. However, that gnuchess feature depends on the FIONREAD
9303 * ioctl, which does not work properly on some flavors of Unix.
9307 ChessProgramState *cps;
9310 if (!cps->useSigint) return;
9311 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9313 case MachinePlaysWhite:
9314 case MachinePlaysBlack:
9315 case TwoMachinesPlay:
9316 case IcsPlayingWhite:
9317 case IcsPlayingBlack:
9320 /* Skip if we know it isn't thinking */
9321 if (!cps->maybeThinking) return;
9322 if (appData.debugMode)
9323 fprintf(debugFP, "Interrupting %s\n", cps->which);
9324 InterruptChildProcess(cps->pr);
9325 cps->maybeThinking = FALSE;
9330 #endif /*ATTENTION*/
9336 if (whiteTimeRemaining <= 0) {
9339 if (appData.icsActive) {
9340 if (appData.autoCallFlag &&
9341 gameMode == IcsPlayingBlack && !blackFlag) {
9342 SendToICS(ics_prefix);
9343 SendToICS("flag\n");
9347 DisplayTitle(_("Both flags fell"));
9349 DisplayTitle(_("White's flag fell"));
9350 if (appData.autoCallFlag) {
9351 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9358 if (blackTimeRemaining <= 0) {
9361 if (appData.icsActive) {
9362 if (appData.autoCallFlag &&
9363 gameMode == IcsPlayingWhite && !whiteFlag) {
9364 SendToICS(ics_prefix);
9365 SendToICS("flag\n");
9369 DisplayTitle(_("Both flags fell"));
9371 DisplayTitle(_("Black's flag fell"));
9372 if (appData.autoCallFlag) {
9373 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9386 if (!appData.clockMode || appData.icsActive ||
9387 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9389 if (timeIncrement >= 0) {
9390 if (WhiteOnMove(forwardMostMove)) {
9391 blackTimeRemaining += timeIncrement;
9393 whiteTimeRemaining += timeIncrement;
9397 * add time to clocks when time control is achieved
9399 if (movesPerSession) {
9400 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9402 /* White made time control */
9403 whiteTimeRemaining += timeControl;
9406 /* Black made time control */
9407 blackTimeRemaining += timeControl;
9418 int wom = gameMode == EditPosition ?
9419 !blackPlaysFirst : WhiteOnMove(currentMove);
9420 DisplayWhiteClock(whiteTimeRemaining, wom);
9421 DisplayBlackClock(blackTimeRemaining, !wom);
9425 /* Timekeeping seems to be a portability nightmare. I think everyone
9426 has ftime(), but I'm really not sure, so I'm including some ifdefs
9427 to use other calls if you don't. Clocks will be less accurate if
9428 you have neither ftime nor gettimeofday.
9431 /* Get the current time as a TimeMark */
9436 #if HAVE_GETTIMEOFDAY
9438 struct timeval timeVal;
9439 struct timezone timeZone;
9441 gettimeofday(&timeVal, &timeZone);
9442 tm->sec = (long) timeVal.tv_sec;
9443 tm->ms = (int) (timeVal.tv_usec / 1000L);
9445 #else /*!HAVE_GETTIMEOFDAY*/
9448 #include <sys/timeb.h>
9452 tm->sec = (long) timeB.time;
9453 tm->ms = (int) timeB.millitm;
9455 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9456 tm->sec = (long) time(NULL);
9462 /* Return the difference in milliseconds between two
9463 time marks. We assume the difference will fit in a long!
9466 SubtractTimeMarks(tm2, tm1)
9467 TimeMark *tm2, *tm1;
9469 return 1000L*(tm2->sec - tm1->sec) +
9470 (long) (tm2->ms - tm1->ms);
9475 * Code to manage the game clocks.
9477 * In tournament play, black starts the clock and then white makes a move.
9478 * We give the human user a slight advantage if he is playing white---the
9479 * clocks don't run until he makes his first move, so it takes zero time.
9480 * Also, we don't account for network lag, so we could get out of sync
9481 * with GNU Chess's clock -- but then, referees are always right.
9484 static TimeMark tickStartTM;
9485 static long intendedTickLength;
9488 NextTickLength(timeRemaining)
9491 long nominalTickLength, nextTickLength;
9493 if (timeRemaining > 0L && timeRemaining <= 10000L)
9494 nominalTickLength = 100L;
9496 nominalTickLength = 1000L;
9497 nextTickLength = timeRemaining % nominalTickLength;
9498 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9500 return nextTickLength;
9503 /* Stop clocks and reset to a fresh time control */
9507 (void) StopClockTimer();
9508 if (appData.icsActive) {
9509 whiteTimeRemaining = blackTimeRemaining = 0;
9511 whiteTimeRemaining = blackTimeRemaining = timeControl;
9513 if (whiteFlag || blackFlag) {
9515 whiteFlag = blackFlag = FALSE;
9517 DisplayBothClocks();
9520 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9522 /* Decrement running clock by amount of time that has passed */
9527 long lastTickLength, fudge;
9530 if (!appData.clockMode) return;
9531 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9535 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9537 /* Fudge if we woke up a little too soon */
9538 fudge = intendedTickLength - lastTickLength;
9539 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9541 if (WhiteOnMove(forwardMostMove)) {
9542 timeRemaining = whiteTimeRemaining -= lastTickLength;
9543 DisplayWhiteClock(whiteTimeRemaining - fudge,
9544 WhiteOnMove(currentMove));
9546 timeRemaining = blackTimeRemaining -= lastTickLength;
9547 DisplayBlackClock(blackTimeRemaining - fudge,
9548 !WhiteOnMove(currentMove));
9551 if (CheckFlags()) return;
9554 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9555 StartClockTimer(intendedTickLength);
9557 /* if the time remaining has fallen below the alarm threshold, sound the
9558 * alarm. if the alarm has sounded and (due to a takeback or time control
9559 * with increment) the time remaining has increased to a level above the
9560 * threshold, reset the alarm so it can sound again.
9563 if (appData.icsActive && appData.icsAlarm) {
9565 /* make sure we are dealing with the user's clock */
9566 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9567 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9570 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9571 alarmSounded = FALSE;
9572 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9574 alarmSounded = TRUE;
9580 /* A player has just moved, so stop the previously running
9581 clock and (if in clock mode) start the other one.
9582 We redisplay both clocks in case we're in ICS mode, because
9583 ICS gives us an update to both clocks after every move.
9584 Note that this routine is called *after* forwardMostMove
9585 is updated, so the last fractional tick must be subtracted
9586 from the color that is *not* on move now.
9591 long lastTickLength;
9593 int flagged = FALSE;
9597 if (StopClockTimer() && appData.clockMode) {
9598 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9599 if (WhiteOnMove(forwardMostMove)) {
9600 blackTimeRemaining -= lastTickLength;
9602 whiteTimeRemaining -= lastTickLength;
9604 flagged = CheckFlags();
9608 if (flagged || !appData.clockMode) return;
9611 case MachinePlaysBlack:
9612 case MachinePlaysWhite:
9613 case BeginningOfGame:
9614 if (pausing) return;
9618 case PlayFromGameFile:
9627 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9628 whiteTimeRemaining : blackTimeRemaining);
9629 StartClockTimer(intendedTickLength);
9633 /* Stop both clocks */
9637 long lastTickLength;
9640 if (!StopClockTimer()) return;
9641 if (!appData.clockMode) return;
9645 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9646 if (WhiteOnMove(forwardMostMove)) {
9647 whiteTimeRemaining -= lastTickLength;
9648 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9650 blackTimeRemaining -= lastTickLength;
9651 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9656 /* Start clock of player on move. Time may have been reset, so
9657 if clock is already running, stop and restart it. */
9661 (void) StopClockTimer(); /* in case it was running already */
9662 DisplayBothClocks();
9663 if (CheckFlags()) return;
9665 if (!appData.clockMode) return;
9666 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9668 GetTimeMark(&tickStartTM);
9669 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9670 whiteTimeRemaining : blackTimeRemaining);
9671 StartClockTimer(intendedTickLength);
9678 long second, minute, hour, day;
9680 static char buf[32];
9682 if (ms > 0 && ms <= 9900) {
9683 /* convert milliseconds to tenths, rounding up */
9684 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9686 sprintf(buf, " %03.1f ", tenths/10.0);
9690 /* convert milliseconds to seconds, rounding up */
9691 /* use floating point to avoid strangeness of integer division
9692 with negative dividends on many machines */
9693 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9700 day = second / (60 * 60 * 24);
9701 second = second % (60 * 60 * 24);
9702 hour = second / (60 * 60);
9703 second = second % (60 * 60);
9704 minute = second / 60;
9705 second = second % 60;
9708 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9709 sign, day, hour, minute, second);
9711 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9713 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9720 * This is necessary because some C libraries aren't ANSI C compliant yet.
9723 StrStr(string, match)
9724 char *string, *match;
9728 length = strlen(match);
9730 for (i = strlen(string) - length; i >= 0; i--, string++)
9731 if (!strncmp(match, string, length))
9738 StrCaseStr(string, match)
9739 char *string, *match;
9743 length = strlen(match);
9745 for (i = strlen(string) - length; i >= 0; i--, string++) {
9746 for (j = 0; j < length; j++) {
9747 if (ToLower(match[j]) != ToLower(string[j]))
9750 if (j == length) return string;
9764 c1 = ToLower(*s1++);
9765 c2 = ToLower(*s2++);
9766 if (c1 > c2) return 1;
9767 if (c1 < c2) return -1;
9768 if (c1 == NULLCHAR) return 0;
9777 return isupper(c) ? tolower(c) : c;
9785 return islower(c) ? toupper(c) : c;
9787 #endif /* !_amigados */
9795 if ((ret = (char *) malloc(strlen(s) + 1))) {
9802 StrSavePtr(s, savePtr)
9808 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9809 strcpy(*savePtr, s);
9821 clock = time((time_t *)NULL);
9822 tm = localtime(&clock);
9823 sprintf(buf, "%04d.%02d.%02d",
9824 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9825 return StrSave(buf);
9833 int i, j, fromX, fromY, toX, toY;
9839 whiteToPlay = (gameMode == EditPosition) ?
9840 !blackPlaysFirst : (move % 2 == 0);
9843 /* Piece placement data */
9844 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9846 for (j = 0; j < BOARD_SIZE; j++) {
9847 if (boards[move][i][j] == EmptySquare) {
9850 if (emptycount > 0) {
9851 *p++ = '0' + emptycount;
9854 *p++ = PieceToChar(boards[move][i][j]);
9857 if (emptycount > 0) {
9858 *p++ = '0' + emptycount;
9866 *p++ = whiteToPlay ? 'w' : 'b';
9869 /* !!We don't keep track of castling availability, so fake it */
9871 if (boards[move][0][4] == WhiteKing) {
9872 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9873 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9875 if (boards[move][7][4] == BlackKing) {
9876 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9877 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9879 if (q == p) *p++ = '-';
9882 /* En passant target square */
9883 if (move > backwardMostMove) {
9884 fromX = moveList[move - 1][0] - 'a';
9885 fromY = moveList[move - 1][1] - '1';
9886 toX = moveList[move - 1][2] - 'a';
9887 toY = moveList[move - 1][3] - '1';
9888 if (fromY == (whiteToPlay ? 6 : 1) &&
9889 toY == (whiteToPlay ? 4 : 3) &&
9890 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9892 /* 2-square pawn move just happened */
9894 *p++ = whiteToPlay ? '6' : '3';
9902 /* !!We don't keep track of halfmove clock for 50-move rule */
9906 /* Fullmove number */
9907 sprintf(p, "%d", (move / 2) + 1);
9909 return StrSave(buf);
9913 ParseFEN(board, blackPlaysFirst, fen)
9915 int *blackPlaysFirst;
9924 /* Piece placement data */
9925 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9928 if (*p == '/' || *p == ' ') {
9930 emptycount = BOARD_SIZE - j;
9931 while (emptycount--) board[i][j++] = EmptySquare;
9933 } else if (isdigit(*p)) {
9934 emptycount = *p++ - '0';
9935 if (j + emptycount > BOARD_SIZE) return FALSE;
9936 while (emptycount--) board[i][j++] = EmptySquare;
9937 } else if (isalpha(*p)) {
9938 if (j >= BOARD_SIZE) return FALSE;
9939 board[i][j++] = CharToPiece(*p++);
9945 while (*p == '/' || *p == ' ') p++;
9950 *blackPlaysFirst = FALSE;
9953 *blackPlaysFirst = TRUE;
9958 /* !!We ignore the rest of the FEN notation */
9963 EditPositionPasteFEN(char *fen)
9966 Board initial_position;
9968 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
9969 DisplayError(_("Bad FEN position in clipboard"), 0);
9972 int savedBlackPlaysFirst = blackPlaysFirst;
9973 EditPositionEvent();
9974 blackPlaysFirst = savedBlackPlaysFirst;
9975 CopyBoard(boards[0], initial_position);
9977 DisplayBothClocks();
9978 DrawPosition(FALSE, boards[currentMove]);