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 u64 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));
215 double u64ToDouble P((u64 value));
218 extern void ConsoleCreate();
221 extern int tinyLayout, smallLayout;
222 static ChessProgramStats programStats;
224 /* States for ics_getting_history */
226 #define H_REQUESTED 1
227 #define H_GOT_REQ_HEADER 2
228 #define H_GOT_UNREQ_HEADER 3
229 #define H_GETTING_MOVES 4
230 #define H_GOT_UNWANTED_HEADER 5
232 /* whosays values for GameEnds */
239 /* Maximum number of games in a cmail message */
240 #define CMAIL_MAX_GAMES 20
242 /* Different types of move when calling RegisterMove */
244 #define CMAIL_RESIGN 1
246 #define CMAIL_ACCEPT 3
248 /* Different types of result to remember for each game */
249 #define CMAIL_NOT_RESULT 0
250 #define CMAIL_OLD_RESULT 1
251 #define CMAIL_NEW_RESULT 2
253 /* Telnet protocol constants */
263 /* Some compiler can't cast u64 to double
264 * This function do the job for us:
266 * We use the highest bit for cast, this only
267 * works if the highest bit is not
268 * in use (This should not happen)
270 * We used this for all compiler
273 u64ToDouble(u64 value)
276 u64 tmp = value & 0x7fffffffffffffff;
277 r = (double)(s64)tmp;
278 if (value & 0x8000000000000000)
279 r += 9.2233720368547758080e18; /* 2^63 */
283 /* Fake up flags for now, as we aren't keeping track of castling
288 int flags = F_ALL_CASTLE_OK;
289 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
290 switch (gameInfo.variant) {
292 case VariantGiveaway:
293 flags |= F_IGNORE_CHECK;
294 flags &= ~F_ALL_CASTLE_OK;
297 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
299 case VariantKriegspiel:
300 flags |= F_KRIEGSPIEL_CAPTURE;
302 case VariantNoCastle:
303 flags &= ~F_ALL_CASTLE_OK;
311 FILE *gameFileFP, *debugFP;
313 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
314 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
315 char thinkOutput1[MSG_SIZ*10];
317 ChessProgramState first, second;
319 /* premove variables */
322 int premoveFromX = 0;
323 int premoveFromY = 0;
324 int premovePromoChar = 0;
326 Boolean alarmSounded;
327 /* end premove variables */
329 char *ics_prefix = "$";
330 int ics_type = ICS_GENERIC;
332 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
333 int pauseExamForwardMostMove = 0;
334 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
335 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
336 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
337 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
338 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
339 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
340 int whiteFlag = FALSE, blackFlag = FALSE;
341 int userOfferedDraw = FALSE;
342 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
343 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
344 int cmailMoveType[CMAIL_MAX_GAMES];
345 long ics_clock_paused = 0;
346 ProcRef icsPR = NoProc, cmailPR = NoProc;
347 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
348 GameMode gameMode = BeginningOfGame;
349 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
350 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
351 char white_holding[64], black_holding[64];
352 TimeMark lastNodeCountTime;
353 long lastNodeCount=0;
354 int have_sent_ICS_logon = 0;
356 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
357 long timeRemaining[2][MAX_MOVES];
359 TimeMark programStartTime;
360 char ics_handle[MSG_SIZ];
361 int have_set_title = 0;
363 /* animateTraining preserves the state of appData.animate
364 * when Training mode is activated. This allows the
365 * response to be animated when appData.animate == TRUE and
366 * appData.animateDragging == TRUE.
368 Boolean animateTraining;
374 Board boards[MAX_MOVES];
375 Board initialPosition = {
376 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
377 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
378 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
379 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
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 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
386 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
387 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
388 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
389 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
390 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
391 BlackKing, BlackBishop, BlackKnight, BlackRook }
393 Board twoKingsPosition = {
394 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
395 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
396 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
397 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
398 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
399 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
400 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
401 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
402 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
403 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
404 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
405 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
406 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
407 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
408 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
409 BlackKing, BlackKing, BlackKnight, BlackRook }
413 /* Convert str to a rating. Checks for special cases of "----",
414 "++++", etc. Also strips ()'s */
416 string_to_rating(str)
419 while(*str && !isdigit(*str)) ++str;
421 return 0; /* One of the special "no rating" cases */
429 /* Init programStats */
430 programStats.movelist[0] = 0;
431 programStats.depth = 0;
432 programStats.nr_moves = 0;
433 programStats.moves_left = 0;
434 programStats.nodes = 0;
435 programStats.time = 100;
436 programStats.score = 0;
437 programStats.got_only_move = 0;
438 programStats.got_fail = 0;
439 programStats.line_is_book = 0;
445 int matched, min, sec;
447 GetTimeMark(&programStartTime);
450 programStats.ok_to_send = 1;
451 programStats.seen_stat = 0;
454 * Initialize game list
460 * Internet chess server status
462 if (appData.icsActive) {
463 appData.matchMode = FALSE;
464 appData.matchGames = 0;
466 appData.noChessProgram = !appData.zippyPlay;
468 appData.zippyPlay = FALSE;
469 appData.zippyTalk = FALSE;
470 appData.noChessProgram = TRUE;
472 if (*appData.icsHelper != NULLCHAR) {
473 appData.useTelnet = TRUE;
474 appData.telnetProgram = appData.icsHelper;
477 appData.zippyTalk = appData.zippyPlay = FALSE;
481 * Parse timeControl resource
483 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
484 appData.movesPerSession)) {
486 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
487 DisplayFatalError(buf, 0, 2);
491 * Parse searchTime resource
493 if (*appData.searchTime != NULLCHAR) {
494 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
496 searchTime = min * 60;
497 } else if (matched == 2) {
498 searchTime = min * 60 + sec;
501 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
502 DisplayFatalError(buf, 0, 2);
506 first.which = "first";
507 second.which = "second";
508 first.maybeThinking = second.maybeThinking = FALSE;
509 first.pr = second.pr = NoProc;
510 first.isr = second.isr = NULL;
511 first.sendTime = second.sendTime = 2;
512 first.sendDrawOffers = 1;
513 if (appData.firstPlaysBlack) {
514 first.twoMachinesColor = "black\n";
515 second.twoMachinesColor = "white\n";
517 first.twoMachinesColor = "white\n";
518 second.twoMachinesColor = "black\n";
520 first.program = appData.firstChessProgram;
521 second.program = appData.secondChessProgram;
522 first.host = appData.firstHost;
523 second.host = appData.secondHost;
524 first.dir = appData.firstDirectory;
525 second.dir = appData.secondDirectory;
526 first.other = &second;
527 second.other = &first;
528 first.initString = appData.initString;
529 second.initString = appData.secondInitString;
530 first.computerString = appData.firstComputerString;
531 second.computerString = appData.secondComputerString;
532 first.useSigint = second.useSigint = TRUE;
533 first.useSigterm = second.useSigterm = TRUE;
534 first.reuse = appData.reuseFirst;
535 second.reuse = appData.reuseSecond;
536 first.useSetboard = second.useSetboard = FALSE;
537 first.useSAN = second.useSAN = FALSE;
538 first.usePing = second.usePing = FALSE;
539 first.lastPing = second.lastPing = 0;
540 first.lastPong = second.lastPong = 0;
541 first.usePlayother = second.usePlayother = FALSE;
542 first.useColors = second.useColors = TRUE;
543 first.useUsermove = second.useUsermove = FALSE;
544 first.sendICS = second.sendICS = FALSE;
545 first.sendName = second.sendName = appData.icsActive;
546 first.sdKludge = second.sdKludge = FALSE;
547 first.stKludge = second.stKludge = FALSE;
548 TidyProgramName(first.program, first.host, first.tidy);
549 TidyProgramName(second.program, second.host, second.tidy);
550 first.matchWins = second.matchWins = 0;
551 strcpy(first.variants, appData.variant);
552 strcpy(second.variants, appData.variant);
553 first.analysisSupport = second.analysisSupport = 2; /* detect */
554 first.analyzing = second.analyzing = FALSE;
555 first.initDone = second.initDone = FALSE;
557 if (appData.firstProtocolVersion > PROTOVER ||
558 appData.firstProtocolVersion < 1) {
560 sprintf(buf, _("protocol version %d not supported"),
561 appData.firstProtocolVersion);
562 DisplayFatalError(buf, 0, 2);
564 first.protocolVersion = appData.firstProtocolVersion;
567 if (appData.secondProtocolVersion > PROTOVER ||
568 appData.secondProtocolVersion < 1) {
570 sprintf(buf, _("protocol version %d not supported"),
571 appData.secondProtocolVersion);
572 DisplayFatalError(buf, 0, 2);
574 second.protocolVersion = appData.secondProtocolVersion;
577 if (appData.icsActive) {
578 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
579 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
580 appData.clockMode = FALSE;
581 first.sendTime = second.sendTime = 0;
585 /* Override some settings from environment variables, for backward
586 compatibility. Unfortunately it's not feasible to have the env
587 vars just set defaults, at least in xboard. Ugh.
589 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
594 if (appData.noChessProgram) {
595 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
596 + strlen(PATCHLEVEL));
597 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
601 while (*q != ' ' && *q != NULLCHAR) q++;
603 while (p > first.program && *(p-1) != '/') p--;
604 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
605 + strlen(PATCHLEVEL) + (q - p));
606 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
607 strncat(programVersion, p, q - p);
610 if (!appData.icsActive) {
612 /* Check for variants that are supported only in ICS mode,
613 or not at all. Some that are accepted here nevertheless
614 have bugs; see comments below.
616 VariantClass variant = StringToVariant(appData.variant);
618 case VariantBughouse: /* need four players and two boards */
619 case VariantKriegspiel: /* need to hide pieces and move details */
620 case VariantFischeRandom: /* castling doesn't work, shuffle not done */
621 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
622 DisplayFatalError(buf, 0, 2);
626 case VariantLoadable:
636 sprintf(buf, _("Unknown variant name %s"), appData.variant);
637 DisplayFatalError(buf, 0, 2);
640 case VariantNormal: /* definitely works! */
641 case VariantWildCastle: /* pieces not automatically shuffled */
642 case VariantNoCastle: /* pieces not automatically shuffled */
643 case VariantCrazyhouse: /* holdings not shown,
644 offboard interposition not understood */
645 case VariantLosers: /* should work except for win condition,
646 and doesn't know captures are mandatory */
647 case VariantSuicide: /* should work except for win condition,
648 and doesn't know captures are mandatory */
649 case VariantGiveaway: /* should work except for win condition,
650 and doesn't know captures are mandatory */
651 case VariantTwoKings: /* should work */
652 case VariantAtomic: /* should work except for win condition */
653 case Variant3Check: /* should work except for win condition */
654 case VariantShatranj: /* might work if TestLegality is off */
661 ParseTimeControl(tc, ti, mps)
666 int matched, min, sec;
668 matched = sscanf(tc, "%d:%d", &min, &sec);
670 timeControl = min * 60 * 1000;
671 } else if (matched == 2) {
672 timeControl = (min * 60 + sec) * 1000;
678 timeIncrement = ti * 1000; /* convert to ms */
682 movesPerSession = mps;
690 if (appData.debugMode) {
691 fprintf(debugFP, "%s\n", programVersion);
694 if (appData.matchGames > 0) {
695 appData.matchMode = TRUE;
696 } else if (appData.matchMode) {
697 appData.matchGames = 1;
700 if (appData.noChessProgram || first.protocolVersion == 1) {
703 /* kludge: allow timeout for initial "feature" commands */
705 DisplayMessage("", "Starting chess program");
706 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
711 InitBackEnd3 P((void))
713 GameMode initialMode;
717 InitChessProgram(&first);
720 /* Make a console window if needed */
721 if (appData.icsActive) ConsoleCreate();
724 if (appData.icsActive) {
727 if (*appData.icsCommPort != NULLCHAR) {
728 sprintf(buf, _("Could not open comm port %s"),
729 appData.icsCommPort);
731 sprintf(buf, _("Could not connect to host %s, port %s"),
732 appData.icsHost, appData.icsPort);
734 DisplayFatalError(buf, err, 1);
739 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
741 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
742 } else if (appData.noChessProgram) {
748 if (*appData.cmailGameName != NULLCHAR) {
750 OpenLoopback(&cmailPR);
752 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
756 DisplayMessage("", "");
757 if (StrCaseCmp(appData.initialMode, "") == 0) {
758 initialMode = BeginningOfGame;
759 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
760 initialMode = TwoMachinesPlay;
761 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
762 initialMode = AnalyzeFile;
763 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
764 initialMode = AnalyzeMode;
765 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
766 initialMode = MachinePlaysWhite;
767 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
768 initialMode = MachinePlaysBlack;
769 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
770 initialMode = EditGame;
771 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
772 initialMode = EditPosition;
773 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
774 initialMode = Training;
776 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
777 DisplayFatalError(buf, 0, 2);
781 if (appData.matchMode) {
782 /* Set up machine vs. machine match */
783 if (appData.noChessProgram) {
784 DisplayFatalError(_("Can't have a match with no chess programs"),
790 if (*appData.loadGameFile != NULLCHAR) {
791 if (!LoadGameFromFile(appData.loadGameFile,
792 appData.loadGameIndex,
793 appData.loadGameFile, FALSE)) {
794 DisplayFatalError(_("Bad game file"), 0, 1);
797 } else if (*appData.loadPositionFile != NULLCHAR) {
798 if (!LoadPositionFromFile(appData.loadPositionFile,
799 appData.loadPositionIndex,
800 appData.loadPositionFile)) {
801 DisplayFatalError(_("Bad position file"), 0, 1);
806 } else if (*appData.cmailGameName != NULLCHAR) {
807 /* Set up cmail mode */
808 ReloadCmailMsgEvent(TRUE);
810 /* Set up other modes */
811 if (initialMode == AnalyzeFile) {
812 if (*appData.loadGameFile == NULLCHAR) {
813 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
817 if (*appData.loadGameFile != NULLCHAR) {
818 (void) LoadGameFromFile(appData.loadGameFile,
819 appData.loadGameIndex,
820 appData.loadGameFile, TRUE);
821 } else if (*appData.loadPositionFile != NULLCHAR) {
822 (void) LoadPositionFromFile(appData.loadPositionFile,
823 appData.loadPositionIndex,
824 appData.loadPositionFile);
826 if (initialMode == AnalyzeMode) {
827 if (appData.noChessProgram) {
828 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
831 if (appData.icsActive) {
832 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
836 } else if (initialMode == AnalyzeFile) {
837 ShowThinkingEvent(TRUE);
839 AnalysisPeriodicEvent(1);
840 } else if (initialMode == MachinePlaysWhite) {
841 if (appData.noChessProgram) {
842 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
846 if (appData.icsActive) {
847 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
852 } else if (initialMode == MachinePlaysBlack) {
853 if (appData.noChessProgram) {
854 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
858 if (appData.icsActive) {
859 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
864 } else if (initialMode == TwoMachinesPlay) {
865 if (appData.noChessProgram) {
866 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
870 if (appData.icsActive) {
871 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
876 } else if (initialMode == EditGame) {
878 } else if (initialMode == EditPosition) {
880 } else if (initialMode == Training) {
881 if (*appData.loadGameFile == NULLCHAR) {
882 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
891 * Establish will establish a contact to a remote host.port.
892 * Sets icsPR to a ProcRef for a process (or pseudo-process)
893 * used to talk to the host.
894 * Returns 0 if okay, error code if not.
901 if (*appData.icsCommPort != NULLCHAR) {
902 /* Talk to the host through a serial comm port */
903 return OpenCommPort(appData.icsCommPort, &icsPR);
905 } else if (*appData.gateway != NULLCHAR) {
906 if (*appData.remoteShell == NULLCHAR) {
907 /* Use the rcmd protocol to run telnet program on a gateway host */
908 sprintf(buf, "%s %s %s",
909 appData.telnetProgram, appData.icsHost, appData.icsPort);
910 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
913 /* Use the rsh program to run telnet program on a gateway host */
914 if (*appData.remoteUser == NULLCHAR) {
915 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
916 appData.gateway, appData.telnetProgram,
917 appData.icsHost, appData.icsPort);
919 sprintf(buf, "%s %s -l %s %s %s %s",
920 appData.remoteShell, appData.gateway,
921 appData.remoteUser, appData.telnetProgram,
922 appData.icsHost, appData.icsPort);
924 return StartChildProcess(buf, "", &icsPR);
927 } else if (appData.useTelnet) {
928 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
931 /* TCP socket interface differs somewhat between
932 Unix and NT; handle details in the front end.
934 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
939 show_bytes(fp, buf, count)
945 if (*buf < 040 || *(unsigned char *) buf > 0177) {
946 fprintf(fp, "\\%03o", *buf & 0xff);
955 /* Returns an errno value */
957 OutputMaybeTelnet(pr, message, count, outError)
963 char buf[8192], *p, *q, *buflim;
964 int left, newcount, outcount;
966 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
967 *appData.gateway != NULLCHAR) {
968 if (appData.debugMode) {
969 fprintf(debugFP, ">ICS: ");
970 show_bytes(debugFP, message, count);
971 fprintf(debugFP, "\n");
973 return OutputToProcess(pr, message, count, outError);
976 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
983 if (appData.debugMode) {
984 fprintf(debugFP, ">ICS: ");
985 show_bytes(debugFP, buf, newcount);
986 fprintf(debugFP, "\n");
988 outcount = OutputToProcess(pr, buf, newcount, outError);
989 if (outcount < newcount) return -1; /* to be sure */
996 } else if (((unsigned char) *p) == TN_IAC) {
997 *q++ = (char) TN_IAC;
1004 if (appData.debugMode) {
1005 fprintf(debugFP, ">ICS: ");
1006 show_bytes(debugFP, buf, newcount);
1007 fprintf(debugFP, "\n");
1009 outcount = OutputToProcess(pr, buf, newcount, outError);
1010 if (outcount < newcount) return -1; /* to be sure */
1015 read_from_player(isr, closure, message, count, error)
1022 int outError, outCount;
1023 static int gotEof = 0;
1025 /* Pass data read from player on to ICS */
1028 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1029 if (outCount < count) {
1030 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1032 } else if (count < 0) {
1033 RemoveInputSource(isr);
1034 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1035 } else if (gotEof++ > 0) {
1036 RemoveInputSource(isr);
1037 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1045 int count, outCount, outError;
1047 if (icsPR == NULL) return;
1050 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1051 if (outCount < count) {
1052 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1056 /* This is used for sending logon scripts to the ICS. Sending
1057 without a delay causes problems when using timestamp on ICC
1058 (at least on my machine). */
1060 SendToICSDelayed(s,msdelay)
1064 int count, outCount, outError;
1066 if (icsPR == NULL) return;
1069 if (appData.debugMode) {
1070 fprintf(debugFP, ">ICS: ");
1071 show_bytes(debugFP, s, count);
1072 fprintf(debugFP, "\n");
1074 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1076 if (outCount < count) {
1077 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1082 /* Remove all highlighting escape sequences in s
1083 Also deletes any suffix starting with '('
1086 StripHighlightAndTitle(s)
1089 static char retbuf[MSG_SIZ];
1092 while (*s != NULLCHAR) {
1093 while (*s == '\033') {
1094 while (*s != NULLCHAR && !isalpha(*s)) s++;
1095 if (*s != NULLCHAR) s++;
1097 while (*s != NULLCHAR && *s != '\033') {
1098 if (*s == '(' || *s == '[') {
1109 /* Remove all highlighting escape sequences in s */
1114 static char retbuf[MSG_SIZ];
1117 while (*s != NULLCHAR) {
1118 while (*s == '\033') {
1119 while (*s != NULLCHAR && !isalpha(*s)) s++;
1120 if (*s != NULLCHAR) s++;
1122 while (*s != NULLCHAR && *s != '\033') {
1130 char *variantNames[] = VARIANT_NAMES;
1135 return variantNames[v];
1139 /* Identify a variant from the strings the chess servers use or the
1140 PGN Variant tag names we use. */
1147 VariantClass v = VariantNormal;
1148 int i, found = FALSE;
1153 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1154 if (StrCaseStr(e, variantNames[i])) {
1155 v = (VariantClass) i;
1162 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1163 || StrCaseStr(e, "wild/fr")) {
1164 v = VariantFischeRandom;
1165 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1166 (i = 1, p = StrCaseStr(e, "w"))) {
1168 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1175 case 0: /* FICS only, actually */
1177 /* Castling legal even if K starts on d-file */
1178 v = VariantWildCastle;
1183 /* Castling illegal even if K & R happen to start in
1184 normal positions. */
1185 v = VariantNoCastle;
1198 /* Castling legal iff K & R start in normal positions */
1204 /* Special wilds for position setup; unclear what to do here */
1205 v = VariantLoadable;
1208 /* Bizarre ICC game */
1209 v = VariantTwoKings;
1212 v = VariantKriegspiel;
1218 v = VariantFischeRandom;
1221 v = VariantCrazyhouse;
1224 v = VariantBughouse;
1230 /* Not quite the same as FICS suicide! */
1231 v = VariantGiveaway;
1237 v = VariantShatranj;
1240 /* Temporary names for future ICC types. The name *will* change in
1241 the next xboard/WinBoard release after ICC defines it. */
1268 /* Found "wild" or "w" in the string but no number;
1269 must assume it's normal chess. */
1273 sprintf(buf, "Unknown wild type %d", wnum);
1274 DisplayError(buf, 0);
1280 if (appData.debugMode) {
1281 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1282 e, wnum, VariantName(v));
1287 static int leftover_start = 0, leftover_len = 0;
1288 char star_match[STAR_MATCH_N][MSG_SIZ];
1290 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1291 advance *index beyond it, and set leftover_start to the new value of
1292 *index; else return FALSE. If pattern contains the character '*', it
1293 matches any sequence of characters not containing '\r', '\n', or the
1294 character following the '*' (if any), and the matched sequence(s) are
1295 copied into star_match.
1298 looking_at(buf, index, pattern)
1303 char *bufp = &buf[*index], *patternp = pattern;
1305 char *matchp = star_match[0];
1308 if (*patternp == NULLCHAR) {
1309 *index = leftover_start = bufp - buf;
1313 if (*bufp == NULLCHAR) return FALSE;
1314 if (*patternp == '*') {
1315 if (*bufp == *(patternp + 1)) {
1317 matchp = star_match[++star_count];
1321 } else if (*bufp == '\n' || *bufp == '\r') {
1323 if (*patternp == NULLCHAR)
1328 *matchp++ = *bufp++;
1332 if (*patternp != *bufp) return FALSE;
1339 SendToPlayer(data, length)
1343 int error, outCount;
1344 outCount = OutputToProcess(NoProc, data, length, &error);
1345 if (outCount < length) {
1346 DisplayFatalError(_("Error writing to display"), error, 1);
1351 PackHolding(packed, holding)
1363 switch (runlength) {
1374 sprintf(q, "%d", runlength);
1386 /* Telnet protocol requests from the front end */
1388 TelnetRequest(ddww, option)
1389 unsigned char ddww, option;
1391 unsigned char msg[3];
1392 int outCount, outError;
1394 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1396 if (appData.debugMode) {
1397 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1413 sprintf(buf1, "%d", ddww);
1422 sprintf(buf2, "%d", option);
1425 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1430 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1432 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1439 if (!appData.icsActive) return;
1440 TelnetRequest(TN_DO, TN_ECHO);
1446 if (!appData.icsActive) return;
1447 TelnetRequest(TN_DONT, TN_ECHO);
1450 static int loggedOn = FALSE;
1452 /*-- Game start info cache: --*/
1454 char gs_kind[MSG_SIZ];
1455 static char player1Name[128] = "";
1456 static char player2Name[128] = "";
1457 static int player1Rating = -1;
1458 static int player2Rating = -1;
1459 /*----------------------------*/
1462 read_from_ics(isr, closure, data, count, error)
1469 #define BUF_SIZE 8192
1470 #define STARTED_NONE 0
1471 #define STARTED_MOVES 1
1472 #define STARTED_BOARD 2
1473 #define STARTED_OBSERVE 3
1474 #define STARTED_HOLDINGS 4
1475 #define STARTED_CHATTER 5
1476 #define STARTED_COMMENT 6
1477 #define STARTED_MOVES_NOHIDE 7
1479 static int started = STARTED_NONE;
1480 static char parse[20000];
1481 static int parse_pos = 0;
1482 static char buf[BUF_SIZE + 1];
1483 static int firstTime = TRUE, intfSet = FALSE;
1484 static ColorClass curColor = ColorNormal;
1485 static ColorClass prevColor = ColorNormal;
1486 static int savingComment = FALSE;
1496 if (appData.debugMode) {
1498 fprintf(debugFP, "<ICS: ");
1499 show_bytes(debugFP, data, count);
1500 fprintf(debugFP, "\n");
1506 /* If last read ended with a partial line that we couldn't parse,
1507 prepend it to the new read and try again. */
1508 if (leftover_len > 0) {
1509 for (i=0; i<leftover_len; i++)
1510 buf[i] = buf[leftover_start + i];
1513 /* Copy in new characters, removing nulls and \r's */
1514 buf_len = leftover_len;
1515 for (i = 0; i < count; i++) {
1516 if (data[i] != NULLCHAR && data[i] != '\r')
1517 buf[buf_len++] = data[i];
1520 buf[buf_len] = NULLCHAR;
1521 next_out = leftover_len;
1525 while (i < buf_len) {
1526 /* Deal with part of the TELNET option negotiation
1527 protocol. We refuse to do anything beyond the
1528 defaults, except that we allow the WILL ECHO option,
1529 which ICS uses to turn off password echoing when we are
1530 directly connected to it. We reject this option
1531 if localLineEditing mode is on (always on in xboard)
1532 and we are talking to port 23, which might be a real
1533 telnet server that will try to keep WILL ECHO on permanently.
1535 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1536 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1537 unsigned char option;
1539 switch ((unsigned char) buf[++i]) {
1541 if (appData.debugMode)
1542 fprintf(debugFP, "\n<WILL ");
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;
1550 if (appData.localLineEditing &&
1551 atoi(appData.icsPort) == TN_PORT) {
1552 TelnetRequest(TN_DONT, TN_ECHO);
1555 TelnetRequest(TN_DO, TN_ECHO);
1556 remoteEchoOption = TRUE;
1560 if (appData.debugMode)
1561 fprintf(debugFP, "%d ", option);
1562 /* Whatever this is, we don't want it. */
1563 TelnetRequest(TN_DONT, option);
1568 if (appData.debugMode)
1569 fprintf(debugFP, "\n<WONT ");
1570 switch (option = (unsigned char) buf[++i]) {
1572 if (appData.debugMode)
1573 fprintf(debugFP, "ECHO ");
1574 /* Reply only if this is a change, according
1575 to the protocol rules. */
1576 if (!remoteEchoOption) break;
1578 TelnetRequest(TN_DONT, TN_ECHO);
1579 remoteEchoOption = FALSE;
1582 if (appData.debugMode)
1583 fprintf(debugFP, "%d ", (unsigned char) option);
1584 /* Whatever this is, it must already be turned
1585 off, because we never agree to turn on
1586 anything non-default, so according to the
1587 protocol rules, we don't reply. */
1592 if (appData.debugMode)
1593 fprintf(debugFP, "\n<DO ");
1594 switch (option = (unsigned char) buf[++i]) {
1596 /* Whatever this is, we refuse to do it. */
1597 if (appData.debugMode)
1598 fprintf(debugFP, "%d ", option);
1599 TelnetRequest(TN_WONT, option);
1604 if (appData.debugMode)
1605 fprintf(debugFP, "\n<DONT ");
1606 switch (option = (unsigned char) buf[++i]) {
1608 if (appData.debugMode)
1609 fprintf(debugFP, "%d ", option);
1610 /* Whatever this is, we are already not doing
1611 it, because we never agree to do anything
1612 non-default, so according to the protocol
1613 rules, we don't reply. */
1618 if (appData.debugMode)
1619 fprintf(debugFP, "\n<IAC ");
1620 /* Doubled IAC; pass it through */
1624 if (appData.debugMode)
1625 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1626 /* Drop all other telnet commands on the floor */
1629 if (oldi > next_out)
1630 SendToPlayer(&buf[next_out], oldi - next_out);
1636 /* OK, this at least will *usually* work */
1637 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1641 if (loggedOn && !intfSet) {
1642 if (ics_type == ICS_ICC) {
1644 "/set-quietly interface %s\n/set-quietly style 12\n",
1647 } else if (ics_type == ICS_CHESSNET) {
1648 sprintf(str, "/style 12\n");
1650 strcpy(str, "alias $ @\n$set interface ");
1651 strcat(str, programVersion);
1652 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1654 strcat(str, "$iset nohighlight 1\n");
1656 strcat(str, "$iset lock 1\n$style 12\n");
1662 if (started == STARTED_COMMENT) {
1663 /* Accumulate characters in comment */
1664 parse[parse_pos++] = buf[i];
1665 if (buf[i] == '\n') {
1666 parse[parse_pos] = NULLCHAR;
1667 AppendComment(forwardMostMove, StripHighlight(parse));
1668 started = STARTED_NONE;
1670 /* Don't match patterns against characters in chatter */
1675 if (started == STARTED_CHATTER) {
1676 if (buf[i] != '\n') {
1677 /* Don't match patterns against characters in chatter */
1681 started = STARTED_NONE;
1684 /* Kludge to deal with rcmd protocol */
1685 if (firstTime && looking_at(buf, &i, "\001*")) {
1686 DisplayFatalError(&buf[1], 0, 1);
1692 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1695 if (appData.debugMode)
1696 fprintf(debugFP, "ics_type %d\n", ics_type);
1699 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1700 ics_type = ICS_FICS;
1702 if (appData.debugMode)
1703 fprintf(debugFP, "ics_type %d\n", ics_type);
1706 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1707 ics_type = ICS_CHESSNET;
1709 if (appData.debugMode)
1710 fprintf(debugFP, "ics_type %d\n", ics_type);
1715 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1716 looking_at(buf, &i, "Logging you in as \"*\"") ||
1717 looking_at(buf, &i, "will be \"*\""))) {
1718 strcpy(ics_handle, star_match[0]);
1722 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1724 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1725 DisplayIcsInteractionTitle(buf);
1726 have_set_title = TRUE;
1729 /* skip finger notes */
1730 if (started == STARTED_NONE &&
1731 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1732 (buf[i] == '1' && buf[i+1] == '0')) &&
1733 buf[i+2] == ':' && buf[i+3] == ' ') {
1734 started = STARTED_CHATTER;
1739 /* skip formula vars */
1740 if (started == STARTED_NONE &&
1741 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1742 started = STARTED_CHATTER;
1748 if (appData.zippyTalk || appData.zippyPlay) {
1749 /* Backup address for color zippy lines */
1753 if (loggedOn == TRUE)
1754 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
1755 (appData.zippyPlay && ZippyMatch(buf, &backup)));
1757 if (ZippyControl(buf, &backup) ||
1758 ZippyConverse(buf, &backup) ||
1759 (appData.zippyPlay && ZippyMatch(buf, &backup))) {
1761 if (!appData.colorize) continue;
1766 if (/* Don't color "message" or "messages" output */
1767 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1768 looking_at(buf, &i, "*. * at *:*: ") ||
1769 looking_at(buf, &i, "--* (*:*): ") ||
1770 /* Regular tells and says */
1771 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1772 looking_at(buf, &i, "* (your partner) tells you: ") ||
1773 looking_at(buf, &i, "* says: ") ||
1774 /* Message notifications (same color as tells) */
1775 looking_at(buf, &i, "* has left a message ") ||
1776 looking_at(buf, &i, "* just sent you a message:\n") ||
1777 /* Whispers and kibitzes */
1778 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1779 looking_at(buf, &i, "* kibitzes: ") ||
1781 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1783 if (tkind == 1 && strchr(star_match[0], ':')) {
1784 /* Avoid "tells you:" spoofs in channels */
1787 if (star_match[0][0] == NULLCHAR ||
1788 strchr(star_match[0], ' ') ||
1789 (tkind == 3 && strchr(star_match[1], ' '))) {
1790 /* Reject bogus matches */
1793 if (appData.colorize) {
1794 if (oldi > next_out) {
1795 SendToPlayer(&buf[next_out], oldi - next_out);
1800 Colorize(ColorTell, FALSE);
1801 curColor = ColorTell;
1804 Colorize(ColorKibitz, FALSE);
1805 curColor = ColorKibitz;
1808 p = strrchr(star_match[1], '(');
1815 Colorize(ColorChannel1, FALSE);
1816 curColor = ColorChannel1;
1818 Colorize(ColorChannel, FALSE);
1819 curColor = ColorChannel;
1823 curColor = ColorNormal;
1827 if (started == STARTED_NONE && appData.autoComment &&
1828 (gameMode == IcsObserving ||
1829 gameMode == IcsPlayingWhite ||
1830 gameMode == IcsPlayingBlack)) {
1831 parse_pos = i - oldi;
1832 memcpy(parse, &buf[oldi], parse_pos);
1833 parse[parse_pos] = NULLCHAR;
1834 started = STARTED_COMMENT;
1835 savingComment = TRUE;
1837 started = STARTED_CHATTER;
1838 savingComment = FALSE;
1845 if (looking_at(buf, &i, "* s-shouts: ") ||
1846 looking_at(buf, &i, "* c-shouts: ")) {
1847 if (appData.colorize) {
1848 if (oldi > next_out) {
1849 SendToPlayer(&buf[next_out], oldi - next_out);
1852 Colorize(ColorSShout, FALSE);
1853 curColor = ColorSShout;
1856 started = STARTED_CHATTER;
1860 if (looking_at(buf, &i, "--->")) {
1865 if (looking_at(buf, &i, "* shouts: ") ||
1866 looking_at(buf, &i, "--> ")) {
1867 if (appData.colorize) {
1868 if (oldi > next_out) {
1869 SendToPlayer(&buf[next_out], oldi - next_out);
1872 Colorize(ColorShout, FALSE);
1873 curColor = ColorShout;
1876 started = STARTED_CHATTER;
1880 if (looking_at( buf, &i, "Challenge:")) {
1881 if (appData.colorize) {
1882 if (oldi > next_out) {
1883 SendToPlayer(&buf[next_out], oldi - next_out);
1886 Colorize(ColorChallenge, FALSE);
1887 curColor = ColorChallenge;
1893 if (looking_at(buf, &i, "* offers you") ||
1894 looking_at(buf, &i, "* offers to be") ||
1895 looking_at(buf, &i, "* would like to") ||
1896 looking_at(buf, &i, "* requests to") ||
1897 looking_at(buf, &i, "Your opponent offers") ||
1898 looking_at(buf, &i, "Your opponent requests")) {
1900 if (appData.colorize) {
1901 if (oldi > next_out) {
1902 SendToPlayer(&buf[next_out], oldi - next_out);
1905 Colorize(ColorRequest, FALSE);
1906 curColor = ColorRequest;
1911 if (looking_at(buf, &i, "* (*) seeking")) {
1912 if (appData.colorize) {
1913 if (oldi > next_out) {
1914 SendToPlayer(&buf[next_out], oldi - next_out);
1917 Colorize(ColorSeek, FALSE);
1918 curColor = ColorSeek;
1923 if (looking_at(buf, &i, "\\ ")) {
1924 if (prevColor != ColorNormal) {
1925 if (oldi > next_out) {
1926 SendToPlayer(&buf[next_out], oldi - next_out);
1929 Colorize(prevColor, TRUE);
1930 curColor = prevColor;
1932 if (savingComment) {
1933 parse_pos = i - oldi;
1934 memcpy(parse, &buf[oldi], parse_pos);
1935 parse[parse_pos] = NULLCHAR;
1936 started = STARTED_COMMENT;
1938 started = STARTED_CHATTER;
1943 if (looking_at(buf, &i, "Black Strength :") ||
1944 looking_at(buf, &i, "<<< style 10 board >>>") ||
1945 looking_at(buf, &i, "<10>") ||
1946 looking_at(buf, &i, "#@#")) {
1947 /* Wrong board style */
1949 SendToICS(ics_prefix);
1950 SendToICS("set style 12\n");
1951 SendToICS(ics_prefix);
1952 SendToICS("refresh\n");
1956 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1958 have_sent_ICS_logon = 1;
1962 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1963 (looking_at(buf, &i, "\n<12> ") ||
1964 looking_at(buf, &i, "<12> "))) {
1966 if (oldi > next_out) {
1967 SendToPlayer(&buf[next_out], oldi - next_out);
1970 started = STARTED_BOARD;
1975 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1976 looking_at(buf, &i, "<b1> ")) {
1977 if (oldi > next_out) {
1978 SendToPlayer(&buf[next_out], oldi - next_out);
1981 started = STARTED_HOLDINGS;
1986 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1988 /* Header for a move list -- first line */
1990 switch (ics_getting_history) {
1994 case BeginningOfGame:
1995 /* User typed "moves" or "oldmoves" while we
1996 were idle. Pretend we asked for these
1997 moves and soak them up so user can step
1998 through them and/or save them.
2001 gameMode = IcsObserving;
2004 ics_getting_history = H_GOT_UNREQ_HEADER;
2006 case EditGame: /*?*/
2007 case EditPosition: /*?*/
2008 /* Should above feature work in these modes too? */
2009 /* For now it doesn't */
2010 ics_getting_history = H_GOT_UNWANTED_HEADER;
2013 ics_getting_history = H_GOT_UNWANTED_HEADER;
2018 /* Is this the right one? */
2019 if (gameInfo.white && gameInfo.black &&
2020 strcmp(gameInfo.white, star_match[0]) == 0 &&
2021 strcmp(gameInfo.black, star_match[2]) == 0) {
2023 ics_getting_history = H_GOT_REQ_HEADER;
2026 case H_GOT_REQ_HEADER:
2027 case H_GOT_UNREQ_HEADER:
2028 case H_GOT_UNWANTED_HEADER:
2029 case H_GETTING_MOVES:
2030 /* Should not happen */
2031 DisplayError(_("Error gathering move list: two headers"), 0);
2032 ics_getting_history = H_FALSE;
2036 /* Save player ratings into gameInfo if needed */
2037 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2038 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2039 (gameInfo.whiteRating == -1 ||
2040 gameInfo.blackRating == -1)) {
2042 gameInfo.whiteRating = string_to_rating(star_match[1]);
2043 gameInfo.blackRating = string_to_rating(star_match[3]);
2044 if (appData.debugMode)
2045 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2046 gameInfo.whiteRating, gameInfo.blackRating);
2051 if (looking_at(buf, &i,
2052 "* * match, initial time: * minute*, increment: * second")) {
2053 /* Header for a move list -- second line */
2054 /* Initial board will follow if this is a wild game */
2056 if (gameInfo.event != NULL) free(gameInfo.event);
2057 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2058 gameInfo.event = StrSave(str);
2059 gameInfo.variant = StringToVariant(gameInfo.event);
2063 if (looking_at(buf, &i, "Move ")) {
2064 /* Beginning of a move list */
2065 switch (ics_getting_history) {
2067 /* Normally should not happen */
2068 /* Maybe user hit reset while we were parsing */
2071 /* Happens if we are ignoring a move list that is not
2072 * the one we just requested. Common if the user
2073 * tries to observe two games without turning off
2076 case H_GETTING_MOVES:
2077 /* Should not happen */
2078 DisplayError(_("Error gathering move list: nested"), 0);
2079 ics_getting_history = H_FALSE;
2081 case H_GOT_REQ_HEADER:
2082 ics_getting_history = H_GETTING_MOVES;
2083 started = STARTED_MOVES;
2085 if (oldi > next_out) {
2086 SendToPlayer(&buf[next_out], oldi - next_out);
2089 case H_GOT_UNREQ_HEADER:
2090 ics_getting_history = H_GETTING_MOVES;
2091 started = STARTED_MOVES_NOHIDE;
2094 case H_GOT_UNWANTED_HEADER:
2095 ics_getting_history = H_FALSE;
2101 if (looking_at(buf, &i, "% ") ||
2102 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2103 && looking_at(buf, &i, "}*"))) {
2104 savingComment = FALSE;
2107 case STARTED_MOVES_NOHIDE:
2108 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2109 parse[parse_pos + i - oldi] = NULLCHAR;
2110 ParseGameHistory(parse);
2112 if (appData.zippyPlay && first.initDone) {
2113 FeedMovesToProgram(&first, forwardMostMove);
2114 if (gameMode == IcsPlayingWhite) {
2115 if (WhiteOnMove(forwardMostMove)) {
2116 if (first.sendTime) {
2117 if (first.useColors) {
2118 SendToProgram("black\n", &first);
2120 SendTimeRemaining(&first, TRUE);
2122 if (first.useColors) {
2123 SendToProgram("white\ngo\n", &first);
2125 SendToProgram("go\n", &first);
2127 first.maybeThinking = TRUE;
2129 if (first.usePlayother) {
2130 if (first.sendTime) {
2131 SendTimeRemaining(&first, TRUE);
2133 SendToProgram("playother\n", &first);
2139 } else if (gameMode == IcsPlayingBlack) {
2140 if (!WhiteOnMove(forwardMostMove)) {
2141 if (first.sendTime) {
2142 if (first.useColors) {
2143 SendToProgram("white\n", &first);
2145 SendTimeRemaining(&first, FALSE);
2147 if (first.useColors) {
2148 SendToProgram("black\ngo\n", &first);
2150 SendToProgram("go\n", &first);
2152 first.maybeThinking = TRUE;
2154 if (first.usePlayother) {
2155 if (first.sendTime) {
2156 SendTimeRemaining(&first, FALSE);
2158 SendToProgram("playother\n", &first);
2167 if (gameMode == IcsObserving && ics_gamenum == -1) {
2168 /* Moves came from oldmoves or moves command
2169 while we weren't doing anything else.
2171 currentMove = forwardMostMove;
2172 ClearHighlights();/*!!could figure this out*/
2173 flipView = appData.flipView;
2174 DrawPosition(FALSE, boards[currentMove]);
2175 DisplayBothClocks();
2176 sprintf(str, "%s vs. %s",
2177 gameInfo.white, gameInfo.black);
2181 /* Moves were history of an active game */
2182 if (gameInfo.resultDetails != NULL) {
2183 free(gameInfo.resultDetails);
2184 gameInfo.resultDetails = NULL;
2187 HistorySet(parseList, backwardMostMove,
2188 forwardMostMove, currentMove-1);
2189 DisplayMove(currentMove - 1);
2190 if (started == STARTED_MOVES) next_out = i;
2191 started = STARTED_NONE;
2192 ics_getting_history = H_FALSE;
2195 case STARTED_OBSERVE:
2196 started = STARTED_NONE;
2197 SendToICS(ics_prefix);
2198 SendToICS("refresh\n");
2207 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2208 started == STARTED_HOLDINGS ||
2209 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2210 /* Accumulate characters in move list or board */
2211 parse[parse_pos++] = buf[i];
2214 /* Start of game messages. Mostly we detect start of game
2215 when the first board image arrives. On some versions
2216 of the ICS, though, we need to do a "refresh" after starting
2217 to observe in order to get the current board right away. */
2218 if (looking_at(buf, &i, "Adding game * to observation list")) {
2219 started = STARTED_OBSERVE;
2223 /* Handle auto-observe */
2224 if (appData.autoObserve &&
2225 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2226 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2228 /* Choose the player that was highlighted, if any. */
2229 if (star_match[0][0] == '\033' ||
2230 star_match[1][0] != '\033') {
2231 player = star_match[0];
2233 player = star_match[2];
2235 sprintf(str, "%sobserve %s\n",
2236 ics_prefix, StripHighlightAndTitle(player));
2239 /* Save ratings from notify string */
2240 strcpy(player1Name, star_match[0]);
2241 player1Rating = string_to_rating(star_match[1]);
2242 strcpy(player2Name, star_match[2]);
2243 player2Rating = string_to_rating(star_match[3]);
2245 if (appData.debugMode)
2247 "Ratings from 'Game notification:' %s %d, %s %d\n",
2248 player1Name, player1Rating,
2249 player2Name, player2Rating);
2254 /* Deal with automatic examine mode after a game,
2255 and with IcsObserving -> IcsExamining transition */
2256 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2257 looking_at(buf, &i, "has made you an examiner of game *")) {
2259 int gamenum = atoi(star_match[0]);
2260 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2261 gamenum == ics_gamenum) {
2262 /* We were already playing or observing this game;
2263 no need to refetch history */
2264 gameMode = IcsExamining;
2266 pauseExamForwardMostMove = forwardMostMove;
2267 } else if (currentMove < forwardMostMove) {
2268 ForwardInner(forwardMostMove);
2271 /* I don't think this case really can happen */
2272 SendToICS(ics_prefix);
2273 SendToICS("refresh\n");
2278 /* Error messages */
2279 if (ics_user_moved) {
2280 if (looking_at(buf, &i, "Illegal move") ||
2281 looking_at(buf, &i, "Not a legal move") ||
2282 looking_at(buf, &i, "Your king is in check") ||
2283 looking_at(buf, &i, "It isn't your turn") ||
2284 looking_at(buf, &i, "It is not your move")) {
2287 if (forwardMostMove > backwardMostMove) {
2288 currentMove = --forwardMostMove;
2289 DisplayMove(currentMove - 1); /* before DMError */
2290 DisplayMoveError("Illegal move (rejected by ICS)");
2291 DrawPosition(FALSE, boards[currentMove]);
2293 DisplayBothClocks();
2299 if (looking_at(buf, &i, "still have time") ||
2300 looking_at(buf, &i, "not out of time") ||
2301 looking_at(buf, &i, "either player is out of time") ||
2302 looking_at(buf, &i, "has timeseal; checking")) {
2303 /* We must have called his flag a little too soon */
2304 whiteFlag = blackFlag = FALSE;
2308 if (looking_at(buf, &i, "added * seconds to") ||
2309 looking_at(buf, &i, "seconds were added to")) {
2310 /* Update the clocks */
2311 SendToICS(ics_prefix);
2312 SendToICS("refresh\n");
2316 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2317 ics_clock_paused = TRUE;
2322 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2323 ics_clock_paused = FALSE;
2328 /* Grab player ratings from the Creating: message.
2329 Note we have to check for the special case when
2330 the ICS inserts things like [white] or [black]. */
2331 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2332 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2334 0 player 1 name (not necessarily white)
2336 2 empty, white, or black (IGNORED)
2337 3 player 2 name (not necessarily black)
2340 The names/ratings are sorted out when the game
2341 actually starts (below).
2343 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2344 player1Rating = string_to_rating(star_match[1]);
2345 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2346 player2Rating = string_to_rating(star_match[4]);
2348 if (appData.debugMode)
2350 "Ratings from 'Creating:' %s %d, %s %d\n",
2351 player1Name, player1Rating,
2352 player2Name, player2Rating);
2357 /* Improved generic start/end-of-game messages */
2358 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2359 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2360 /* If tkind == 0: */
2361 /* star_match[0] is the game number */
2362 /* [1] is the white player's name */
2363 /* [2] is the black player's name */
2364 /* For end-of-game: */
2365 /* [3] is the reason for the game end */
2366 /* [4] is a PGN end game-token, preceded by " " */
2367 /* For start-of-game: */
2368 /* [3] begins with "Creating" or "Continuing" */
2369 /* [4] is " *" or empty (don't care). */
2370 int gamenum = atoi(star_match[0]);
2371 char *whitename, *blackname, *why, *endtoken;
2372 ChessMove endtype = (ChessMove) 0;
2375 whitename = star_match[1];
2376 blackname = star_match[2];
2377 why = star_match[3];
2378 endtoken = star_match[4];
2380 whitename = star_match[1];
2381 blackname = star_match[3];
2382 why = star_match[5];
2383 endtoken = star_match[6];
2386 /* Game start messages */
2387 if (strncmp(why, "Creating ", 9) == 0 ||
2388 strncmp(why, "Continuing ", 11) == 0) {
2389 gs_gamenum = gamenum;
2390 strcpy(gs_kind, strchr(why, ' ') + 1);
2392 if (appData.zippyPlay) {
2393 ZippyGameStart(whitename, blackname);
2399 /* Game end messages */
2400 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2401 ics_gamenum != gamenum) {
2404 while (endtoken[0] == ' ') endtoken++;
2405 switch (endtoken[0]) {
2408 endtype = GameUnfinished;
2411 endtype = BlackWins;
2414 if (endtoken[1] == '/')
2415 endtype = GameIsDrawn;
2417 endtype = WhiteWins;
2420 GameEnds(endtype, why, GE_ICS);
2422 if (appData.zippyPlay && first.initDone) {
2423 ZippyGameEnd(endtype, why);
2424 if (first.pr == NULL) {
2425 /* Start the next process early so that we'll
2426 be ready for the next challenge */
2427 StartChessProgram(&first);
2429 /* Send "new" early, in case this command takes
2430 a long time to finish, so that we'll be ready
2431 for the next challenge. */
2438 if (looking_at(buf, &i, "Removing game * from observation") ||
2439 looking_at(buf, &i, "no longer observing game *") ||
2440 looking_at(buf, &i, "Game * (*) has no examiners")) {
2441 if (gameMode == IcsObserving &&
2442 atoi(star_match[0]) == ics_gamenum)
2444 /* icsEngineAnalyze */
2445 if (appData.icsEngineAnalyze) {
2452 ics_user_moved = FALSE;
2457 if (looking_at(buf, &i, "no longer examining game *")) {
2458 if (gameMode == IcsExamining &&
2459 atoi(star_match[0]) == ics_gamenum)
2463 ics_user_moved = FALSE;
2468 /* Advance leftover_start past any newlines we find,
2469 so only partial lines can get reparsed */
2470 if (looking_at(buf, &i, "\n")) {
2471 prevColor = curColor;
2472 if (curColor != ColorNormal) {
2473 if (oldi > next_out) {
2474 SendToPlayer(&buf[next_out], oldi - next_out);
2477 Colorize(ColorNormal, FALSE);
2478 curColor = ColorNormal;
2480 if (started == STARTED_BOARD) {
2481 started = STARTED_NONE;
2482 parse[parse_pos] = NULLCHAR;
2483 ParseBoard12(parse);
2486 /* Send premove here */
2487 if (appData.premove) {
2489 if (currentMove == 0 &&
2490 gameMode == IcsPlayingWhite &&
2491 appData.premoveWhite) {
2492 sprintf(str, "%s%s\n", ics_prefix,
2493 appData.premoveWhiteText);
2494 if (appData.debugMode)
2495 fprintf(debugFP, "Sending premove:\n");
2497 } else if (currentMove == 1 &&
2498 gameMode == IcsPlayingBlack &&
2499 appData.premoveBlack) {
2500 sprintf(str, "%s%s\n", ics_prefix,
2501 appData.premoveBlackText);
2502 if (appData.debugMode)
2503 fprintf(debugFP, "Sending premove:\n");
2505 } else if (gotPremove) {
2507 ClearPremoveHighlights();
2508 if (appData.debugMode)
2509 fprintf(debugFP, "Sending premove:\n");
2510 UserMoveEvent(premoveFromX, premoveFromY,
2511 premoveToX, premoveToY,
2516 /* Usually suppress following prompt */
2517 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2518 if (looking_at(buf, &i, "*% ")) {
2519 savingComment = FALSE;
2523 } else if (started == STARTED_HOLDINGS) {
2525 char new_piece[MSG_SIZ];
2526 started = STARTED_NONE;
2527 parse[parse_pos] = NULLCHAR;
2528 if (appData.debugMode)
2529 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2530 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2531 gamenum == ics_gamenum) {
2532 if (gameInfo.variant == VariantNormal) {
2533 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2534 /* Get a move list just to see the header, which
2535 will tell us whether this is really bug or zh */
2536 if (ics_getting_history == H_FALSE) {
2537 ics_getting_history = H_REQUESTED;
2538 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2542 new_piece[0] = NULLCHAR;
2543 sscanf(parse, "game %d white [%s black [%s <- %s",
2544 &gamenum, white_holding, black_holding,
2546 white_holding[strlen(white_holding)-1] = NULLCHAR;
2547 black_holding[strlen(black_holding)-1] = NULLCHAR;
2549 if (appData.zippyPlay && first.initDone) {
2550 ZippyHoldings(white_holding, black_holding,
2554 if (tinyLayout || smallLayout) {
2555 char wh[16], bh[16];
2556 PackHolding(wh, white_holding);
2557 PackHolding(bh, black_holding);
2558 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2559 gameInfo.white, gameInfo.black);
2561 sprintf(str, "%s [%s] vs. %s [%s]",
2562 gameInfo.white, white_holding,
2563 gameInfo.black, black_holding);
2565 DrawPosition(FALSE, NULL);
2568 /* Suppress following prompt */
2569 if (looking_at(buf, &i, "*% ")) {
2570 savingComment = FALSE;
2577 i++; /* skip unparsed character and loop back */
2580 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2581 started != STARTED_HOLDINGS && i > next_out) {
2582 SendToPlayer(&buf[next_out], i - next_out);
2586 leftover_len = buf_len - leftover_start;
2587 /* if buffer ends with something we couldn't parse,
2588 reparse it after appending the next read */
2590 } else if (count == 0) {
2591 RemoveInputSource(isr);
2592 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2594 DisplayFatalError(_("Error reading from ICS"), error, 1);
2599 /* Board style 12 looks like this:
2601 <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
2603 * The "<12> " is stripped before it gets to this routine. The two
2604 * trailing 0's (flip state and clock ticking) are later addition, and
2605 * some chess servers may not have them, or may have only the first.
2606 * Additional trailing fields may be added in the future.
2609 #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"
2611 #define RELATION_OBSERVING_PLAYED 0
2612 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2613 #define RELATION_PLAYING_MYMOVE 1
2614 #define RELATION_PLAYING_NOTMYMOVE -1
2615 #define RELATION_EXAMINING 2
2616 #define RELATION_ISOLATED_BOARD -3
2617 #define RELATION_STARTING_POSITION -4 /* FICS only */
2620 ParseBoard12(string)
2623 GameMode newGameMode;
2624 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
2625 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
2626 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2627 char to_play, board_chars[72];
2628 char move_str[500], str[500], elapsed_time[500];
2629 char black[32], white[32];
2631 int prevMove = currentMove;
2634 int fromX, fromY, toX, toY;
2637 fromX = fromY = toX = toY = -1;
2641 if (appData.debugMode)
2642 fprintf(debugFP, _("Parsing board: %s\n"), string);
2644 move_str[0] = NULLCHAR;
2645 elapsed_time[0] = NULLCHAR;
2646 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2647 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2648 &gamenum, white, black, &relation, &basetime, &increment,
2649 &white_stren, &black_stren, &white_time, &black_time,
2650 &moveNum, str, elapsed_time, move_str, &ics_flip,
2654 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2655 DisplayError(str, 0);
2659 /* Convert the move number to internal form */
2660 moveNum = (moveNum - 1) * 2;
2661 if (to_play == 'B') moveNum++;
2662 if (moveNum >= MAX_MOVES) {
2663 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2669 case RELATION_OBSERVING_PLAYED:
2670 case RELATION_OBSERVING_STATIC:
2671 if (gamenum == -1) {
2672 /* Old ICC buglet */
2673 relation = RELATION_OBSERVING_STATIC;
2675 newGameMode = IcsObserving;
2677 case RELATION_PLAYING_MYMOVE:
2678 case RELATION_PLAYING_NOTMYMOVE:
2680 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2681 IcsPlayingWhite : IcsPlayingBlack;
2683 case RELATION_EXAMINING:
2684 newGameMode = IcsExamining;
2686 case RELATION_ISOLATED_BOARD:
2688 /* Just display this board. If user was doing something else,
2689 we will forget about it until the next board comes. */
2690 newGameMode = IcsIdle;
2692 case RELATION_STARTING_POSITION:
2693 newGameMode = gameMode;
2697 /* Modify behavior for initial board display on move listing
2700 switch (ics_getting_history) {
2704 case H_GOT_REQ_HEADER:
2705 case H_GOT_UNREQ_HEADER:
2706 /* This is the initial position of the current game */
2707 gamenum = ics_gamenum;
2708 moveNum = 0; /* old ICS bug workaround */
2709 if (to_play == 'B') {
2710 startedFromSetupPosition = TRUE;
2711 blackPlaysFirst = TRUE;
2713 if (forwardMostMove == 0) forwardMostMove = 1;
2714 if (backwardMostMove == 0) backwardMostMove = 1;
2715 if (currentMove == 0) currentMove = 1;
2717 newGameMode = gameMode;
2718 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2720 case H_GOT_UNWANTED_HEADER:
2721 /* This is an initial board that we don't want */
2723 case H_GETTING_MOVES:
2724 /* Should not happen */
2725 DisplayError(_("Error gathering move list: extra board"), 0);
2726 ics_getting_history = H_FALSE;
2730 /* Take action if this is the first board of a new game, or of a
2731 different game than is currently being displayed. */
2732 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2733 relation == RELATION_ISOLATED_BOARD) {
2735 /* Forget the old game and get the history (if any) of the new one */
2736 if (gameMode != BeginningOfGame) {
2740 if (appData.autoRaiseBoard) BoardToTop();
2742 if (gamenum == -1) {
2743 newGameMode = IcsIdle;
2744 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2745 appData.getMoveList) {
2746 /* Need to get game history */
2747 ics_getting_history = H_REQUESTED;
2748 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2752 /* Initially flip the board to have black on the bottom if playing
2753 black or if the ICS flip flag is set, but let the user change
2754 it with the Flip View button. */
2755 flipView = appData.autoFlipView ?
2756 (newGameMode == IcsPlayingBlack) || ics_flip :
2759 /* Done with values from previous mode; copy in new ones */
2760 gameMode = newGameMode;
2762 ics_gamenum = gamenum;
2763 if (gamenum == gs_gamenum) {
2764 int klen = strlen(gs_kind);
2765 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2766 sprintf(str, "ICS %s", gs_kind);
2767 gameInfo.event = StrSave(str);
2769 gameInfo.event = StrSave("ICS game");
2771 gameInfo.site = StrSave(appData.icsHost);
2772 gameInfo.date = PGNDate();
2773 gameInfo.round = StrSave("-");
2774 gameInfo.white = StrSave(white);
2775 gameInfo.black = StrSave(black);
2776 timeControl = basetime * 60 * 1000;
2777 timeIncrement = increment * 1000;
2778 movesPerSession = 0;
2779 gameInfo.timeControl = TimeControlTagValue();
2780 gameInfo.variant = StringToVariant(gameInfo.event);
2782 /* Do we have the ratings? */
2783 if (strcmp(player1Name, white) == 0 &&
2784 strcmp(player2Name, black) == 0) {
2785 if (appData.debugMode)
2786 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2787 player1Rating, player2Rating);
2788 gameInfo.whiteRating = player1Rating;
2789 gameInfo.blackRating = player2Rating;
2790 } else if (strcmp(player2Name, white) == 0 &&
2791 strcmp(player1Name, black) == 0) {
2792 if (appData.debugMode)
2793 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2794 player2Rating, player1Rating);
2795 gameInfo.whiteRating = player2Rating;
2796 gameInfo.blackRating = player1Rating;
2798 player1Name[0] = player2Name[0] = NULLCHAR;
2800 /* Silence shouts if requested */
2801 if (appData.quietPlay &&
2802 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2803 SendToICS(ics_prefix);
2804 SendToICS("set shout 0\n");
2808 /* Deal with midgame name changes */
2810 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2811 if (gameInfo.white) free(gameInfo.white);
2812 gameInfo.white = StrSave(white);
2814 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2815 if (gameInfo.black) free(gameInfo.black);
2816 gameInfo.black = StrSave(black);
2820 /* Throw away game result if anything actually changes in examine mode */
2821 if (gameMode == IcsExamining && !newGame) {
2822 gameInfo.result = GameUnfinished;
2823 if (gameInfo.resultDetails != NULL) {
2824 free(gameInfo.resultDetails);
2825 gameInfo.resultDetails = NULL;
2829 /* In pausing && IcsExamining mode, we ignore boards coming
2830 in if they are in a different variation than we are. */
2831 if (pauseExamInvalid) return;
2832 if (pausing && gameMode == IcsExamining) {
2833 if (moveNum <= pauseExamForwardMostMove) {
2834 pauseExamInvalid = TRUE;
2835 forwardMostMove = pauseExamForwardMostMove;
2840 /* Parse the board */
2841 for (k = 0; k < 8; k++)
2842 for (j = 0; j < 8; j++)
2843 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2844 CopyBoard(boards[moveNum], board);
2846 startedFromSetupPosition =
2847 !CompareBoards(board, initialPosition);
2850 if (ics_getting_history == H_GOT_REQ_HEADER ||
2851 ics_getting_history == H_GOT_UNREQ_HEADER) {
2852 /* This was an initial position from a move list, not
2853 the current position */
2857 /* Update currentMove and known move number limits */
2858 newMove = newGame || moveNum > forwardMostMove;
2860 /* If we found takebacks during icsEngineAnalyze
2861 try send to engine */
2862 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
2863 takeback = forwardMostMove - moveNum;
2864 for (i = 0; i < takeback; i++) {
2865 if (appData.debugMode) fprintf(debugFP, "take back move\n");
2866 SendToProgram("undo\n", &first);
2870 forwardMostMove = backwardMostMove = currentMove = moveNum;
2871 if (gameMode == IcsExamining && moveNum == 0) {
2872 /* Workaround for ICS limitation: we are not told the wild
2873 type when starting to examine a game. But if we ask for
2874 the move list, the move list header will tell us */
2875 ics_getting_history = H_REQUESTED;
2876 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2879 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2880 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2881 forwardMostMove = moveNum;
2882 if (!pausing || currentMove > forwardMostMove)
2883 currentMove = forwardMostMove;
2885 /* New part of history that is not contiguous with old part */
2886 if (pausing && gameMode == IcsExamining) {
2887 pauseExamInvalid = TRUE;
2888 forwardMostMove = pauseExamForwardMostMove;
2891 forwardMostMove = backwardMostMove = currentMove = moveNum;
2892 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2893 ics_getting_history = H_REQUESTED;
2894 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2899 /* Update the clocks */
2900 if (strchr(elapsed_time, '.')) {
2902 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2903 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2905 /* Time is in seconds */
2906 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2907 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2912 if (appData.zippyPlay && newGame &&
2913 gameMode != IcsObserving && gameMode != IcsIdle &&
2914 gameMode != IcsExamining)
2915 ZippyFirstBoard(moveNum, basetime, increment);
2918 /* Put the move on the move list, first converting
2919 to canonical algebraic form. */
2921 if (moveNum <= backwardMostMove) {
2922 /* We don't know what the board looked like before
2924 strcpy(parseList[moveNum - 1], move_str);
2925 strcat(parseList[moveNum - 1], " ");
2926 strcat(parseList[moveNum - 1], elapsed_time);
2927 moveList[moveNum - 1][0] = NULLCHAR;
2928 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2929 &fromX, &fromY, &toX, &toY, &promoChar)) {
2930 (void) CoordsToAlgebraic(boards[moveNum - 1],
2931 PosFlags(moveNum - 1), EP_UNKNOWN,
2932 fromY, fromX, toY, toX, promoChar,
2933 parseList[moveNum-1]);
2934 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2940 strcat(parseList[moveNum - 1], "+");
2943 strcat(parseList[moveNum - 1], "#");
2946 strcat(parseList[moveNum - 1], " ");
2947 strcat(parseList[moveNum - 1], elapsed_time);
2948 /* currentMoveString is set as a side-effect of ParseOneMove */
2949 strcpy(moveList[moveNum - 1], currentMoveString);
2950 strcat(moveList[moveNum - 1], "\n");
2951 } else if (strcmp(move_str, "none") == 0) {
2952 /* Again, we don't know what the board looked like;
2953 this is really the start of the game. */
2954 parseList[moveNum - 1][0] = NULLCHAR;
2955 moveList[moveNum - 1][0] = NULLCHAR;
2956 backwardMostMove = moveNum;
2957 startedFromSetupPosition = TRUE;
2958 fromX = fromY = toX = toY = -1;
2960 /* Move from ICS was illegal!? Punt. */
2962 if (appData.testLegality && appData.debugMode) {
2963 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2964 DisplayError(str, 0);
2967 strcpy(parseList[moveNum - 1], move_str);
2968 strcat(parseList[moveNum - 1], " ");
2969 strcat(parseList[moveNum - 1], elapsed_time);
2970 moveList[moveNum - 1][0] = NULLCHAR;
2971 fromX = fromY = toX = toY = -1;
2975 /* Send move to chess program (BEFORE animating it). */
2976 if (appData.zippyPlay && !newGame && newMove &&
2977 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2979 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2980 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2981 if (moveList[moveNum - 1][0] == NULLCHAR) {
2982 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2984 DisplayError(str, 0);
2986 if (first.sendTime) {
2987 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2989 SendMoveToProgram(moveNum - 1, &first);
2992 if (first.useColors) {
2993 SendToProgram(gameMode == IcsPlayingWhite ?
2995 "black\ngo\n", &first);
2997 SendToProgram("go\n", &first);
2999 first.maybeThinking = TRUE;
3002 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3003 if (moveList[moveNum - 1][0] == NULLCHAR) {
3004 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3005 DisplayError(str, 0);
3007 SendMoveToProgram(moveNum - 1, &first);
3014 if (moveNum > 0 && !gotPremove) {
3015 /* If move comes from a remote source, animate it. If it
3016 isn't remote, it will have already been animated. */
3017 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3018 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3020 if (!pausing && appData.highlightLastMove) {
3021 SetHighlights(fromX, fromY, toX, toY);
3025 /* Start the clocks */
3026 whiteFlag = blackFlag = FALSE;
3027 appData.clockMode = !(basetime == 0 && increment == 0);
3029 ics_clock_paused = TRUE;
3031 } else if (ticking == 1) {
3032 ics_clock_paused = FALSE;
3034 if (gameMode == IcsIdle ||
3035 relation == RELATION_OBSERVING_STATIC ||
3036 relation == RELATION_EXAMINING ||
3038 DisplayBothClocks();
3042 /* Display opponents and material strengths */
3043 if (gameInfo.variant != VariantBughouse &&
3044 gameInfo.variant != VariantCrazyhouse) {
3045 if (tinyLayout || smallLayout) {
3046 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3047 gameInfo.white, white_stren, gameInfo.black, black_stren,
3048 basetime, increment);
3050 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3051 gameInfo.white, white_stren, gameInfo.black, black_stren,
3052 basetime, increment);
3058 /* Display the board */
3061 if (appData.premove)
3063 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3064 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3065 ClearPremoveHighlights();
3067 DrawPosition(FALSE, boards[currentMove]);
3068 DisplayMove(moveNum - 1);
3069 if (appData.ringBellAfterMoves && !ics_user_moved)
3073 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3080 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3081 ics_getting_history = H_REQUESTED;
3082 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3088 AnalysisPeriodicEvent(force)
3091 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3092 && !force) || !appData.periodicUpdates)
3095 /* Send . command to Crafty to collect stats */
3096 SendToProgram(".\n", &first);
3098 /* Don't send another until we get a response (this makes
3099 us stop sending to old Crafty's which don't understand
3100 the "." command (sending illegal cmds resets node count & time,
3101 which looks bad)) */
3102 programStats.ok_to_send = 0;
3106 SendMoveToProgram(moveNum, cps)
3108 ChessProgramState *cps;
3111 if (cps->useUsermove) {
3112 SendToProgram("usermove ", cps);
3116 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3117 int len = space - parseList[moveNum];
3118 memcpy(buf, parseList[moveNum], len);
3120 buf[len] = NULLCHAR;
3122 sprintf(buf, "%s\n", parseList[moveNum]);
3124 SendToProgram(buf, cps);
3126 SendToProgram(moveList[moveNum], cps);
3131 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3133 int fromX, fromY, toX, toY;
3135 char user_move[MSG_SIZ];
3139 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3140 (int)moveType, fromX, fromY, toX, toY);
3141 DisplayError(user_move + strlen("say "), 0);
3143 case WhiteKingSideCastle:
3144 case BlackKingSideCastle:
3145 case WhiteQueenSideCastleWild:
3146 case BlackQueenSideCastleWild:
3147 sprintf(user_move, "o-o\n");
3149 case WhiteQueenSideCastle:
3150 case BlackQueenSideCastle:
3151 case WhiteKingSideCastleWild:
3152 case BlackKingSideCastleWild:
3153 sprintf(user_move, "o-o-o\n");
3155 case WhitePromotionQueen:
3156 case BlackPromotionQueen:
3157 case WhitePromotionRook:
3158 case BlackPromotionRook:
3159 case WhitePromotionBishop:
3160 case BlackPromotionBishop:
3161 case WhitePromotionKnight:
3162 case BlackPromotionKnight:
3163 case WhitePromotionKing:
3164 case BlackPromotionKing:
3165 sprintf(user_move, "%c%c%c%c=%c\n",
3166 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3167 PieceToChar(PromoPiece(moveType)));
3171 sprintf(user_move, "%c@%c%c\n",
3172 ToUpper(PieceToChar((ChessSquare) fromX)),
3173 'a' + toX, '1' + toY);
3176 case WhiteCapturesEnPassant:
3177 case BlackCapturesEnPassant:
3178 case IllegalMove: /* could be a variant we don't quite understand */
3179 sprintf(user_move, "%c%c%c%c\n",
3180 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3183 SendToICS(user_move);
3187 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3192 if (rf == DROP_RANK) {
3193 sprintf(move, "%c@%c%c\n",
3194 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3196 if (promoChar == 'x' || promoChar == NULLCHAR) {
3197 sprintf(move, "%c%c%c%c\n",
3198 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3200 sprintf(move, "%c%c%c%c%c\n",
3201 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3207 ProcessICSInitScript(f)
3212 while (fgets(buf, MSG_SIZ, f)) {
3213 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3220 /* Parser for moves from gnuchess, ICS, or user typein box */
3222 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3225 ChessMove *moveType;
3226 int *fromX, *fromY, *toX, *toY;
3229 *moveType = yylexstr(moveNum, move);
3230 switch (*moveType) {
3231 case WhitePromotionQueen:
3232 case BlackPromotionQueen:
3233 case WhitePromotionRook:
3234 case BlackPromotionRook:
3235 case WhitePromotionBishop:
3236 case BlackPromotionBishop:
3237 case WhitePromotionKnight:
3238 case BlackPromotionKnight:
3239 case WhitePromotionKing:
3240 case BlackPromotionKing:
3242 case WhiteCapturesEnPassant:
3243 case BlackCapturesEnPassant:
3244 case WhiteKingSideCastle:
3245 case WhiteQueenSideCastle:
3246 case BlackKingSideCastle:
3247 case BlackQueenSideCastle:
3248 case WhiteKingSideCastleWild:
3249 case WhiteQueenSideCastleWild:
3250 case BlackKingSideCastleWild:
3251 case BlackQueenSideCastleWild:
3252 case IllegalMove: /* bug or odd chess variant */
3253 *fromX = currentMoveString[0] - 'a';
3254 *fromY = currentMoveString[1] - '1';
3255 *toX = currentMoveString[2] - 'a';
3256 *toY = currentMoveString[3] - '1';
3257 *promoChar = currentMoveString[4];
3258 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3259 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3260 *fromX = *fromY = *toX = *toY = 0;
3263 if (appData.testLegality) {
3264 return (*moveType != IllegalMove);
3266 return !(fromX == fromY && toX == toY);
3271 *fromX = *moveType == WhiteDrop ?
3272 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3273 (int) CharToPiece(ToLower(currentMoveString[0]));
3275 *toX = currentMoveString[2] - 'a';
3276 *toY = currentMoveString[3] - '1';
3277 *promoChar = NULLCHAR;
3281 case ImpossibleMove:
3282 case (ChessMove) 0: /* end of file */
3292 *fromX = *fromY = *toX = *toY = 0;
3293 *promoChar = NULLCHAR;
3300 InitPosition(redraw)
3303 currentMove = forwardMostMove = backwardMostMove = 0;
3304 switch (gameInfo.variant) {
3306 CopyBoard(boards[0], initialPosition);
3308 case VariantTwoKings:
3309 CopyBoard(boards[0], twoKingsPosition);
3310 startedFromSetupPosition = TRUE;
3312 case VariantWildCastle:
3313 CopyBoard(boards[0], initialPosition);
3314 /* !!?shuffle with kings guaranteed to be on d or e file */
3316 case VariantNoCastle:
3317 CopyBoard(boards[0], initialPosition);
3318 /* !!?unconstrained back-rank shuffle */
3320 case VariantFischeRandom:
3321 CopyBoard(boards[0], initialPosition);
3322 /* !!shuffle according to FR rules */
3326 DrawPosition(FALSE, boards[currentMove]);
3330 SendBoard(cps, moveNum)
3331 ChessProgramState *cps;
3334 char message[MSG_SIZ];
3336 if (cps->useSetboard) {
3337 char* fen = PositionToFEN(moveNum);
3338 sprintf(message, "setboard %s\n", fen);
3339 SendToProgram(message, cps);
3345 /* Kludge to set black to move, avoiding the troublesome and now
3346 * deprecated "black" command.
3348 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3350 SendToProgram("edit\n", cps);
3351 SendToProgram("#\n", cps);
3352 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3353 bp = &boards[moveNum][i][0];
3354 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3355 if ((int) *bp < (int) BlackPawn) {
3356 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3358 SendToProgram(message, cps);
3363 SendToProgram("c\n", cps);
3364 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3365 bp = &boards[moveNum][i][0];
3366 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3367 if (((int) *bp != (int) EmptySquare)
3368 && ((int) *bp >= (int) BlackPawn)) {
3369 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3371 SendToProgram(message, cps);
3376 SendToProgram(".\n", cps);
3381 IsPromotion(fromX, fromY, toX, toY)
3382 int fromX, fromY, toX, toY;
3384 return gameMode != EditPosition &&
3385 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3386 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3387 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3392 PieceForSquare (x, y)
3396 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3399 return boards[currentMove][y][x];
3403 OKToStartUserMove(x, y)
3406 ChessSquare from_piece;
3409 if (matchMode) return FALSE;
3410 if (gameMode == EditPosition) return TRUE;
3412 if (x >= 0 && y >= 0)
3413 from_piece = boards[currentMove][y][x];
3415 from_piece = EmptySquare;
3417 if (from_piece == EmptySquare) return FALSE;
3419 white_piece = (int)from_piece >= (int)WhitePawn &&
3420 (int)from_piece <= (int)WhiteKing;
3423 case PlayFromGameFile:
3425 case TwoMachinesPlay:
3433 case MachinePlaysWhite:
3434 case IcsPlayingBlack:
3435 if (appData.zippyPlay) return FALSE;
3437 DisplayMoveError(_("You are playing Black"));
3442 case MachinePlaysBlack:
3443 case IcsPlayingWhite:
3444 if (appData.zippyPlay) return FALSE;
3446 DisplayMoveError(_("You are playing White"));
3452 if (!white_piece && WhiteOnMove(currentMove)) {
3453 DisplayMoveError(_("It is White's turn"));
3456 if (white_piece && !WhiteOnMove(currentMove)) {
3457 DisplayMoveError(_("It is Black's turn"));
3460 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3461 /* Editing correspondence game history */
3462 /* Could disallow this or prompt for confirmation */
3465 if (currentMove < forwardMostMove) {
3466 /* Discarding moves */
3467 /* Could prompt for confirmation here,
3468 but I don't think that's such a good idea */
3469 forwardMostMove = currentMove;
3473 case BeginningOfGame:
3474 if (appData.icsActive) return FALSE;
3475 if (!appData.noChessProgram) {
3477 DisplayMoveError(_("You are playing White"));
3484 if (!white_piece && WhiteOnMove(currentMove)) {
3485 DisplayMoveError(_("It is White's turn"));
3488 if (white_piece && !WhiteOnMove(currentMove)) {
3489 DisplayMoveError(_("It is Black's turn"));
3498 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3499 && gameMode != AnalyzeFile && gameMode != Training) {
3500 DisplayMoveError(_("Displayed position is not current"));
3506 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3507 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3508 int lastLoadGameUseList = FALSE;
3509 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3510 ChessMove lastLoadGameStart = (ChessMove) 0;
3514 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3515 int fromX, fromY, toX, toY;
3520 if (fromX < 0 || fromY < 0) return;
3521 if ((fromX == toX) && (fromY == toY)) {
3525 /* Check if the user is playing in turn. This is complicated because we
3526 let the user "pick up" a piece before it is his turn. So the piece he
3527 tried to pick up may have been captured by the time he puts it down!
3528 Therefore we use the color the user is supposed to be playing in this
3529 test, not the color of the piece that is currently on the starting
3530 square---except in EditGame mode, where the user is playing both
3531 sides; fortunately there the capture race can't happen. (It can
3532 now happen in IcsExamining mode, but that's just too bad. The user
3533 will get a somewhat confusing message in that case.)
3537 case PlayFromGameFile:
3539 case TwoMachinesPlay:
3543 /* We switched into a game mode where moves are not accepted,
3544 perhaps while the mouse button was down. */
3547 case MachinePlaysWhite:
3548 /* User is moving for Black */
3549 if (WhiteOnMove(currentMove)) {
3550 DisplayMoveError(_("It is White's turn"));
3555 case MachinePlaysBlack:
3556 /* User is moving for White */
3557 if (!WhiteOnMove(currentMove)) {
3558 DisplayMoveError(_("It is Black's turn"));
3565 case BeginningOfGame:
3568 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3569 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3570 /* User is moving for Black */
3571 if (WhiteOnMove(currentMove)) {
3572 DisplayMoveError(_("It is White's turn"));
3576 /* User is moving for White */
3577 if (!WhiteOnMove(currentMove)) {
3578 DisplayMoveError(_("It is Black's turn"));
3584 case IcsPlayingBlack:
3585 /* User is moving for Black */
3586 if (WhiteOnMove(currentMove)) {
3587 if (!appData.premove) {
3588 DisplayMoveError(_("It is White's turn"));
3589 } else if (toX >= 0 && toY >= 0) {
3592 premoveFromX = fromX;
3593 premoveFromY = fromY;
3594 premovePromoChar = promoChar;
3596 if (appData.debugMode)
3597 fprintf(debugFP, "Got premove: fromX %d,"
3598 "fromY %d, toX %d, toY %d\n",
3599 fromX, fromY, toX, toY);
3605 case IcsPlayingWhite:
3606 /* User is moving for White */
3607 if (!WhiteOnMove(currentMove)) {
3608 if (!appData.premove) {
3609 DisplayMoveError(_("It is Black's turn"));
3610 } else if (toX >= 0 && toY >= 0) {
3613 premoveFromX = fromX;
3614 premoveFromY = fromY;
3615 premovePromoChar = promoChar;
3617 if (appData.debugMode)
3618 fprintf(debugFP, "Got premove: fromX %d,"
3619 "fromY %d, toX %d, toY %d\n",
3620 fromX, fromY, toX, toY);
3630 if (toX == -2 || toY == -2) {
3631 boards[0][fromY][fromX] = EmptySquare;
3632 DrawPosition(FALSE, boards[currentMove]);
3633 } else if (toX >= 0 && toY >= 0) {
3634 boards[0][toY][toX] = boards[0][fromY][fromX];
3635 boards[0][fromY][fromX] = EmptySquare;
3636 DrawPosition(FALSE, boards[currentMove]);
3641 if (toX < 0 || toY < 0) return;
3642 userOfferedDraw = FALSE;
3644 if (appData.testLegality) {
3645 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3646 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3647 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3648 DisplayMoveError(_("Illegal move"));
3652 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3655 if (gameMode == Training) {
3656 /* compare the move played on the board to the next move in the
3657 * game. If they match, display the move and the opponent's response.
3658 * If they don't match, display an error message.
3662 CopyBoard(testBoard, boards[currentMove]);
3663 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3665 if (CompareBoards(testBoard, boards[currentMove+1])) {
3666 ForwardInner(currentMove+1);
3668 /* Autoplay the opponent's response.
3669 * if appData.animate was TRUE when Training mode was entered,
3670 * the response will be animated.
3672 saveAnimate = appData.animate;
3673 appData.animate = animateTraining;
3674 ForwardInner(currentMove+1);
3675 appData.animate = saveAnimate;
3677 /* check for the end of the game */
3678 if (currentMove >= forwardMostMove) {
3679 gameMode = PlayFromGameFile;
3681 SetTrainingModeOff();
3682 DisplayInformation(_("End of game"));
3685 DisplayError(_("Incorrect move"), 0);
3690 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3693 /* Common tail of UserMoveEvent and DropMenuEvent */
3695 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3697 int fromX, fromY, toX, toY;
3698 /*char*/int promoChar;
3700 /* Ok, now we know that the move is good, so we can kill
3701 the previous line in Analysis Mode */
3702 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3703 forwardMostMove = currentMove;
3706 /* If we need the chess program but it's dead, restart it */
3707 ResurrectChessProgram();
3709 /* A user move restarts a paused game*/
3713 thinkOutput[0] = NULLCHAR;
3715 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3717 if (gameMode == BeginningOfGame) {
3718 if (appData.noChessProgram) {
3719 gameMode = EditGame;
3723 gameMode = MachinePlaysBlack;
3725 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3727 if (first.sendName) {
3728 sprintf(buf, "name %s\n", gameInfo.white);
3729 SendToProgram(buf, &first);
3735 /* Relay move to ICS or chess engine */
3736 if (appData.icsActive) {
3737 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3738 gameMode == IcsExamining) {
3739 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3743 if (first.sendTime && (gameMode == BeginningOfGame ||
3744 gameMode == MachinePlaysWhite ||
3745 gameMode == MachinePlaysBlack)) {
3746 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3748 SendMoveToProgram(forwardMostMove-1, &first);
3749 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3750 first.maybeThinking = TRUE;
3752 if (currentMove == cmailOldMove + 1) {
3753 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3757 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3761 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3767 if (WhiteOnMove(currentMove)) {
3768 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3770 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3774 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3779 case MachinePlaysBlack:
3780 case MachinePlaysWhite:
3781 /* disable certain menu options while machine is thinking */
3782 SetMachineThinkingEnables();
3791 HandleMachineMove(message, cps)
3793 ChessProgramState *cps;
3795 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3796 char realname[MSG_SIZ];
3797 int fromX, fromY, toX, toY;
3804 * Kludge to ignore BEL characters
3806 while (*message == '\007') message++;
3809 * Look for book output
3811 if (cps == &first && bookRequested) {
3812 if (message[0] == '\t' || message[0] == ' ') {
3813 /* Part of the book output is here; append it */
3814 strcat(bookOutput, message);
3815 strcat(bookOutput, " \n");
3817 } else if (bookOutput[0] != NULLCHAR) {
3818 /* All of book output has arrived; display it */
3819 char *p = bookOutput;
3820 while (*p != NULLCHAR) {
3821 if (*p == '\t') *p = ' ';
3824 DisplayInformation(bookOutput);
3825 bookRequested = FALSE;
3826 /* Fall through to parse the current output */
3831 * Look for machine move.
3833 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3834 strcmp(buf2, "...") == 0) ||
3835 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3836 strcmp(buf1, "move") == 0)) {
3838 /* This method is only useful on engines that support ping */
3839 if (cps->lastPing != cps->lastPong) {
3840 if (gameMode == BeginningOfGame) {
3841 /* Extra move from before last new; ignore */
3842 if (appData.debugMode) {
3843 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3846 if (appData.debugMode) {
3847 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3848 cps->which, gameMode);
3850 SendToProgram("undo\n", cps);
3856 case BeginningOfGame:
3857 /* Extra move from before last reset; ignore */
3858 if (appData.debugMode) {
3859 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3866 /* Extra move after we tried to stop. The mode test is
3867 not a reliable way of detecting this problem, but it's
3868 the best we can do on engines that don't support ping.
3870 if (appData.debugMode) {
3871 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3872 cps->which, gameMode);
3874 SendToProgram("undo\n", cps);
3877 case MachinePlaysWhite:
3878 case IcsPlayingWhite:
3879 machineWhite = TRUE;
3882 case MachinePlaysBlack:
3883 case IcsPlayingBlack:
3884 machineWhite = FALSE;
3887 case TwoMachinesPlay:
3888 machineWhite = (cps->twoMachinesColor[0] == 'w');
3891 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3892 if (appData.debugMode) {
3894 "Ignoring move out of turn by %s, gameMode %d"
3895 ", forwardMost %d\n",
3896 cps->which, gameMode, forwardMostMove);
3901 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3902 &fromX, &fromY, &toX, &toY, &promoChar)) {
3903 /* Machine move could not be parsed; ignore it. */
3904 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3905 machineMove, cps->which);
3906 DisplayError(buf1, 0);
3907 if (gameMode == TwoMachinesPlay) {
3908 GameEnds(machineWhite ? BlackWins : WhiteWins,
3909 "Forfeit due to illegal move", GE_XBOARD);
3914 hintRequested = FALSE;
3915 lastHint[0] = NULLCHAR;
3916 bookRequested = FALSE;
3917 /* Program may be pondering now */
3918 cps->maybeThinking = TRUE;
3919 if (cps->sendTime == 2) cps->sendTime = 1;
3920 if (cps->offeredDraw) cps->offeredDraw--;
3923 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3925 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3929 /* currentMoveString is set as a side-effect of ParseOneMove */
3930 strcpy(machineMove, currentMoveString);
3931 strcat(machineMove, "\n");
3932 strcpy(moveList[forwardMostMove], machineMove);
3934 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3936 if (gameMode == TwoMachinesPlay) {
3937 if (cps->other->sendTime) {
3938 SendTimeRemaining(cps->other,
3939 cps->other->twoMachinesColor[0] == 'w');
3941 SendMoveToProgram(forwardMostMove-1, cps->other);
3944 if (cps->other->useColors) {
3945 SendToProgram(cps->other->twoMachinesColor, cps->other);
3947 SendToProgram("go\n", cps->other);
3949 cps->other->maybeThinking = TRUE;
3952 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3953 if (!pausing && appData.ringBellAfterMoves) {
3957 * Reenable menu items that were disabled while
3958 * machine was thinking
3960 if (gameMode != TwoMachinesPlay)
3961 SetUserThinkingEnables();
3965 /* Set special modes for chess engines. Later something general
3966 * could be added here; for now there is just one kludge feature,
3967 * needed because Crafty 15.10 and earlier don't ignore SIGINT
3968 * when "xboard" is given as an interactive command.
3970 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3971 cps->useSigint = FALSE;
3972 cps->useSigterm = FALSE;
3976 * Look for communication commands
3978 if (!strncmp(message, "telluser ", 9)) {
3979 DisplayNote(message + 9);
3982 if (!strncmp(message, "tellusererror ", 14)) {
3983 DisplayError(message + 14, 0);
3986 if (!strncmp(message, "tellopponent ", 13)) {
3987 if (appData.icsActive) {
3989 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3993 DisplayNote(message + 13);
3997 if (!strncmp(message, "tellothers ", 11)) {
3998 if (appData.icsActive) {
4000 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4006 if (!strncmp(message, "tellall ", 8)) {
4007 if (appData.icsActive) {
4009 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4013 DisplayNote(message + 8);
4017 if (strncmp(message, "warning", 7) == 0) {
4018 /* Undocumented feature, use tellusererror in new code */
4019 DisplayError(message, 0);
4022 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4023 strcpy(realname, cps->tidy);
4024 strcat(realname, " query");
4025 AskQuestion(realname, buf2, buf1, cps->pr);
4028 /* Commands from the engine directly to ICS. We don't allow these to be
4029 * sent until we are logged on. Crafty kibitzes have been known to
4030 * interfere with the login process.
4033 if (!strncmp(message, "tellics ", 8)) {
4034 SendToICS(message + 8);
4038 if (!strncmp(message, "tellicsnoalias ", 15)) {
4039 SendToICS(ics_prefix);
4040 SendToICS(message + 15);
4044 /* The following are for backward compatibility only */
4045 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4046 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4047 SendToICS(ics_prefix);
4053 if (strncmp(message, "feature ", 8) == 0) {
4054 ParseFeatures(message+8, cps);
4056 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4060 * If the move is illegal, cancel it and redraw the board.
4061 * Also deal with other error cases. Matching is rather loose
4062 * here to accommodate engines written before the spec.
4064 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4065 strncmp(message, "Error", 5) == 0) {
4066 if (StrStr(message, "name") ||
4067 StrStr(message, "rating") || StrStr(message, "?") ||
4068 StrStr(message, "result") || StrStr(message, "board") ||
4069 StrStr(message, "bk") || StrStr(message, "computer") ||
4070 StrStr(message, "variant") || StrStr(message, "hint") ||
4071 StrStr(message, "random") || StrStr(message, "depth") ||
4072 StrStr(message, "accepted")) {
4075 if (StrStr(message, "protover")) {
4076 /* Program is responding to input, so it's apparently done
4077 initializing, and this error message indicates it is
4078 protocol version 1. So we don't need to wait any longer
4079 for it to initialize and send feature commands. */
4080 FeatureDone(cps, 1);
4081 cps->protocolVersion = 1;
4084 cps->maybeThinking = FALSE;
4086 if (StrStr(message, "draw")) {
4087 /* Program doesn't have "draw" command */
4088 cps->sendDrawOffers = 0;
4091 if (cps->sendTime != 1 &&
4092 (StrStr(message, "time") || StrStr(message, "otim"))) {
4093 /* Program apparently doesn't have "time" or "otim" command */
4097 if (StrStr(message, "analyze")) {
4098 cps->analysisSupport = FALSE;
4099 cps->analyzing = FALSE;
4101 sprintf(buf2, "%s does not support analysis", cps->tidy);
4102 DisplayError(buf2, 0);
4105 if (StrStr(message, "(no matching move)st")) {
4106 /* Special kludge for GNU Chess 4 only */
4107 cps->stKludge = TRUE;
4108 SendTimeControl(cps, movesPerSession, timeControl,
4109 timeIncrement, appData.searchDepth,
4113 if (StrStr(message, "(no matching move)sd")) {
4114 /* Special kludge for GNU Chess 4 only */
4115 cps->sdKludge = TRUE;
4116 SendTimeControl(cps, movesPerSession, timeControl,
4117 timeIncrement, appData.searchDepth,
4121 if (!StrStr(message, "llegal")) return;
4122 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4123 gameMode == IcsIdle) return;
4124 if (forwardMostMove <= backwardMostMove) return;
4126 /* Following removed: it caused a bug where a real illegal move
4127 message in analyze mored would be ignored. */
4128 if (cps == &first && programStats.ok_to_send == 0) {
4129 /* Bogus message from Crafty responding to "." This filtering
4130 can miss some of the bad messages, but fortunately the bug
4131 is fixed in current Crafty versions, so it doesn't matter. */
4135 if (pausing) PauseEvent();
4136 if (gameMode == PlayFromGameFile) {
4137 /* Stop reading this game file */
4138 gameMode = EditGame;
4141 currentMove = --forwardMostMove;
4142 DisplayMove(currentMove-1); /* before DisplayMoveError */
4144 DisplayBothClocks();
4145 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
4146 parseList[currentMove], cps->which);
4147 DisplayMoveError(buf1);
4148 DrawPosition(FALSE, boards[currentMove]);
4151 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4152 /* Program has a broken "time" command that
4153 outputs a string not ending in newline.
4159 * If chess program startup fails, exit with an error message.
4160 * Attempts to recover here are futile.
4162 if ((StrStr(message, "unknown host") != NULL)
4163 || (StrStr(message, "No remote directory") != NULL)
4164 || (StrStr(message, "not found") != NULL)
4165 || (StrStr(message, "No such file") != NULL)
4166 || (StrStr(message, "can't alloc") != NULL)
4167 || (StrStr(message, "Permission denied") != NULL)) {
4169 cps->maybeThinking = FALSE;
4170 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
4171 cps->which, cps->program, cps->host, message);
4172 RemoveInputSource(cps->isr);
4173 DisplayFatalError(buf1, 0, 1);
4178 * Look for hint output
4180 if (sscanf(message, "Hint: %s", buf1) == 1) {
4181 if (cps == &first && hintRequested) {
4182 hintRequested = FALSE;
4183 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4184 &fromX, &fromY, &toX, &toY, &promoChar)) {
4185 (void) CoordsToAlgebraic(boards[forwardMostMove],
4186 PosFlags(forwardMostMove), EP_UNKNOWN,
4187 fromY, fromX, toY, toX, promoChar, buf1);
4188 sprintf(buf2, "Hint: %s", buf1);
4189 DisplayInformation(buf2);
4191 /* Hint move could not be parsed!? */
4193 _("Illegal hint move \"%s\"\nfrom %s chess program"),
4195 DisplayError(buf2, 0);
4198 strcpy(lastHint, buf1);
4204 * Ignore other messages if game is not in progress
4206 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4207 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4210 * look for win, lose, draw, or draw offer
4212 if (strncmp(message, "1-0", 3) == 0) {
4213 char *p, *q, *r = "";
4214 p = strchr(message, '{');
4222 GameEnds(WhiteWins, r, GE_ENGINE);
4224 } else if (strncmp(message, "0-1", 3) == 0) {
4225 char *p, *q, *r = "";
4226 p = strchr(message, '{');
4234 /* Kludge for Arasan 4.1 bug */
4235 if (strcmp(r, "Black resigns") == 0) {
4236 GameEnds(WhiteWins, r, GE_ENGINE);
4239 GameEnds(BlackWins, r, GE_ENGINE);
4241 } else if (strncmp(message, "1/2", 3) == 0) {
4242 char *p, *q, *r = "";
4243 p = strchr(message, '{');
4251 GameEnds(GameIsDrawn, r, GE_ENGINE);
4254 } else if (strncmp(message, "White resign", 12) == 0) {
4255 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4257 } else if (strncmp(message, "Black resign", 12) == 0) {
4258 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4260 } else if (strncmp(message, "White", 5) == 0 &&
4261 message[5] != '(' &&
4262 StrStr(message, "Black") == NULL) {
4263 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4265 } else if (strncmp(message, "Black", 5) == 0 &&
4266 message[5] != '(') {
4267 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4269 } else if (strcmp(message, "resign") == 0 ||
4270 strcmp(message, "computer resigns") == 0) {
4272 case MachinePlaysBlack:
4273 case IcsPlayingBlack:
4274 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4276 case MachinePlaysWhite:
4277 case IcsPlayingWhite:
4278 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4280 case TwoMachinesPlay:
4281 if (cps->twoMachinesColor[0] == 'w')
4282 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4284 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4291 } else if (strncmp(message, "opponent mates", 14) == 0) {
4293 case MachinePlaysBlack:
4294 case IcsPlayingBlack:
4295 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4297 case MachinePlaysWhite:
4298 case IcsPlayingWhite:
4299 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4301 case TwoMachinesPlay:
4302 if (cps->twoMachinesColor[0] == 'w')
4303 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4305 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4312 } else if (strncmp(message, "computer mates", 14) == 0) {
4314 case MachinePlaysBlack:
4315 case IcsPlayingBlack:
4316 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4318 case MachinePlaysWhite:
4319 case IcsPlayingWhite:
4320 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4322 case TwoMachinesPlay:
4323 if (cps->twoMachinesColor[0] == 'w')
4324 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4326 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4333 } else if (strncmp(message, "checkmate", 9) == 0) {
4334 if (WhiteOnMove(forwardMostMove)) {
4335 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4337 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4340 } else if (strstr(message, "Draw") != NULL ||
4341 strstr(message, "game is a draw") != NULL) {
4342 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4344 } else if (strstr(message, "offer") != NULL &&
4345 strstr(message, "draw") != NULL) {
4347 if (appData.zippyPlay && first.initDone) {
4348 /* Relay offer to ICS */
4349 SendToICS(ics_prefix);
4350 SendToICS("draw\n");
4353 cps->offeredDraw = 2; /* valid until this engine moves twice */
4354 if (gameMode == TwoMachinesPlay) {
4355 if (cps->other->offeredDraw) {
4356 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4358 if (cps->other->sendDrawOffers) {
4359 SendToProgram("draw\n", cps->other);
4362 } else if (gameMode == MachinePlaysWhite ||
4363 gameMode == MachinePlaysBlack) {
4364 if (userOfferedDraw) {
4365 DisplayInformation(_("Machine accepts your draw offer"));
4366 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4368 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
4375 * Look for thinking output
4377 if (appData.showThinking) {
4378 int plylev, mvleft, mvtot, curscore, time;
4379 char mvname[MOVE_LEN];
4383 int prefixHint = FALSE;
4384 mvname[0] = NULLCHAR;
4387 case MachinePlaysBlack:
4388 case IcsPlayingBlack:
4389 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4391 case MachinePlaysWhite:
4392 case IcsPlayingWhite:
4393 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4398 /* icsEngineAnalyze */
4400 if (!appData.icsEngineAnalyze) ignore = TRUE;
4402 case TwoMachinesPlay:
4403 if ((cps->twoMachinesColor[0] == 'w') !=
4404 WhiteOnMove(forwardMostMove)) {
4415 if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
4416 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4418 if (plyext != ' ' && plyext != '\t') {
4421 programStats.depth = plylev;
4422 programStats.nodes = nodes;
4423 programStats.time = time;
4424 programStats.score = curscore;
4425 programStats.got_only_move = 0;
4427 /* Buffer overflow protection */
4428 if (buf1[0] != NULLCHAR) {
4429 if (strlen(buf1) >= sizeof(programStats.movelist)
4430 && appData.debugMode) {
4432 "PV is too long; using the first %d bytes.\n",
4433 sizeof(programStats.movelist) - 1);
4435 strncpy(programStats.movelist, buf1,
4436 sizeof(programStats.movelist));
4437 programStats.movelist[sizeof(programStats.movelist) - 1]
4440 sprintf(programStats.movelist, " no PV\n");
4443 if (programStats.seen_stat) {
4444 programStats.ok_to_send = 1;
4447 if (strchr(programStats.movelist, '(') != NULL) {
4448 programStats.line_is_book = 1;
4449 programStats.nr_moves = 0;
4450 programStats.moves_left = 0;
4452 programStats.line_is_book = 0;
4455 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4457 (gameMode == TwoMachinesPlay ?
4458 ToUpper(cps->twoMachinesColor[0]) : ' '),
4459 ((double) curscore) / 100.0,
4460 prefixHint ? lastHint : "",
4461 prefixHint ? " " : "", buf1);
4463 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
4464 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4465 DisplayMove(currentMove - 1);
4470 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4471 /* crafty (9.25+) says "(only move) <move>"
4472 * if there is only 1 legal move
4474 sscanf(p, "(only move) %s", buf1);
4475 sprintf(thinkOutput, "%s (only move)", buf1);
4476 sprintf(programStats.movelist, "%s (only move)", buf1);
4477 programStats.depth = 1;
4478 programStats.nr_moves = 1;
4479 programStats.moves_left = 1;
4480 programStats.nodes = 1;
4481 programStats.time = 1;
4482 programStats.got_only_move = 1;
4484 /* Not really, but we also use this member to
4485 mean "line isn't going to change" (Crafty
4486 isn't searching, so stats won't change) */
4487 programStats.line_is_book = 1;
4489 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4490 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4491 DisplayMove(currentMove - 1);
4495 } else if (sscanf(message,"stat01: %d" u64Display "%d %d %d %s",
4496 &time, &nodes, &plylev, &mvleft,
4497 &mvtot, mvname) >= 5) {
4498 /* The stat01: line is from Crafty (9.29+) in response
4499 to the "." command */
4500 programStats.seen_stat = 1;
4501 cps->maybeThinking = TRUE;
4503 if (programStats.got_only_move || !appData.periodicUpdates)
4506 programStats.depth = plylev;
4507 programStats.time = time;
4508 programStats.nodes = nodes;
4509 programStats.moves_left = mvleft;
4510 programStats.nr_moves = mvtot;
4511 strcpy(programStats.move_name, mvname);
4512 programStats.ok_to_send = 1;
4516 } else if (strncmp(message,"++",2) == 0) {
4517 /* Crafty 9.29+ outputs this */
4518 programStats.got_fail = 2;
4521 } else if (strncmp(message,"--",2) == 0) {
4522 /* Crafty 9.29+ outputs this */
4523 programStats.got_fail = 1;
4526 } else if (thinkOutput[0] != NULLCHAR &&
4527 strncmp(message, " ", 4) == 0) {
4529 while (*p && *p == ' ') p++;
4530 strcat(thinkOutput, " ");
4531 strcat(thinkOutput, p);
4532 strcat(programStats.movelist, " ");
4533 strcat(programStats.movelist, p);
4534 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4535 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4536 DisplayMove(currentMove - 1);
4546 /* Parse a game score from the character string "game", and
4547 record it as the history of the current game. The game
4548 score is NOT assumed to start from the standard position.
4549 The display is not updated in any way.
4552 ParseGameHistory(game)
4556 int fromX, fromY, toX, toY, boardIndex;
4561 if (appData.debugMode)
4562 fprintf(debugFP, "Parsing game history: %s\n", game);
4564 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4565 gameInfo.site = StrSave(appData.icsHost);
4566 gameInfo.date = PGNDate();
4567 gameInfo.round = StrSave("-");
4569 /* Parse out names of players */
4570 while (*game == ' ') game++;
4572 while (*game != ' ') *p++ = *game++;
4574 gameInfo.white = StrSave(buf);
4575 while (*game == ' ') game++;
4577 while (*game != ' ' && *game != '\n') *p++ = *game++;
4579 gameInfo.black = StrSave(buf);
4582 boardIndex = blackPlaysFirst ? 1 : 0;
4585 yyboardindex = boardIndex;
4586 moveType = (ChessMove) yylex();
4588 case WhitePromotionQueen:
4589 case BlackPromotionQueen:
4590 case WhitePromotionRook:
4591 case BlackPromotionRook:
4592 case WhitePromotionBishop:
4593 case BlackPromotionBishop:
4594 case WhitePromotionKnight:
4595 case BlackPromotionKnight:
4596 case WhitePromotionKing:
4597 case BlackPromotionKing:
4599 case WhiteCapturesEnPassant:
4600 case BlackCapturesEnPassant:
4601 case WhiteKingSideCastle:
4602 case WhiteQueenSideCastle:
4603 case BlackKingSideCastle:
4604 case BlackQueenSideCastle:
4605 case WhiteKingSideCastleWild:
4606 case WhiteQueenSideCastleWild:
4607 case BlackKingSideCastleWild:
4608 case BlackQueenSideCastleWild:
4609 case IllegalMove: /* maybe suicide chess, etc. */
4610 fromX = currentMoveString[0] - 'a';
4611 fromY = currentMoveString[1] - '1';
4612 toX = currentMoveString[2] - 'a';
4613 toY = currentMoveString[3] - '1';
4614 promoChar = currentMoveString[4];
4618 fromX = moveType == WhiteDrop ?
4619 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4620 (int) CharToPiece(ToLower(currentMoveString[0]));
4622 toX = currentMoveString[2] - 'a';
4623 toY = currentMoveString[3] - '1';
4624 promoChar = NULLCHAR;
4628 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4629 DisplayError(buf, 0);
4631 case ImpossibleMove:
4633 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4634 DisplayError(buf, 0);
4636 case (ChessMove) 0: /* end of file */
4637 if (boardIndex < backwardMostMove) {
4638 /* Oops, gap. How did that happen? */
4639 DisplayError(_("Gap in move list"), 0);
4642 backwardMostMove = blackPlaysFirst ? 1 : 0;
4643 if (boardIndex > forwardMostMove) {
4644 forwardMostMove = boardIndex;
4648 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4649 strcat(parseList[boardIndex-1], " ");
4650 strcat(parseList[boardIndex-1], yy_text);
4662 case GameUnfinished:
4663 if (gameMode == IcsExamining) {
4664 if (boardIndex < backwardMostMove) {
4665 /* Oops, gap. How did that happen? */
4668 backwardMostMove = blackPlaysFirst ? 1 : 0;
4671 gameInfo.result = moveType;
4672 p = strchr(yy_text, '{');
4673 if (p == NULL) p = strchr(yy_text, '(');
4676 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4678 q = strchr(p, *p == '{' ? '}' : ')');
4679 if (q != NULL) *q = NULLCHAR;
4682 gameInfo.resultDetails = StrSave(p);
4685 if (boardIndex >= forwardMostMove &&
4686 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4687 backwardMostMove = blackPlaysFirst ? 1 : 0;
4690 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4691 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4692 parseList[boardIndex]);
4693 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4694 /* currentMoveString is set as a side-effect of yylex */
4695 strcpy(moveList[boardIndex], currentMoveString);
4696 strcat(moveList[boardIndex], "\n");
4698 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4699 switch (MateTest(boards[boardIndex],
4700 PosFlags(boardIndex), EP_UNKNOWN)) {
4706 strcat(parseList[boardIndex - 1], "+");
4709 strcat(parseList[boardIndex - 1], "#");
4716 /* Apply a move to the given board */
4718 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4719 int fromX, fromY, toX, toY;
4723 ChessSquare captured = board[toY][toX];
4724 if (fromY == DROP_RANK) {
4726 board[toY][toX] = (ChessSquare) fromX;
4727 } else if (fromX == toX && fromY == toY) {
4729 } else if (fromY == 0 && fromX == 4
4730 && board[fromY][fromX] == WhiteKing
4731 && toY == 0 && toX == 6) {
4732 board[fromY][fromX] = EmptySquare;
4733 board[toY][toX] = WhiteKing;
4734 board[fromY][7] = EmptySquare;
4735 board[toY][5] = WhiteRook;
4736 } else if (fromY == 0 && fromX == 4
4737 && board[fromY][fromX] == WhiteKing
4738 && toY == 0 && toX == 2) {
4739 board[fromY][fromX] = EmptySquare;
4740 board[toY][toX] = WhiteKing;
4741 board[fromY][0] = EmptySquare;
4742 board[toY][3] = WhiteRook;
4743 } else if (fromY == 0 && fromX == 3
4744 && board[fromY][fromX] == WhiteKing
4745 && toY == 0 && toX == 5) {
4746 board[fromY][fromX] = EmptySquare;
4747 board[toY][toX] = WhiteKing;
4748 board[fromY][7] = EmptySquare;
4749 board[toY][4] = WhiteRook;
4750 } else if (fromY == 0 && fromX == 3
4751 && board[fromY][fromX] == WhiteKing
4752 && toY == 0 && toX == 1) {
4753 board[fromY][fromX] = EmptySquare;
4754 board[toY][toX] = WhiteKing;
4755 board[fromY][0] = EmptySquare;
4756 board[toY][2] = WhiteRook;
4757 } else if (board[fromY][fromX] == WhitePawn
4759 /* white pawn promotion */
4760 board[7][toX] = CharToPiece(ToUpper(promoChar));
4761 if (board[7][toX] == EmptySquare) {
4762 board[7][toX] = WhiteQueen;
4764 board[fromY][fromX] = EmptySquare;
4765 } else if ((fromY == 4)
4767 && (board[fromY][fromX] == WhitePawn)
4768 && (board[toY][toX] == EmptySquare)) {
4769 board[fromY][fromX] = EmptySquare;
4770 board[toY][toX] = WhitePawn;
4771 captured = board[toY - 1][toX];
4772 board[toY - 1][toX] = EmptySquare;
4773 } else if (fromY == 7 && fromX == 4
4774 && board[fromY][fromX] == BlackKing
4775 && toY == 7 && toX == 6) {
4776 board[fromY][fromX] = EmptySquare;
4777 board[toY][toX] = BlackKing;
4778 board[fromY][7] = EmptySquare;
4779 board[toY][5] = BlackRook;
4780 } else if (fromY == 7 && fromX == 4
4781 && board[fromY][fromX] == BlackKing
4782 && toY == 7 && toX == 2) {
4783 board[fromY][fromX] = EmptySquare;
4784 board[toY][toX] = BlackKing;
4785 board[fromY][0] = EmptySquare;
4786 board[toY][3] = BlackRook;
4787 } else if (fromY == 7 && fromX == 3
4788 && board[fromY][fromX] == BlackKing
4789 && toY == 7 && toX == 5) {
4790 board[fromY][fromX] = EmptySquare;
4791 board[toY][toX] = BlackKing;
4792 board[fromY][7] = EmptySquare;
4793 board[toY][4] = BlackRook;
4794 } else if (fromY == 7 && fromX == 3
4795 && board[fromY][fromX] == BlackKing
4796 && toY == 7 && toX == 1) {
4797 board[fromY][fromX] = EmptySquare;
4798 board[toY][toX] = BlackKing;
4799 board[fromY][0] = EmptySquare;
4800 board[toY][2] = BlackRook;
4801 } else if (board[fromY][fromX] == BlackPawn
4803 /* black pawn promotion */
4804 board[0][toX] = CharToPiece(ToLower(promoChar));
4805 if (board[0][toX] == EmptySquare) {
4806 board[0][toX] = BlackQueen;
4808 board[fromY][fromX] = EmptySquare;
4809 } else if ((fromY == 3)
4811 && (board[fromY][fromX] == BlackPawn)
4812 && (board[toY][toX] == EmptySquare)) {
4813 board[fromY][fromX] = EmptySquare;
4814 board[toY][toX] = BlackPawn;
4815 captured = board[toY + 1][toX];
4816 board[toY + 1][toX] = EmptySquare;
4818 board[toY][toX] = board[fromY][fromX];
4819 board[fromY][fromX] = EmptySquare;
4821 if (gameInfo.variant == VariantCrazyhouse) {
4823 /* !!A lot more code needs to be written to support holdings */
4824 if (fromY == DROP_RANK) {
4825 /* Delete from holdings */
4826 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4828 if (captured != EmptySquare) {
4829 /* Add to holdings */
4830 if (captured < BlackPawn) {
4831 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4833 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4837 } else if (gameInfo.variant == VariantAtomic) {
4838 if (captured != EmptySquare) {
4840 for (y = toY-1; y <= toY+1; y++) {
4841 for (x = toX-1; x <= toX+1; x++) {
4842 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4843 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4844 board[y][x] = EmptySquare;
4848 board[toY][toX] = EmptySquare;
4853 /* Updates forwardMostMove */
4855 MakeMove(fromX, fromY, toX, toY, promoChar)
4856 int fromX, fromY, toX, toY;
4860 if (forwardMostMove >= MAX_MOVES) {
4861 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4866 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4867 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4868 if (commentList[forwardMostMove] != NULL) {
4869 free(commentList[forwardMostMove]);
4870 commentList[forwardMostMove] = NULL;
4872 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4873 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4874 gameInfo.result = GameUnfinished;
4875 if (gameInfo.resultDetails != NULL) {
4876 free(gameInfo.resultDetails);
4877 gameInfo.resultDetails = NULL;
4879 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4880 moveList[forwardMostMove - 1]);
4881 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4882 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4883 fromY, fromX, toY, toX, promoChar,
4884 parseList[forwardMostMove - 1]);
4885 switch (MateTest(boards[forwardMostMove],
4886 PosFlags(forwardMostMove), EP_UNKNOWN)){
4892 strcat(parseList[forwardMostMove - 1], "+");
4895 strcat(parseList[forwardMostMove - 1], "#");
4900 /* Updates currentMove if not pausing */
4902 ShowMove(fromX, fromY, toX, toY)
4904 int instant = (gameMode == PlayFromGameFile) ?
4905 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4906 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4908 if (forwardMostMove == currentMove + 1) {
4909 AnimateMove(boards[forwardMostMove - 1],
4910 fromX, fromY, toX, toY);
4912 if (appData.highlightLastMove) {
4913 SetHighlights(fromX, fromY, toX, toY);
4916 currentMove = forwardMostMove;
4919 if (instant) return;
4920 DisplayMove(currentMove - 1);
4921 DrawPosition(FALSE, boards[currentMove]);
4922 DisplayBothClocks();
4923 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4928 InitChessProgram(cps)
4929 ChessProgramState *cps;
4932 if (appData.noChessProgram) return;
4933 hintRequested = FALSE;
4934 bookRequested = FALSE;
4935 SendToProgram(cps->initString, cps);
4936 if (gameInfo.variant != VariantNormal &&
4937 gameInfo.variant != VariantLoadable) {
4938 char *v = VariantName(gameInfo.variant);
4939 if (StrStr(cps->variants, v) == NULL) {
4940 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4941 DisplayFatalError(buf, 0, 1);
4944 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4945 SendToProgram(buf, cps);
4948 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4949 SendToProgram(buf, cps);
4951 cps->maybeThinking = FALSE;
4952 cps->offeredDraw = 0;
4953 if (!appData.icsActive) {
4954 SendTimeControl(cps, movesPerSession, timeControl,
4955 timeIncrement, appData.searchDepth,
4958 if (appData.showThinking) {
4959 SendToProgram("post\n", cps);
4961 SendToProgram("hard\n", cps);
4962 if (!appData.ponderNextMove) {
4963 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4964 it without being sure what state we are in first. "hard"
4965 is not a toggle, so that one is OK.
4967 SendToProgram("easy\n", cps);
4970 sprintf(buf, "ping %d\n", ++cps->lastPing);
4971 SendToProgram(buf, cps);
4973 cps->initDone = TRUE;
4978 StartChessProgram(cps)
4979 ChessProgramState *cps;
4984 if (appData.noChessProgram) return;
4985 cps->initDone = FALSE;
4987 if (strcmp(cps->host, "localhost") == 0) {
4988 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4989 } else if (*appData.remoteShell == NULLCHAR) {
4990 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4992 if (*appData.remoteUser == NULLCHAR) {
4993 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4996 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
4997 cps->host, appData.remoteUser, cps->program);
4999 err = StartChildProcess(buf, "", &cps->pr);
5003 sprintf(buf, "Startup failure on '%s'", cps->program);
5004 DisplayFatalError(buf, err, 1);
5010 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5011 if (cps->protocolVersion > 1) {
5012 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5013 SendToProgram(buf, cps);
5015 SendToProgram("xboard\n", cps);
5021 TwoMachinesEventIfReady P((void))
5023 if (first.lastPing != first.lastPong) {
5024 DisplayMessage("", "Waiting for first chess program");
5025 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5028 if (second.lastPing != second.lastPong) {
5029 DisplayMessage("", "Waiting for second chess program");
5030 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5038 NextMatchGame P((void))
5041 if (*appData.loadGameFile != NULLCHAR) {
5042 LoadGameFromFile(appData.loadGameFile,
5043 appData.loadGameIndex,
5044 appData.loadGameFile, FALSE);
5045 } else if (*appData.loadPositionFile != NULLCHAR) {
5046 LoadPositionFromFile(appData.loadPositionFile,
5047 appData.loadPositionIndex,
5048 appData.loadPositionFile);
5050 TwoMachinesEventIfReady();
5054 GameEnds(result, resultDetails, whosays)
5056 char *resultDetails;
5059 GameMode nextGameMode;
5062 if (appData.debugMode) {
5063 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5064 result, resultDetails ? resultDetails : "(null)", whosays);
5067 if (appData.icsActive && whosays == GE_ENGINE) {
5068 /* If we are playing on ICS, the server decides when the
5069 game is over, but the engine can offer to draw, claim
5073 if (appData.zippyPlay && first.initDone) {
5074 if (result == GameIsDrawn) {
5075 /* In case draw still needs to be claimed */
5076 SendToICS(ics_prefix);
5077 SendToICS("draw\n");
5078 } else if (StrCaseStr(resultDetails, "resign")) {
5079 SendToICS(ics_prefix);
5080 SendToICS("resign\n");
5087 /* If we're loading the game from a file, stop */
5088 if (whosays == GE_FILE) {
5089 (void) StopLoadGameTimer();
5093 /* Cancel draw offers */
5094 first.offeredDraw = second.offeredDraw = 0;
5096 /* If this is an ICS game, only ICS can really say it's done;
5097 if not, anyone can. */
5098 isIcsGame = (gameMode == IcsPlayingWhite ||
5099 gameMode == IcsPlayingBlack ||
5100 gameMode == IcsObserving ||
5101 gameMode == IcsExamining);
5103 if (!isIcsGame || whosays == GE_ICS) {
5104 /* OK -- not an ICS game, or ICS said it was done */
5106 if (!isIcsGame && !appData.noChessProgram)
5107 SetUserThinkingEnables();
5109 if (resultDetails != NULL) {
5110 gameInfo.result = result;
5111 gameInfo.resultDetails = StrSave(resultDetails);
5113 /* Tell program how game ended in case it is learning */
5114 if (gameMode == MachinePlaysWhite ||
5115 gameMode == MachinePlaysBlack ||
5116 gameMode == TwoMachinesPlay ||
5117 gameMode == IcsPlayingWhite ||
5118 gameMode == IcsPlayingBlack ||
5119 gameMode == BeginningOfGame) {
5121 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5123 if (first.pr != NoProc) {
5124 SendToProgram(buf, &first);
5126 if (second.pr != NoProc &&
5127 gameMode == TwoMachinesPlay) {
5128 SendToProgram(buf, &second);
5132 /* display last move only if game was not loaded from file */
5133 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5134 DisplayMove(currentMove - 1);
5136 if (forwardMostMove != 0) {
5137 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5138 if (*appData.saveGameFile != NULLCHAR) {
5139 SaveGameToFile(appData.saveGameFile, TRUE);
5140 } else if (appData.autoSaveGames) {
5143 if (*appData.savePositionFile != NULLCHAR) {
5144 SavePositionToFile(appData.savePositionFile);
5150 if (appData.icsActive) {
5151 if (appData.quietPlay &&
5152 (gameMode == IcsPlayingWhite ||
5153 gameMode == IcsPlayingBlack)) {
5154 SendToICS(ics_prefix);
5155 SendToICS("set shout 1\n");
5157 nextGameMode = IcsIdle;
5158 ics_user_moved = FALSE;
5159 /* clean up premove. It's ugly when the game has ended and the
5160 * premove highlights are still on the board.
5164 ClearPremoveHighlights();
5165 DrawPosition(FALSE, boards[currentMove]);
5167 if (whosays == GE_ICS) {
5170 if (gameMode == IcsPlayingWhite)
5172 else if(gameMode == IcsPlayingBlack)
5176 if (gameMode == IcsPlayingBlack)
5178 else if(gameMode == IcsPlayingWhite)
5185 PlayIcsUnfinishedSound();
5188 } else if (gameMode == EditGame ||
5189 gameMode == PlayFromGameFile ||
5190 gameMode == AnalyzeMode ||
5191 gameMode == AnalyzeFile) {
5192 nextGameMode = gameMode;
5194 nextGameMode = EndOfGame;
5199 nextGameMode = gameMode;
5202 if (appData.noChessProgram) {
5203 gameMode = nextGameMode;
5209 /* Put first chess program into idle state */
5210 if (first.pr != NoProc &&
5211 (gameMode == MachinePlaysWhite ||
5212 gameMode == MachinePlaysBlack ||
5213 gameMode == TwoMachinesPlay ||
5214 gameMode == IcsPlayingWhite ||
5215 gameMode == IcsPlayingBlack ||
5216 gameMode == BeginningOfGame)) {
5217 SendToProgram("force\n", &first);
5218 if (first.usePing) {
5220 sprintf(buf, "ping %d\n", ++first.lastPing);
5221 SendToProgram(buf, &first);
5224 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5225 /* Kill off first chess program */
5226 if (first.isr != NULL)
5227 RemoveInputSource(first.isr);
5230 if (first.pr != NoProc) {
5232 SendToProgram("quit\n", &first);
5233 DestroyChildProcess(first.pr, first.useSigterm);
5238 /* Put second chess program into idle state */
5239 if (second.pr != NoProc &&
5240 gameMode == TwoMachinesPlay) {
5241 SendToProgram("force\n", &second);
5242 if (second.usePing) {
5244 sprintf(buf, "ping %d\n", ++second.lastPing);
5245 SendToProgram(buf, &second);
5248 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5249 /* Kill off second chess program */
5250 if (second.isr != NULL)
5251 RemoveInputSource(second.isr);
5254 if (second.pr != NoProc) {
5255 SendToProgram("quit\n", &second);
5256 DestroyChildProcess(second.pr, second.useSigterm);
5261 if (matchMode && gameMode == TwoMachinesPlay) {
5264 if (first.twoMachinesColor[0] == 'w') {
5271 if (first.twoMachinesColor[0] == 'b') {
5280 if (matchGame < appData.matchGames) {
5282 tmp = first.twoMachinesColor;
5283 first.twoMachinesColor = second.twoMachinesColor;
5284 second.twoMachinesColor = tmp;
5285 gameMode = nextGameMode;
5287 ScheduleDelayedEvent(NextMatchGame, 10000);
5291 gameMode = nextGameMode;
5292 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5293 first.tidy, second.tidy,
5294 first.matchWins, second.matchWins,
5295 appData.matchGames - (first.matchWins + second.matchWins));
5296 DisplayFatalError(buf, 0, 0);
5299 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5300 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5302 gameMode = nextGameMode;
5306 /* Assumes program was just initialized (initString sent).
5307 Leaves program in force mode. */
5309 FeedMovesToProgram(cps, upto)
5310 ChessProgramState *cps;
5315 if (appData.debugMode)
5316 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5317 startedFromSetupPosition ? "position and " : "",
5318 backwardMostMove, upto, cps->which);
5319 SendToProgram("force\n", cps);
5320 if (startedFromSetupPosition) {
5321 SendBoard(cps, backwardMostMove);
5323 for (i = backwardMostMove; i < upto; i++) {
5324 SendMoveToProgram(i, cps);
5330 ResurrectChessProgram()
5332 /* The chess program may have exited.
5333 If so, restart it and feed it all the moves made so far. */
5335 if (appData.noChessProgram || first.pr != NoProc) return;
5337 StartChessProgram(&first);
5338 InitChessProgram(&first);
5339 FeedMovesToProgram(&first, currentMove);
5341 if (!first.sendTime) {
5342 /* can't tell gnuchess what its clock should read,
5343 so we bow to its notion. */
5345 timeRemaining[0][currentMove] = whiteTimeRemaining;
5346 timeRemaining[1][currentMove] = blackTimeRemaining;
5349 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
5350 appData.icsEngineAnalyze) && first.analysisSupport) {
5351 SendToProgram("analyze\n", &first);
5352 first.analyzing = TRUE;
5365 if (appData.debugMode) {
5366 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5367 redraw, init, gameMode);
5370 pausing = pauseExamInvalid = FALSE;
5371 startedFromSetupPosition = blackPlaysFirst = FALSE;
5373 whiteFlag = blackFlag = FALSE;
5374 userOfferedDraw = FALSE;
5375 hintRequested = bookRequested = FALSE;
5376 first.maybeThinking = FALSE;
5377 second.maybeThinking = FALSE;
5378 thinkOutput[0] = NULLCHAR;
5379 lastHint[0] = NULLCHAR;
5380 ClearGameInfo(&gameInfo);
5381 gameInfo.variant = StringToVariant(appData.variant);
5382 ics_user_moved = ics_clock_paused = FALSE;
5383 ics_getting_history = H_FALSE;
5385 white_holding[0] = black_holding[0] = NULLCHAR;
5386 ClearProgramStats();
5390 flipView = appData.flipView;
5391 ClearPremoveHighlights();
5393 alarmSounded = FALSE;
5395 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5397 gameMode = BeginningOfGame;
5399 InitPosition(redraw);
5400 for (i = 0; i < MAX_MOVES; i++) {
5401 if (commentList[i] != NULL) {
5402 free(commentList[i]);
5403 commentList[i] = NULL;
5407 timeRemaining[0][0] = whiteTimeRemaining;
5408 timeRemaining[1][0] = blackTimeRemaining;
5409 if (first.pr == NULL) {
5410 StartChessProgram(&first);
5412 if (init) InitChessProgram(&first);
5414 DisplayMessage("", "");
5415 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5422 if (!AutoPlayOneMove())
5424 if (matchMode || appData.timeDelay == 0)
5426 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5428 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5437 int fromX, fromY, toX, toY;
5439 if (appData.debugMode) {
5440 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5443 if (gameMode != PlayFromGameFile)
5446 if (currentMove >= forwardMostMove) {
5447 gameMode = EditGame;
5452 toX = moveList[currentMove][2] - 'a';
5453 toY = moveList[currentMove][3] - '1';
5455 if (moveList[currentMove][1] == '@') {
5456 if (appData.highlightLastMove) {
5457 SetHighlights(-1, -1, toX, toY);
5460 fromX = moveList[currentMove][0] - 'a';
5461 fromY = moveList[currentMove][1] - '1';
5462 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5464 if (appData.highlightLastMove) {
5465 SetHighlights(fromX, fromY, toX, toY);
5468 DisplayMove(currentMove);
5469 SendMoveToProgram(currentMove++, &first);
5470 DisplayBothClocks();
5471 DrawPosition(FALSE, boards[currentMove]);
5472 if (commentList[currentMove] != NULL) {
5473 DisplayComment(currentMove - 1, commentList[currentMove]);
5480 LoadGameOneMove(readAhead)
5481 ChessMove readAhead;
5483 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5484 char promoChar = NULLCHAR;
5489 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5490 gameMode != AnalyzeMode && gameMode != Training) {
5495 yyboardindex = forwardMostMove;
5496 if (readAhead != (ChessMove)0) {
5497 moveType = readAhead;
5499 if (gameFileFP == NULL)
5501 moveType = (ChessMove) yylex();
5507 if (appData.debugMode)
5508 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5510 if (*p == '{' || *p == '[' || *p == '(') {
5511 p[strlen(p) - 1] = NULLCHAR;
5515 /* append the comment but don't display it */
5516 while (*p == '\n') p++;
5517 AppendComment(currentMove, p);
5520 case WhiteCapturesEnPassant:
5521 case BlackCapturesEnPassant:
5522 case WhitePromotionQueen:
5523 case BlackPromotionQueen:
5524 case WhitePromotionRook:
5525 case BlackPromotionRook:
5526 case WhitePromotionBishop:
5527 case BlackPromotionBishop:
5528 case WhitePromotionKnight:
5529 case BlackPromotionKnight:
5530 case WhitePromotionKing:
5531 case BlackPromotionKing:
5533 case WhiteKingSideCastle:
5534 case WhiteQueenSideCastle:
5535 case BlackKingSideCastle:
5536 case BlackQueenSideCastle:
5537 case WhiteKingSideCastleWild:
5538 case WhiteQueenSideCastleWild:
5539 case BlackKingSideCastleWild:
5540 case BlackQueenSideCastleWild:
5541 if (appData.debugMode)
5542 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5543 fromX = currentMoveString[0] - 'a';
5544 fromY = currentMoveString[1] - '1';
5545 toX = currentMoveString[2] - 'a';
5546 toY = currentMoveString[3] - '1';
5547 promoChar = currentMoveString[4];
5552 if (appData.debugMode)
5553 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5554 fromX = moveType == WhiteDrop ?
5555 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5556 (int) CharToPiece(ToLower(currentMoveString[0]));
5558 toX = currentMoveString[2] - 'a';
5559 toY = currentMoveString[3] - '1';
5565 case GameUnfinished:
5566 if (appData.debugMode)
5567 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5568 p = strchr(yy_text, '{');
5569 if (p == NULL) p = strchr(yy_text, '(');
5572 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5574 q = strchr(p, *p == '{' ? '}' : ')');
5575 if (q != NULL) *q = NULLCHAR;
5578 GameEnds(moveType, p, GE_FILE);
5580 if (cmailMsgLoaded) {
5582 flipView = WhiteOnMove(currentMove);
5583 if (moveType == GameUnfinished) flipView = !flipView;
5584 if (appData.debugMode)
5585 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5589 case (ChessMove) 0: /* end of file */
5590 if (appData.debugMode)
5591 fprintf(debugFP, "Parser hit end of file\n");
5592 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5598 if (WhiteOnMove(currentMove)) {
5599 GameEnds(BlackWins, "Black mates", GE_FILE);
5601 GameEnds(WhiteWins, "White mates", GE_FILE);
5605 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5612 if (lastLoadGameStart == GNUChessGame) {
5613 /* GNUChessGames have numbers, but they aren't move numbers */
5614 if (appData.debugMode)
5615 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5616 yy_text, (int) moveType);
5617 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5619 /* else fall thru */
5624 /* Reached start of next game in file */
5625 if (appData.debugMode)
5626 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5627 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5633 if (WhiteOnMove(currentMove)) {
5634 GameEnds(BlackWins, "Black mates", GE_FILE);
5636 GameEnds(WhiteWins, "White mates", GE_FILE);
5640 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5646 case PositionDiagram: /* should not happen; ignore */
5647 case ElapsedTime: /* ignore */
5648 case NAG: /* ignore */
5649 if (appData.debugMode)
5650 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5651 yy_text, (int) moveType);
5652 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5655 if (appData.testLegality) {
5656 if (appData.debugMode)
5657 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5658 sprintf(move, _("Illegal move: %d.%s%s"),
5659 (forwardMostMove / 2) + 1,
5660 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5661 DisplayError(move, 0);
5664 if (appData.debugMode)
5665 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5666 yy_text, currentMoveString);
5667 fromX = currentMoveString[0] - 'a';
5668 fromY = currentMoveString[1] - '1';
5669 toX = currentMoveString[2] - 'a';
5670 toY = currentMoveString[3] - '1';
5671 promoChar = currentMoveString[4];
5676 if (appData.debugMode)
5677 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5678 sprintf(move, _("Ambiguous move: %d.%s%s"),
5679 (forwardMostMove / 2) + 1,
5680 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5681 DisplayError(move, 0);
5686 case ImpossibleMove:
5687 if (appData.debugMode)
5688 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5689 sprintf(move, _("Illegal move: %d.%s%s"),
5690 (forwardMostMove / 2) + 1,
5691 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5692 DisplayError(move, 0);
5698 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5699 DrawPosition(FALSE, boards[currentMove]);
5700 DisplayBothClocks();
5701 if (!appData.matchMode && commentList[currentMove] != NULL)
5702 DisplayComment(currentMove - 1, commentList[currentMove]);
5704 (void) StopLoadGameTimer();
5706 cmailOldMove = forwardMostMove;
5709 /* currentMoveString is set as a side-effect of yylex */
5710 strcat(currentMoveString, "\n");
5711 strcpy(moveList[forwardMostMove], currentMoveString);
5713 thinkOutput[0] = NULLCHAR;
5714 MakeMove(fromX, fromY, toX, toY, promoChar);
5715 currentMove = forwardMostMove;
5720 /* Load the nth game from the given file */
5722 LoadGameFromFile(filename, n, title, useList)
5726 /*Boolean*/ int useList;
5731 if (strcmp(filename, "-") == 0) {
5735 f = fopen(filename, "rb");
5737 sprintf(buf, _("Can't open \"%s\""), filename);
5738 DisplayError(buf, errno);
5742 if (fseek(f, 0, 0) == -1) {
5743 /* f is not seekable; probably a pipe */
5746 if (useList && n == 0) {
5747 int error = GameListBuild(f);
5749 DisplayError(_("Cannot build game list"), error);
5750 } else if (!ListEmpty(&gameList) &&
5751 ((ListGame *) gameList.tailPred)->number > 1) {
5752 GameListPopUp(f, title);
5759 return LoadGame(f, n, title, FALSE);
5764 MakeRegisteredMove()
5766 int fromX, fromY, toX, toY;
5768 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5769 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5772 if (appData.debugMode)
5773 fprintf(debugFP, "Restoring %s for game %d\n",
5774 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5776 thinkOutput[0] = NULLCHAR;
5777 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5778 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5779 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5780 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5781 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5782 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5783 MakeMove(fromX, fromY, toX, toY, promoChar);
5784 ShowMove(fromX, fromY, toX, toY);
5786 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5793 if (WhiteOnMove(currentMove)) {
5794 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5796 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5801 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5808 if (WhiteOnMove(currentMove)) {
5809 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5811 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5816 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5827 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5829 CmailLoadGame(f, gameNumber, title, useList)
5837 if (gameNumber > nCmailGames) {
5838 DisplayError(_("No more games in this message"), 0);
5841 if (f == lastLoadGameFP) {
5842 int offset = gameNumber - lastLoadGameNumber;
5844 cmailMsg[0] = NULLCHAR;
5845 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5846 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5847 nCmailMovesRegistered--;
5849 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5850 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5851 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5854 if (! RegisterMove()) return FALSE;
5858 retVal = LoadGame(f, gameNumber, title, useList);
5860 /* Make move registered during previous look at this game, if any */
5861 MakeRegisteredMove();
5863 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5864 commentList[currentMove]
5865 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5866 DisplayComment(currentMove - 1, commentList[currentMove]);
5872 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5877 int gameNumber = lastLoadGameNumber + offset;
5878 if (lastLoadGameFP == NULL) {
5879 DisplayError(_("No game has been loaded yet"), 0);
5882 if (gameNumber <= 0) {
5883 DisplayError(_("Can't back up any further"), 0);
5886 if (cmailMsgLoaded) {
5887 return CmailLoadGame(lastLoadGameFP, gameNumber,
5888 lastLoadGameTitle, lastLoadGameUseList);
5890 return LoadGame(lastLoadGameFP, gameNumber,
5891 lastLoadGameTitle, lastLoadGameUseList);
5897 /* Load the nth game from open file f */
5899 LoadGame(f, gameNumber, title, useList)
5907 int gn = gameNumber;
5908 ListGame *lg = NULL;
5911 GameMode oldGameMode;
5913 if (appData.debugMode)
5914 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5916 if (gameMode == Training )
5917 SetTrainingModeOff();
5919 oldGameMode = gameMode;
5920 if (gameMode != BeginningOfGame) {
5925 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5926 fclose(lastLoadGameFP);
5930 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5933 fseek(f, lg->offset, 0);
5934 GameListHighlight(gameNumber);
5938 DisplayError(_("Game number out of range"), 0);
5943 if (fseek(f, 0, 0) == -1) {
5944 if (f == lastLoadGameFP ?
5945 gameNumber == lastLoadGameNumber + 1 :
5949 DisplayError(_("Can't seek on game file"), 0);
5955 lastLoadGameNumber = gameNumber;
5956 strcpy(lastLoadGameTitle, title);
5957 lastLoadGameUseList = useList;
5962 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5963 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5964 lg->gameInfo.black);
5966 } else if (*title != NULLCHAR) {
5967 if (gameNumber > 1) {
5968 sprintf(buf, "%s %d", title, gameNumber);
5971 DisplayTitle(title);
5975 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5976 gameMode = PlayFromGameFile;
5980 currentMove = forwardMostMove = backwardMostMove = 0;
5981 CopyBoard(boards[0], initialPosition);
5985 * Skip the first gn-1 games in the file.
5986 * Also skip over anything that precedes an identifiable
5987 * start of game marker, to avoid being confused by
5988 * garbage at the start of the file. Currently
5989 * recognized start of game markers are the move number "1",
5990 * the pattern "gnuchess .* game", the pattern
5991 * "^[#;%] [^ ]* game file", and a PGN tag block.
5992 * A game that starts with one of the latter two patterns
5993 * will also have a move number 1, possibly
5994 * following a position diagram.
5995 * 5-4-02: Let's try being more lenient and allowing a game to
5996 * start with an unnumbered move. Does that break anything?
5998 cm = lastLoadGameStart = (ChessMove) 0;
6000 yyboardindex = forwardMostMove;
6001 cm = (ChessMove) yylex();
6004 if (cmailMsgLoaded) {
6005 nCmailGames = CMAIL_MAX_GAMES - gn;
6008 DisplayError(_("Game not found in file"), 0);
6015 lastLoadGameStart = cm;
6019 switch (lastLoadGameStart) {
6026 gn--; /* count this game */
6027 lastLoadGameStart = cm;
6036 switch (lastLoadGameStart) {
6041 gn--; /* count this game */
6042 lastLoadGameStart = cm;
6045 lastLoadGameStart = cm; /* game counted already */
6053 yyboardindex = forwardMostMove;
6054 cm = (ChessMove) yylex();
6055 } while (cm == PGNTag || cm == Comment);
6062 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6063 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6064 != CMAIL_OLD_RESULT) {
6066 cmailResult[ CMAIL_MAX_GAMES
6067 - gn - 1] = CMAIL_OLD_RESULT;
6073 /* Only a NormalMove can be at the start of a game
6074 * without a position diagram. */
6075 if (lastLoadGameStart == (ChessMove) 0) {
6077 lastLoadGameStart = MoveNumberOne;
6086 if (appData.debugMode)
6087 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6089 if (cm == XBoardGame) {
6090 /* Skip any header junk before position diagram and/or move 1 */
6092 yyboardindex = forwardMostMove;
6093 cm = (ChessMove) yylex();
6095 if (cm == (ChessMove) 0 ||
6096 cm == GNUChessGame || cm == XBoardGame) {
6097 /* Empty game; pretend end-of-file and handle later */
6102 if (cm == MoveNumberOne || cm == PositionDiagram ||
6103 cm == PGNTag || cm == Comment)
6106 } else if (cm == GNUChessGame) {
6107 if (gameInfo.event != NULL) {
6108 free(gameInfo.event);
6110 gameInfo.event = StrSave(yy_text);
6113 startedFromSetupPosition = FALSE;
6114 while (cm == PGNTag) {
6115 if (appData.debugMode)
6116 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6117 err = ParsePGNTag(yy_text, &gameInfo);
6118 if (!err) numPGNTags++;
6120 if (gameInfo.fen != NULL) {
6121 Board initial_position;
6122 startedFromSetupPosition = TRUE;
6123 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6125 DisplayError(_("Bad FEN position in file"), 0);
6128 CopyBoard(boards[0], initial_position);
6129 if (blackPlaysFirst) {
6130 currentMove = forwardMostMove = backwardMostMove = 1;
6131 CopyBoard(boards[1], initial_position);
6132 strcpy(moveList[0], "");
6133 strcpy(parseList[0], "");
6134 timeRemaining[0][1] = whiteTimeRemaining;
6135 timeRemaining[1][1] = blackTimeRemaining;
6136 if (commentList[0] != NULL) {
6137 commentList[1] = commentList[0];
6138 commentList[0] = NULL;
6141 currentMove = forwardMostMove = backwardMostMove = 0;
6143 yyboardindex = forwardMostMove;
6145 gameInfo.fen = NULL;
6148 yyboardindex = forwardMostMove;
6149 cm = (ChessMove) yylex();
6151 /* Handle comments interspersed among the tags */
6152 while (cm == Comment) {
6154 if (appData.debugMode)
6155 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6157 if (*p == '{' || *p == '[' || *p == '(') {
6158 p[strlen(p) - 1] = NULLCHAR;
6161 while (*p == '\n') p++;
6162 AppendComment(currentMove, p);
6163 yyboardindex = forwardMostMove;
6164 cm = (ChessMove) yylex();
6168 /* don't rely on existence of Event tag since if game was
6169 * pasted from clipboard the Event tag may not exist
6171 if (numPGNTags > 0){
6173 if (gameInfo.variant == VariantNormal) {
6174 gameInfo.variant = StringToVariant(gameInfo.event);
6177 tags = PGNTags(&gameInfo);
6178 TagsPopUp(tags, CmailMsg());
6182 /* Make something up, but don't display it now */
6187 if (cm == PositionDiagram) {
6190 Board initial_position;
6192 if (appData.debugMode)
6193 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6195 if (!startedFromSetupPosition) {
6197 for (i = BOARD_SIZE - 1; i >= 0; i--)
6198 for (j = 0; j < BOARD_SIZE; p++)
6208 initial_position[i][j++] = CharToPiece(*p);
6211 while (*p == ' ' || *p == '\t' ||
6212 *p == '\n' || *p == '\r') p++;
6214 if (strncmp(p, "black", strlen("black"))==0)
6215 blackPlaysFirst = TRUE;
6217 blackPlaysFirst = FALSE;
6218 startedFromSetupPosition = TRUE;
6220 CopyBoard(boards[0], initial_position);
6221 if (blackPlaysFirst) {
6222 currentMove = forwardMostMove = backwardMostMove = 1;
6223 CopyBoard(boards[1], initial_position);
6224 strcpy(moveList[0], "");
6225 strcpy(parseList[0], "");
6226 timeRemaining[0][1] = whiteTimeRemaining;
6227 timeRemaining[1][1] = blackTimeRemaining;
6228 if (commentList[0] != NULL) {
6229 commentList[1] = commentList[0];
6230 commentList[0] = NULL;
6233 currentMove = forwardMostMove = backwardMostMove = 0;
6236 yyboardindex = forwardMostMove;
6237 cm = (ChessMove) yylex();
6240 if (first.pr == NoProc) {
6241 StartChessProgram(&first);
6243 InitChessProgram(&first);
6244 SendToProgram("force\n", &first);
6245 if (startedFromSetupPosition) {
6246 SendBoard(&first, forwardMostMove);
6247 DisplayBothClocks();
6250 while (cm == Comment) {
6252 if (appData.debugMode)
6253 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6255 if (*p == '{' || *p == '[' || *p == '(') {
6256 p[strlen(p) - 1] = NULLCHAR;
6259 while (*p == '\n') p++;
6260 AppendComment(currentMove, p);
6261 yyboardindex = forwardMostMove;
6262 cm = (ChessMove) yylex();
6265 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6266 cm == WhiteWins || cm == BlackWins ||
6267 cm == GameIsDrawn || cm == GameUnfinished) {
6268 DisplayMessage("", _("No moves in game"));
6269 if (cmailMsgLoaded) {
6270 if (appData.debugMode)
6271 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6275 DrawPosition(FALSE, boards[currentMove]);
6276 DisplayBothClocks();
6277 gameMode = EditGame;
6284 if (commentList[currentMove] != NULL) {
6285 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6286 DisplayComment(currentMove - 1, commentList[currentMove]);
6289 if (!matchMode && appData.timeDelay != 0)
6290 DrawPosition(FALSE, boards[currentMove]);
6292 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6293 programStats.ok_to_send = 1;
6296 /* if the first token after the PGN tags is a move
6297 * and not move number 1, retrieve it from the parser
6299 if (cm != MoveNumberOne)
6300 LoadGameOneMove(cm);
6302 /* load the remaining moves from the file */
6303 while (LoadGameOneMove((ChessMove)0)) {
6304 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6305 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6308 /* rewind to the start of the game */
6309 currentMove = backwardMostMove;
6311 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6313 if (oldGameMode == AnalyzeFile ||
6314 oldGameMode == AnalyzeMode) {
6318 if (matchMode || appData.timeDelay == 0) {
6320 gameMode = EditGame;
6322 } else if (appData.timeDelay > 0) {
6326 if (appData.debugMode)
6327 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6331 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6333 ReloadPosition(offset)
6336 int positionNumber = lastLoadPositionNumber + offset;
6337 if (lastLoadPositionFP == NULL) {
6338 DisplayError(_("No position has been loaded yet"), 0);
6341 if (positionNumber <= 0) {
6342 DisplayError(_("Can't back up any further"), 0);
6345 return LoadPosition(lastLoadPositionFP, positionNumber,
6346 lastLoadPositionTitle);
6349 /* Load the nth position from the given file */
6351 LoadPositionFromFile(filename, n, title)
6359 if (strcmp(filename, "-") == 0) {
6360 return LoadPosition(stdin, n, "stdin");
6362 f = fopen(filename, "rb");
6364 sprintf(buf, _("Can't open \"%s\""), filename);
6365 DisplayError(buf, errno);
6368 return LoadPosition(f, n, title);
6373 /* Load the nth position from the given open file, and close it */
6375 LoadPosition(f, positionNumber, title)
6380 char *p, line[MSG_SIZ];
6381 Board initial_position;
6382 int i, j, fenMode, pn;
6384 if (gameMode == Training )
6385 SetTrainingModeOff();
6387 if (gameMode != BeginningOfGame) {
6390 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6391 fclose(lastLoadPositionFP);
6393 if (positionNumber == 0) positionNumber = 1;
6394 lastLoadPositionFP = f;
6395 lastLoadPositionNumber = positionNumber;
6396 strcpy(lastLoadPositionTitle, title);
6397 if (first.pr == NoProc) {
6398 StartChessProgram(&first);
6399 InitChessProgram(&first);
6401 pn = positionNumber;
6402 if (positionNumber < 0) {
6403 /* Negative position number means to seek to that byte offset */
6404 if (fseek(f, -positionNumber, 0) == -1) {
6405 DisplayError(_("Can't seek on position file"), 0);
6410 if (fseek(f, 0, 0) == -1) {
6411 if (f == lastLoadPositionFP ?
6412 positionNumber == lastLoadPositionNumber + 1 :
6413 positionNumber == 1) {
6416 DisplayError(_("Can't seek on position file"), 0);
6421 /* See if this file is FEN or old-style xboard */
6422 if (fgets(line, MSG_SIZ, f) == NULL) {
6423 DisplayError(_("Position not found in file"), 0);
6431 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6432 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6433 case '1': case '2': case '3': case '4': case '5': case '6':
6440 if (fenMode || line[0] == '#') pn--;
6442 /* skip postions before number pn */
6443 if (fgets(line, MSG_SIZ, f) == NULL) {
6445 DisplayError(_("Position not found in file"), 0);
6448 if (fenMode || line[0] == '#') pn--;
6453 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6454 DisplayError(_("Bad FEN position in file"), 0);
6458 (void) fgets(line, MSG_SIZ, f);
6459 (void) fgets(line, MSG_SIZ, f);
6461 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6462 (void) fgets(line, MSG_SIZ, f);
6463 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6466 initial_position[i][j++] = CharToPiece(*p);
6470 blackPlaysFirst = FALSE;
6472 (void) fgets(line, MSG_SIZ, f);
6473 if (strncmp(line, "black", strlen("black"))==0)
6474 blackPlaysFirst = TRUE;
6477 startedFromSetupPosition = TRUE;
6479 SendToProgram("force\n", &first);
6480 CopyBoard(boards[0], initial_position);
6481 if (blackPlaysFirst) {
6482 currentMove = forwardMostMove = backwardMostMove = 1;
6483 strcpy(moveList[0], "");
6484 strcpy(parseList[0], "");
6485 CopyBoard(boards[1], initial_position);
6486 DisplayMessage("", _("Black to play"));
6488 currentMove = forwardMostMove = backwardMostMove = 0;
6489 DisplayMessage("", _("White to play"));
6491 SendBoard(&first, forwardMostMove);
6493 if (positionNumber > 1) {
6494 sprintf(line, "%s %d", title, positionNumber);
6497 DisplayTitle(title);
6499 gameMode = EditGame;
6502 timeRemaining[0][1] = whiteTimeRemaining;
6503 timeRemaining[1][1] = blackTimeRemaining;
6504 DrawPosition(FALSE, boards[currentMove]);
6511 CopyPlayerNameIntoFileName(dest, src)
6514 while (*src != NULLCHAR && *src != ',') {
6519 *(*dest)++ = *src++;
6524 char *DefaultFileName(ext)
6527 static char def[MSG_SIZ];
6530 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6532 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6534 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6543 /* Save the current game to the given file */
6545 SaveGameToFile(filename, append)
6552 if (strcmp(filename, "-") == 0) {
6553 return SaveGame(stdout, 0, NULL);
6555 f = fopen(filename, append ? "a" : "w");
6557 sprintf(buf, _("Can't open \"%s\""), filename);
6558 DisplayError(buf, errno);
6561 return SaveGame(f, 0, NULL);
6570 static char buf[MSG_SIZ];
6573 p = strchr(str, ' ');
6574 if (p == NULL) return str;
6575 strncpy(buf, str, p - str);
6576 buf[p - str] = NULLCHAR;
6580 #define PGN_MAX_LINE 75
6582 /* Save game in PGN style and close the file */
6587 int i, offset, linelen, newblock;
6591 int movelen, numlen, blank;
6593 tm = time((time_t *) NULL);
6595 PrintPGNTags(f, &gameInfo);
6597 if (backwardMostMove > 0 || startedFromSetupPosition) {
6598 char *fen = PositionToFEN(backwardMostMove);
6599 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6600 fprintf(f, "\n{--------------\n");
6601 PrintPosition(f, backwardMostMove);
6602 fprintf(f, "--------------}\n");
6608 i = backwardMostMove;
6609 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6613 while (i < forwardMostMove) {
6614 /* Print comments preceding this move */
6615 if (commentList[i] != NULL) {
6616 if (linelen > 0) fprintf(f, "\n");
6617 fprintf(f, "{\n%s}\n", commentList[i]);
6622 /* Format move number */
6624 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6627 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6629 numtext[0] = NULLCHAR;
6632 numlen = strlen(numtext);
6635 /* Print move number */
6636 blank = linelen > 0 && numlen > 0;
6637 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6646 fprintf(f, numtext);
6650 movetext = SavePart(parseList[i]);
6651 movelen = strlen(movetext);
6654 blank = linelen > 0 && movelen > 0;
6655 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6664 fprintf(f, movetext);
6670 /* Start a new line */
6671 if (linelen > 0) fprintf(f, "\n");
6673 /* Print comments after last move */
6674 if (commentList[i] != NULL) {
6675 fprintf(f, "{\n%s}\n", commentList[i]);
6679 if (gameInfo.resultDetails != NULL &&
6680 gameInfo.resultDetails[0] != NULLCHAR) {
6681 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6682 PGNResult(gameInfo.result));
6684 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6691 /* Save game in old style and close the file */
6699 tm = time((time_t *) NULL);
6701 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6704 if (backwardMostMove > 0 || startedFromSetupPosition) {
6705 fprintf(f, "\n[--------------\n");
6706 PrintPosition(f, backwardMostMove);
6707 fprintf(f, "--------------]\n");
6712 i = backwardMostMove;
6713 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6715 while (i < forwardMostMove) {
6716 if (commentList[i] != NULL) {
6717 fprintf(f, "[%s]\n", commentList[i]);
6721 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6724 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6726 if (commentList[i] != NULL) {
6730 if (i >= forwardMostMove) {
6734 fprintf(f, "%s\n", parseList[i]);
6739 if (commentList[i] != NULL) {
6740 fprintf(f, "[%s]\n", commentList[i]);
6743 /* This isn't really the old style, but it's close enough */
6744 if (gameInfo.resultDetails != NULL &&
6745 gameInfo.resultDetails[0] != NULLCHAR) {
6746 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6747 gameInfo.resultDetails);
6749 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6756 /* Save the current game to open file f and close the file */
6758 SaveGame(f, dummy, dummy2)
6763 if (gameMode == EditPosition) EditPositionDone();
6764 if (appData.oldSaveStyle)
6765 return SaveGameOldStyle(f);
6767 return SaveGamePGN(f);
6770 /* Save the current position to the given file */
6772 SavePositionToFile(filename)
6778 if (strcmp(filename, "-") == 0) {
6779 return SavePosition(stdout, 0, NULL);
6781 f = fopen(filename, "a");
6783 sprintf(buf, _("Can't open \"%s\""), filename);
6784 DisplayError(buf, errno);
6787 SavePosition(f, 0, NULL);
6793 /* Save the current position to the given open file and close the file */
6795 SavePosition(f, dummy, dummy2)
6803 if (appData.oldSaveStyle) {
6804 tm = time((time_t *) NULL);
6806 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6808 fprintf(f, "[--------------\n");
6809 PrintPosition(f, currentMove);
6810 fprintf(f, "--------------]\n");
6812 fen = PositionToFEN(currentMove);
6813 fprintf(f, "%s\n", fen);
6821 ReloadCmailMsgEvent(unregister)
6825 static char *inFilename = NULL;
6826 static char *outFilename;
6828 struct stat inbuf, outbuf;
6831 /* Any registered moves are unregistered if unregister is set, */
6832 /* i.e. invoked by the signal handler */
6834 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6835 cmailMoveRegistered[i] = FALSE;
6836 if (cmailCommentList[i] != NULL) {
6837 free(cmailCommentList[i]);
6838 cmailCommentList[i] = NULL;
6841 nCmailMovesRegistered = 0;
6844 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6845 cmailResult[i] = CMAIL_NOT_RESULT;
6849 if (inFilename == NULL) {
6850 /* Because the filenames are static they only get malloced once */
6851 /* and they never get freed */
6852 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6853 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6855 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6856 sprintf(outFilename, "%s.out", appData.cmailGameName);
6859 status = stat(outFilename, &outbuf);
6861 cmailMailedMove = FALSE;
6863 status = stat(inFilename, &inbuf);
6864 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6867 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6868 counts the games, notes how each one terminated, etc.
6870 It would be nice to remove this kludge and instead gather all
6871 the information while building the game list. (And to keep it
6872 in the game list nodes instead of having a bunch of fixed-size
6873 parallel arrays.) Note this will require getting each game's
6874 termination from the PGN tags, as the game list builder does
6875 not process the game moves. --mann
6877 cmailMsgLoaded = TRUE;
6878 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6880 /* Load first game in the file or popup game menu */
6881 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6891 char string[MSG_SIZ];
6893 if ( cmailMailedMove
6894 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6895 return TRUE; /* Allow free viewing */
6898 /* Unregister move to ensure that we don't leave RegisterMove */
6899 /* with the move registered when the conditions for registering no */
6901 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6902 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6903 nCmailMovesRegistered --;
6905 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6907 free(cmailCommentList[lastLoadGameNumber - 1]);
6908 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6912 if (cmailOldMove == -1) {
6913 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6917 if (currentMove > cmailOldMove + 1) {
6918 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6922 if (currentMove < cmailOldMove) {
6923 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6927 if (forwardMostMove > currentMove) {
6928 /* Silently truncate extra moves */
6932 if ( (currentMove == cmailOldMove + 1)
6933 || ( (currentMove == cmailOldMove)
6934 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6935 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6936 if (gameInfo.result != GameUnfinished) {
6937 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6940 if (commentList[currentMove] != NULL) {
6941 cmailCommentList[lastLoadGameNumber - 1]
6942 = StrSave(commentList[currentMove]);
6944 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6946 if (appData.debugMode)
6947 fprintf(debugFP, "Saving %s for game %d\n",
6948 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6951 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6953 f = fopen(string, "w");
6954 if (appData.oldSaveStyle) {
6955 SaveGameOldStyle(f); /* also closes the file */
6957 sprintf(string, "%s.pos.out", appData.cmailGameName);
6958 f = fopen(string, "w");
6959 SavePosition(f, 0, NULL); /* also closes the file */
6961 fprintf(f, "{--------------\n");
6962 PrintPosition(f, currentMove);
6963 fprintf(f, "--------------}\n\n");
6965 SaveGame(f, 0, NULL); /* also closes the file*/
6968 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6969 nCmailMovesRegistered ++;
6970 } else if (nCmailGames == 1) {
6971 DisplayError(_("You have not made a move yet"), 0);
6982 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6983 FILE *commandOutput;
6984 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6985 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6991 if (! cmailMsgLoaded) {
6992 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
6996 if (nCmailGames == nCmailResults) {
6997 DisplayError(_("No unfinished games"), 0);
7001 #if CMAIL_PROHIBIT_REMAIL
7002 if (cmailMailedMove) {
7003 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);
7004 DisplayError(msg, 0);
7009 if (! (cmailMailedMove || RegisterMove())) return;
7011 if ( cmailMailedMove
7012 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7013 sprintf(string, partCommandString,
7014 appData.debugMode ? " -v" : "", appData.cmailGameName);
7015 commandOutput = popen(string, "r");
7017 if (commandOutput == NULL) {
7018 DisplayError(_("Failed to invoke cmail"), 0);
7020 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7021 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7024 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7025 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7026 nBytes = MSG_SIZ - 1;
7028 (void) memcpy(msg, buffer, nBytes);
7030 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7032 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7033 cmailMailedMove = TRUE; /* Prevent >1 moves */
7036 for (i = 0; i < nCmailGames; i ++) {
7037 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7042 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7044 sprintf(buffer, "%s/%s.%s.archive",
7046 appData.cmailGameName,
7048 LoadGameFromFile(buffer, 1, buffer, FALSE);
7049 cmailMsgLoaded = FALSE;
7053 DisplayInformation(msg);
7054 pclose(commandOutput);
7057 if ((*cmailMsg) != '\0') {
7058 DisplayInformation(cmailMsg);
7072 int prependComma = 0;
7074 char string[MSG_SIZ]; /* Space for game-list */
7077 if (!cmailMsgLoaded) return "";
7079 if (cmailMailedMove) {
7080 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7082 /* Create a list of games left */
7083 sprintf(string, "[");
7084 for (i = 0; i < nCmailGames; i ++) {
7085 if (! ( cmailMoveRegistered[i]
7086 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7088 sprintf(number, ",%d", i + 1);
7090 sprintf(number, "%d", i + 1);
7094 strcat(string, number);
7097 strcat(string, "]");
7099 if (nCmailMovesRegistered + nCmailResults == 0) {
7100 switch (nCmailGames) {
7103 _("Still need to make move for game\n"));
7108 _("Still need to make moves for both games\n"));
7113 _("Still need to make moves for all %d games\n"),
7118 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7121 _("Still need to make a move for game %s\n"),
7126 if (nCmailResults == nCmailGames) {
7127 sprintf(cmailMsg, _("No unfinished games\n"));
7129 sprintf(cmailMsg, _("Ready to send mail\n"));
7135 _("Still need to make moves for games %s\n"),
7147 if (gameMode == Training)
7148 SetTrainingModeOff();
7151 cmailMsgLoaded = FALSE;
7152 if (appData.icsActive) {
7153 SendToICS(ics_prefix);
7154 SendToICS("refresh\n");
7158 static int exiting = 0;
7166 /* Give up on clean exit */
7170 /* Keep trying for clean exit */
7174 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7176 if (telnetISR != NULL) {
7177 RemoveInputSource(telnetISR);
7179 if (icsPR != NoProc) {
7180 DestroyChildProcess(icsPR, TRUE);
7182 /* Save game if resource set and not already saved by GameEnds() */
7183 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7184 if (*appData.saveGameFile != NULLCHAR) {
7185 SaveGameToFile(appData.saveGameFile, TRUE);
7186 } else if (appData.autoSaveGames) {
7189 if (*appData.savePositionFile != NULLCHAR) {
7190 SavePositionToFile(appData.savePositionFile);
7193 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7195 /* Kill off chess programs */
7196 if (first.pr != NoProc) {
7198 SendToProgram("quit\n", &first);
7199 DestroyChildProcess(first.pr, first.useSigterm);
7201 if (second.pr != NoProc) {
7202 SendToProgram("quit\n", &second);
7203 DestroyChildProcess(second.pr, second.useSigterm);
7205 if (first.isr != NULL) {
7206 RemoveInputSource(first.isr);
7208 if (second.isr != NULL) {
7209 RemoveInputSource(second.isr);
7219 if (appData.debugMode)
7220 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7224 if (gameMode == MachinePlaysWhite ||
7225 gameMode == MachinePlaysBlack) {
7228 DisplayBothClocks();
7230 if (gameMode == PlayFromGameFile) {
7231 if (appData.timeDelay >= 0)
7233 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7235 SendToICS(ics_prefix);
7236 SendToICS("refresh\n");
7237 } else if (currentMove < forwardMostMove) {
7238 ForwardInner(forwardMostMove);
7240 pauseExamInvalid = FALSE;
7246 pauseExamForwardMostMove = forwardMostMove;
7247 pauseExamInvalid = FALSE;
7250 case IcsPlayingWhite:
7251 case IcsPlayingBlack:
7255 case PlayFromGameFile:
7256 (void) StopLoadGameTimer();
7260 case BeginningOfGame:
7261 if (appData.icsActive) return;
7262 /* else fall through */
7263 case MachinePlaysWhite:
7264 case MachinePlaysBlack:
7265 case TwoMachinesPlay:
7266 if (forwardMostMove == 0)
7267 return; /* don't pause if no one has moved */
7268 if ((gameMode == MachinePlaysWhite &&
7269 !WhiteOnMove(forwardMostMove)) ||
7270 (gameMode == MachinePlaysBlack &&
7271 WhiteOnMove(forwardMostMove))) {
7284 char title[MSG_SIZ];
7286 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7287 strcpy(title, _("Edit comment"));
7289 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7290 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7291 parseList[currentMove - 1]);
7294 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7301 char *tags = PGNTags(&gameInfo);
7302 EditTagsPopUp(tags);
7309 if (appData.noChessProgram || gameMode == AnalyzeMode)
7312 if (gameMode != AnalyzeFile) {
7313 if (!appData.icsEngineAnalyze) {
7315 if (gameMode != EditGame) return;
7317 ResurrectChessProgram();
7318 SendToProgram("analyze\n", &first);
7319 first.analyzing = TRUE;
7320 /*first.maybeThinking = TRUE;*/
7321 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7322 AnalysisPopUp(_("Analysis"),
7323 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7325 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
7330 StartAnalysisClock();
7331 GetTimeMark(&lastNodeCountTime);
7338 if (appData.noChessProgram || gameMode == AnalyzeFile)
7341 if (gameMode != AnalyzeMode) {
7343 if (gameMode != EditGame) return;
7344 ResurrectChessProgram();
7345 SendToProgram("analyze\n", &first);
7346 first.analyzing = TRUE;
7347 /*first.maybeThinking = TRUE;*/
7348 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7349 AnalysisPopUp(_("Analysis"),
7350 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7352 gameMode = AnalyzeFile;
7357 StartAnalysisClock();
7358 GetTimeMark(&lastNodeCountTime);
7367 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7371 if (gameMode == PlayFromGameFile ||
7372 gameMode == TwoMachinesPlay ||
7373 gameMode == Training ||
7374 gameMode == AnalyzeMode ||
7375 gameMode == EndOfGame)
7378 if (gameMode == EditPosition)
7381 if (!WhiteOnMove(currentMove)) {
7382 DisplayError(_("It is not White's turn"), 0);
7386 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7389 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7390 gameMode == AnalyzeFile)
7393 ResurrectChessProgram(); /* in case it isn't running */
7394 gameMode = MachinePlaysWhite;
7398 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7400 if (first.sendName) {
7401 sprintf(buf, "name %s\n", gameInfo.black);
7402 SendToProgram(buf, &first);
7404 if (first.sendTime) {
7405 if (first.useColors) {
7406 SendToProgram("black\n", &first); /*gnu kludge*/
7408 SendTimeRemaining(&first, TRUE);
7410 if (first.useColors) {
7411 SendToProgram("white\ngo\n", &first);
7413 SendToProgram("go\n", &first);
7415 SetMachineThinkingEnables();
7416 first.maybeThinking = TRUE;
7419 if (appData.autoFlipView && !flipView) {
7420 flipView = !flipView;
7421 DrawPosition(FALSE, NULL);
7430 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7434 if (gameMode == PlayFromGameFile ||
7435 gameMode == TwoMachinesPlay ||
7436 gameMode == Training ||
7437 gameMode == AnalyzeMode ||
7438 gameMode == EndOfGame)
7441 if (gameMode == EditPosition)
7444 if (WhiteOnMove(currentMove)) {
7445 DisplayError(_("It is not Black's turn"), 0);
7449 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7452 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7453 gameMode == AnalyzeFile)
7456 ResurrectChessProgram(); /* in case it isn't running */
7457 gameMode = MachinePlaysBlack;
7461 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7463 if (first.sendName) {
7464 sprintf(buf, "name %s\n", gameInfo.white);
7465 SendToProgram(buf, &first);
7467 if (first.sendTime) {
7468 if (first.useColors) {
7469 SendToProgram("white\n", &first); /*gnu kludge*/
7471 SendTimeRemaining(&first, FALSE);
7473 if (first.useColors) {
7474 SendToProgram("black\ngo\n", &first);
7476 SendToProgram("go\n", &first);
7478 SetMachineThinkingEnables();
7479 first.maybeThinking = TRUE;
7482 if (appData.autoFlipView && flipView) {
7483 flipView = !flipView;
7484 DrawPosition(FALSE, NULL);
7490 DisplayTwoMachinesTitle()
7493 if (appData.matchGames > 0) {
7494 if (first.twoMachinesColor[0] == 'w') {
7495 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7496 gameInfo.white, gameInfo.black,
7497 first.matchWins, second.matchWins,
7498 matchGame - 1 - (first.matchWins + second.matchWins));
7500 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7501 gameInfo.white, gameInfo.black,
7502 second.matchWins, first.matchWins,
7503 matchGame - 1 - (first.matchWins + second.matchWins));
7506 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7512 TwoMachinesEvent P((void))
7516 ChessProgramState *onmove;
7518 if (appData.noChessProgram) return;
7521 case TwoMachinesPlay:
7523 case MachinePlaysWhite:
7524 case MachinePlaysBlack:
7525 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7526 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7530 case BeginningOfGame:
7531 case PlayFromGameFile:
7534 if (gameMode != EditGame) return;
7548 forwardMostMove = currentMove;
7549 ResurrectChessProgram(); /* in case first program isn't running */
7551 if (second.pr == NULL) {
7552 StartChessProgram(&second);
7553 if (second.protocolVersion == 1) {
7554 TwoMachinesEventIfReady();
7556 /* kludge: allow timeout for initial "feature" command */
7558 DisplayMessage("", _("Starting second chess program"));
7559 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7563 DisplayMessage("", "");
7564 InitChessProgram(&second);
7565 SendToProgram("force\n", &second);
7566 if (startedFromSetupPosition) {
7567 SendBoard(&second, backwardMostMove);
7569 for (i = backwardMostMove; i < forwardMostMove; i++) {
7570 SendMoveToProgram(i, &second);
7573 gameMode = TwoMachinesPlay;
7577 DisplayTwoMachinesTitle();
7579 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7585 SendToProgram(first.computerString, &first);
7586 if (first.sendName) {
7587 sprintf(buf, "name %s\n", second.tidy);
7588 SendToProgram(buf, &first);
7590 SendToProgram(second.computerString, &second);
7591 if (second.sendName) {
7592 sprintf(buf, "name %s\n", first.tidy);
7593 SendToProgram(buf, &second);
7596 if (!first.sendTime || !second.sendTime) {
7598 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7599 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7601 if (onmove->sendTime) {
7602 if (onmove->useColors) {
7603 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7605 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7607 if (onmove->useColors) {
7608 SendToProgram(onmove->twoMachinesColor, onmove);
7610 SendToProgram("go\n", onmove);
7611 onmove->maybeThinking = TRUE;
7612 SetMachineThinkingEnables();
7620 if (gameMode == Training) {
7621 SetTrainingModeOff();
7622 gameMode = PlayFromGameFile;
7623 DisplayMessage("", _("Training mode off"));
7625 gameMode = Training;
7626 animateTraining = appData.animate;
7628 /* make sure we are not already at the end of the game */
7629 if (currentMove < forwardMostMove) {
7630 SetTrainingModeOn();
7631 DisplayMessage("", _("Training mode on"));
7633 gameMode = PlayFromGameFile;
7634 DisplayError(_("Already at end of game"), 0);
7643 if (!appData.icsActive) return;
7645 case IcsPlayingWhite:
7646 case IcsPlayingBlack:
7649 case BeginningOfGame:
7683 SetTrainingModeOff();
7685 case MachinePlaysWhite:
7686 case MachinePlaysBlack:
7687 case BeginningOfGame:
7688 SendToProgram("force\n", &first);
7689 SetUserThinkingEnables();
7691 case PlayFromGameFile:
7692 (void) StopLoadGameTimer();
7693 if (gameFileFP != NULL) {
7703 SendToProgram("force\n", &first);
7705 case TwoMachinesPlay:
7706 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7707 ResurrectChessProgram();
7708 SetUserThinkingEnables();
7711 ResurrectChessProgram();
7713 case IcsPlayingBlack:
7714 case IcsPlayingWhite:
7715 DisplayError(_("Warning: You are still playing a game"), 0);
7718 DisplayError(_("Warning: You are still observing a game"), 0);
7721 DisplayError(_("Warning: You are still examining a game"), 0);
7732 first.offeredDraw = second.offeredDraw = 0;
7734 if (gameMode == PlayFromGameFile) {
7735 whiteTimeRemaining = timeRemaining[0][currentMove];
7736 blackTimeRemaining = timeRemaining[1][currentMove];
7740 if (gameMode == MachinePlaysWhite ||
7741 gameMode == MachinePlaysBlack ||
7742 gameMode == TwoMachinesPlay ||
7743 gameMode == EndOfGame) {
7744 i = forwardMostMove;
7745 while (i > currentMove) {
7746 SendToProgram("undo\n", &first);
7749 whiteTimeRemaining = timeRemaining[0][currentMove];
7750 blackTimeRemaining = timeRemaining[1][currentMove];
7751 DisplayBothClocks();
7752 if (whiteFlag || blackFlag) {
7753 whiteFlag = blackFlag = 0;
7758 gameMode = EditGame;
7767 if (gameMode == EditPosition) {
7773 if (gameMode != EditGame) return;
7775 gameMode = EditPosition;
7778 if (currentMove > 0)
7779 CopyBoard(boards[0], boards[currentMove]);
7781 blackPlaysFirst = !WhiteOnMove(currentMove);
7783 currentMove = forwardMostMove = backwardMostMove = 0;
7784 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7791 /* icsEngineAnalyze - possible call from other functions */
7792 if (appData.icsEngineAnalyze) {
7793 appData.icsEngineAnalyze = FALSE;
7794 DisplayMessage("","Close ICS engine analyze...");
7796 if (first.analysisSupport && first.analyzing) {
7797 SendToProgram("exit\n", &first);
7798 first.analyzing = FALSE;
7801 thinkOutput[0] = NULLCHAR;
7807 startedFromSetupPosition = TRUE;
7808 InitChessProgram(&first);
7809 SendToProgram("force\n", &first);
7810 if (blackPlaysFirst) {
7811 strcpy(moveList[0], "");
7812 strcpy(parseList[0], "");
7813 currentMove = forwardMostMove = backwardMostMove = 1;
7814 CopyBoard(boards[1], boards[0]);
7816 currentMove = forwardMostMove = backwardMostMove = 0;
7818 SendBoard(&first, forwardMostMove);
7820 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7821 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7822 gameMode = EditGame;
7824 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7827 /* Pause for `ms' milliseconds */
7828 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7838 } while (SubtractTimeMarks(&m2, &m1) < ms);
7841 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7843 SendMultiLineToICS(buf)
7846 char temp[MSG_SIZ+1], *p;
7853 strncpy(temp, buf, len);
7858 if (*p == '\n' || *p == '\r')
7865 SendToPlayer(temp, strlen(temp));
7869 SetWhiteToPlayEvent()
7871 if (gameMode == EditPosition) {
7872 blackPlaysFirst = FALSE;
7873 DisplayBothClocks(); /* works because currentMove is 0 */
7874 } else if (gameMode == IcsExamining) {
7875 SendToICS(ics_prefix);
7876 SendToICS("tomove white\n");
7881 SetBlackToPlayEvent()
7883 if (gameMode == EditPosition) {
7884 blackPlaysFirst = TRUE;
7885 currentMove = 1; /* kludge */
7886 DisplayBothClocks();
7888 } else if (gameMode == IcsExamining) {
7889 SendToICS(ics_prefix);
7890 SendToICS("tomove black\n");
7895 EditPositionMenuEvent(selection, x, y)
7896 ChessSquare selection;
7901 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7903 switch (selection) {
7905 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7906 SendToICS(ics_prefix);
7907 SendToICS("bsetup clear\n");
7908 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7909 SendToICS(ics_prefix);
7910 SendToICS("clearboard\n");
7912 for (x = 0; x < BOARD_SIZE; x++) {
7913 for (y = 0; y < BOARD_SIZE; y++) {
7914 if (gameMode == IcsExamining) {
7915 if (boards[currentMove][y][x] != EmptySquare) {
7916 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7921 boards[0][y][x] = EmptySquare;
7926 if (gameMode == EditPosition) {
7927 DrawPosition(FALSE, boards[0]);
7932 SetWhiteToPlayEvent();
7936 SetBlackToPlayEvent();
7940 if (gameMode == IcsExamining) {
7941 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7944 boards[0][y][x] = EmptySquare;
7945 DrawPosition(FALSE, boards[0]);
7950 if (gameMode == IcsExamining) {
7951 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7952 PieceToChar(selection), 'a' + x, '1' + y);
7955 boards[0][y][x] = selection;
7956 DrawPosition(FALSE, boards[0]);
7964 DropMenuEvent(selection, x, y)
7965 ChessSquare selection;
7971 case IcsPlayingWhite:
7972 case MachinePlaysBlack:
7973 if (!WhiteOnMove(currentMove)) {
7974 DisplayMoveError(_("It is Black's turn"));
7977 moveType = WhiteDrop;
7979 case IcsPlayingBlack:
7980 case MachinePlaysWhite:
7981 if (WhiteOnMove(currentMove)) {
7982 DisplayMoveError(_("It is White's turn"));
7985 moveType = BlackDrop;
7988 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7994 if (moveType == BlackDrop && selection < BlackPawn) {
7995 selection = (ChessSquare) ((int) selection
7996 + (int) BlackPawn - (int) WhitePawn);
7998 if (boards[currentMove][y][x] != EmptySquare) {
7999 DisplayMoveError(_("That square is occupied"));
8003 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8009 /* Accept a pending offer of any kind from opponent */
8011 if (appData.icsActive) {
8012 SendToICS(ics_prefix);
8013 SendToICS("accept\n");
8014 } else if (cmailMsgLoaded) {
8015 if (currentMove == cmailOldMove &&
8016 commentList[cmailOldMove] != NULL &&
8017 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8018 "Black offers a draw" : "White offers a draw")) {
8020 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8021 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8023 DisplayError(_("There is no pending offer on this move"), 0);
8024 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8027 /* Not used for offers from chess program */
8034 /* Decline a pending offer of any kind from opponent */
8036 if (appData.icsActive) {
8037 SendToICS(ics_prefix);
8038 SendToICS("decline\n");
8039 } else if (cmailMsgLoaded) {
8040 if (currentMove == cmailOldMove &&
8041 commentList[cmailOldMove] != NULL &&
8042 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8043 "Black offers a draw" : "White offers a draw")) {
8045 AppendComment(cmailOldMove, "Draw declined");
8046 DisplayComment(cmailOldMove - 1, "Draw declined");
8049 DisplayError(_("There is no pending offer on this move"), 0);
8052 /* Not used for offers from chess program */
8059 /* Issue ICS rematch command */
8060 if (appData.icsActive) {
8061 SendToICS(ics_prefix);
8062 SendToICS("rematch\n");
8069 /* Call your opponent's flag (claim a win on time) */
8070 if (appData.icsActive) {
8071 SendToICS(ics_prefix);
8072 SendToICS("flag\n");
8077 case MachinePlaysWhite:
8080 GameEnds(GameIsDrawn, "Both players ran out of time",
8083 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8085 DisplayError(_("Your opponent is not out of time"), 0);
8088 case MachinePlaysBlack:
8091 GameEnds(GameIsDrawn, "Both players ran out of time",
8094 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8096 DisplayError(_("Your opponent is not out of time"), 0);
8106 /* Offer draw or accept pending draw offer from opponent */
8108 if (appData.icsActive) {
8109 /* Note: tournament rules require draw offers to be
8110 made after you make your move but before you punch
8111 your clock. Currently ICS doesn't let you do that;
8112 instead, you immediately punch your clock after making
8113 a move, but you can offer a draw at any time. */
8115 SendToICS(ics_prefix);
8116 SendToICS("draw\n");
8117 } else if (cmailMsgLoaded) {
8118 if (currentMove == cmailOldMove &&
8119 commentList[cmailOldMove] != NULL &&
8120 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8121 "Black offers a draw" : "White offers a draw")) {
8122 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8123 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8124 } else if (currentMove == cmailOldMove + 1) {
8125 char *offer = WhiteOnMove(cmailOldMove) ?
8126 "White offers a draw" : "Black offers a draw";
8127 AppendComment(currentMove, offer);
8128 DisplayComment(currentMove - 1, offer);
8129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8131 DisplayError(_("You must make your move before offering a draw"), 0);
8132 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8134 } else if (first.offeredDraw) {
8135 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8137 if (first.sendDrawOffers) {
8138 SendToProgram("draw\n", &first);
8139 userOfferedDraw = TRUE;
8147 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8149 if (appData.icsActive) {
8150 SendToICS(ics_prefix);
8151 SendToICS("adjourn\n");
8153 /* Currently GNU Chess doesn't offer or accept Adjourns */
8161 /* Offer Abort or accept pending Abort offer from opponent */
8163 if (appData.icsActive) {
8164 SendToICS(ics_prefix);
8165 SendToICS("abort\n");
8167 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8174 /* Resign. You can do this even if it's not your turn. */
8176 if (appData.icsActive) {
8177 SendToICS(ics_prefix);
8178 SendToICS("resign\n");
8181 case MachinePlaysWhite:
8182 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8184 case MachinePlaysBlack:
8185 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8188 if (cmailMsgLoaded) {
8190 if (WhiteOnMove(cmailOldMove)) {
8191 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8193 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8195 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8206 StopObservingEvent()
8208 /* Stop observing current games */
8209 SendToICS(ics_prefix);
8210 SendToICS("unobserve\n");
8214 StopExaminingEvent()
8216 /* Stop observing current game */
8217 SendToICS(ics_prefix);
8218 SendToICS("unexamine\n");
8222 ForwardInner(target)
8227 if (appData.debugMode)
8228 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8229 target, currentMove, forwardMostMove);
8231 if (gameMode == EditPosition)
8234 if (gameMode == PlayFromGameFile && !pausing)
8237 if (gameMode == IcsExamining && pausing)
8238 limit = pauseExamForwardMostMove;
8240 limit = forwardMostMove;
8242 if (target > limit) target = limit;
8244 if (target > 0 && moveList[target - 1][0]) {
8245 int fromX, fromY, toX, toY;
8246 toX = moveList[target - 1][2] - 'a';
8247 toY = moveList[target - 1][3] - '1';
8248 if (moveList[target - 1][1] == '@') {
8249 if (appData.highlightLastMove) {
8250 SetHighlights(-1, -1, toX, toY);
8253 fromX = moveList[target - 1][0] - 'a';
8254 fromY = moveList[target - 1][1] - '1';
8255 if (target == currentMove + 1) {
8256 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8258 if (appData.highlightLastMove) {
8259 SetHighlights(fromX, fromY, toX, toY);
8263 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8264 gameMode == Training || gameMode == PlayFromGameFile ||
8265 gameMode == AnalyzeFile) {
8266 while (currentMove < target) {
8267 SendMoveToProgram(currentMove++, &first);
8270 currentMove = target;
8273 if (gameMode == EditGame || gameMode == EndOfGame) {
8274 whiteTimeRemaining = timeRemaining[0][currentMove];
8275 blackTimeRemaining = timeRemaining[1][currentMove];
8277 DisplayBothClocks();
8278 DisplayMove(currentMove - 1);
8279 DrawPosition(FALSE, boards[currentMove]);
8280 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8281 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8282 DisplayComment(currentMove - 1, commentList[currentMove]);
8290 if (gameMode == IcsExamining && !pausing) {
8291 SendToICS(ics_prefix);
8292 SendToICS("forward\n");
8294 ForwardInner(currentMove + 1);
8301 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8302 /* to optimze, we temporarily turn off analysis mode while we feed
8303 * the remaining moves to the engine. Otherwise we get analysis output
8306 if (first.analysisSupport) {
8307 SendToProgram("exit\nforce\n", &first);
8308 first.analyzing = FALSE;
8312 if (gameMode == IcsExamining && !pausing) {
8313 SendToICS(ics_prefix);
8314 SendToICS("forward 999999\n");
8316 ForwardInner(forwardMostMove);
8319 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8320 /* we have fed all the moves, so reactivate analysis mode */
8321 SendToProgram("analyze\n", &first);
8322 first.analyzing = TRUE;
8323 /*first.maybeThinking = TRUE;*/
8324 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8329 BackwardInner(target)
8332 if (appData.debugMode)
8333 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8334 target, currentMove, forwardMostMove);
8336 if (gameMode == EditPosition) return;
8337 if (currentMove <= backwardMostMove) {
8339 DrawPosition(FALSE, boards[currentMove]);
8342 if (gameMode == PlayFromGameFile && !pausing)
8345 if (moveList[target][0]) {
8346 int fromX, fromY, toX, toY;
8347 toX = moveList[target][2] - 'a';
8348 toY = moveList[target][3] - '1';
8349 if (moveList[target][1] == '@') {
8350 if (appData.highlightLastMove) {
8351 SetHighlights(-1, -1, toX, toY);
8354 fromX = moveList[target][0] - 'a';
8355 fromY = moveList[target][1] - '1';
8356 if (target == currentMove - 1) {
8357 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8359 if (appData.highlightLastMove) {
8360 SetHighlights(fromX, fromY, toX, toY);
8364 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8365 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8366 while (currentMove > target) {
8367 SendToProgram("undo\n", &first);
8371 currentMove = target;
8374 if (gameMode == EditGame || gameMode == EndOfGame) {
8375 whiteTimeRemaining = timeRemaining[0][currentMove];
8376 blackTimeRemaining = timeRemaining[1][currentMove];
8378 DisplayBothClocks();
8379 DisplayMove(currentMove - 1);
8380 DrawPosition(FALSE, boards[currentMove]);
8381 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8382 if (commentList[currentMove] != NULL) {
8383 DisplayComment(currentMove - 1, commentList[currentMove]);
8390 if (gameMode == IcsExamining && !pausing) {
8391 SendToICS(ics_prefix);
8392 SendToICS("backward\n");
8394 BackwardInner(currentMove - 1);
8401 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8402 /* to optimze, we temporarily turn off analysis mode while we undo
8403 * all the moves. Otherwise we get analysis output after each undo.
8405 if (first.analysisSupport) {
8406 SendToProgram("exit\nforce\n", &first);
8407 first.analyzing = FALSE;
8411 if (gameMode == IcsExamining && !pausing) {
8412 SendToICS(ics_prefix);
8413 SendToICS("backward 999999\n");
8415 BackwardInner(backwardMostMove);
8418 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8419 /* we have fed all the moves, so reactivate analysis mode */
8420 SendToProgram("analyze\n", &first);
8421 first.analyzing = TRUE;
8422 /*first.maybeThinking = TRUE;*/
8423 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8430 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8431 if (to >= forwardMostMove) to = forwardMostMove;
8432 if (to <= backwardMostMove) to = backwardMostMove;
8433 if (to < currentMove) {
8443 if (gameMode != IcsExamining) {
8444 DisplayError(_("You are not examining a game"), 0);
8448 DisplayError(_("You can't revert while pausing"), 0);
8451 SendToICS(ics_prefix);
8452 SendToICS("revert\n");
8459 case MachinePlaysWhite:
8460 case MachinePlaysBlack:
8461 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8462 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8465 if (forwardMostMove < 2) return;
8466 currentMove = forwardMostMove = forwardMostMove - 2;
8467 whiteTimeRemaining = timeRemaining[0][currentMove];
8468 blackTimeRemaining = timeRemaining[1][currentMove];
8469 DisplayBothClocks();
8470 DisplayMove(currentMove - 1);
8471 ClearHighlights();/*!! could figure this out*/
8472 DrawPosition(FALSE, boards[currentMove]);
8473 SendToProgram("remove\n", &first);
8474 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8477 case BeginningOfGame:
8481 case IcsPlayingWhite:
8482 case IcsPlayingBlack:
8483 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8484 SendToICS(ics_prefix);
8485 SendToICS("takeback 2\n");
8487 SendToICS(ics_prefix);
8488 SendToICS("takeback 1\n");
8497 ChessProgramState *cps;
8500 case MachinePlaysWhite:
8501 if (!WhiteOnMove(forwardMostMove)) {
8502 DisplayError(_("It is your turn"), 0);
8507 case MachinePlaysBlack:
8508 if (WhiteOnMove(forwardMostMove)) {
8509 DisplayError(_("It is your turn"), 0);
8514 case TwoMachinesPlay:
8515 if (WhiteOnMove(forwardMostMove) ==
8516 (first.twoMachinesColor[0] == 'w')) {
8522 case BeginningOfGame:
8526 SendToProgram("?\n", cps);
8533 if (gameMode != EditGame) return;
8540 if (forwardMostMove > currentMove) {
8541 if (gameInfo.resultDetails != NULL) {
8542 free(gameInfo.resultDetails);
8543 gameInfo.resultDetails = NULL;
8544 gameInfo.result = GameUnfinished;
8546 forwardMostMove = currentMove;
8547 HistorySet(parseList, backwardMostMove, forwardMostMove,
8555 if (appData.noChessProgram) return;
8557 case MachinePlaysWhite:
8558 if (WhiteOnMove(forwardMostMove)) {
8559 DisplayError(_("Wait until your turn"), 0);
8563 case BeginningOfGame:
8564 case MachinePlaysBlack:
8565 if (!WhiteOnMove(forwardMostMove)) {
8566 DisplayError(_("Wait until your turn"), 0);
8571 DisplayError(_("No hint available"), 0);
8574 SendToProgram("hint\n", &first);
8575 hintRequested = TRUE;
8581 if (appData.noChessProgram) return;
8583 case MachinePlaysWhite:
8584 if (WhiteOnMove(forwardMostMove)) {
8585 DisplayError(_("Wait until your turn"), 0);
8589 case BeginningOfGame:
8590 case MachinePlaysBlack:
8591 if (!WhiteOnMove(forwardMostMove)) {
8592 DisplayError(_("Wait until your turn"), 0);
8599 case TwoMachinesPlay:
8604 SendToProgram("bk\n", &first);
8605 bookOutput[0] = NULLCHAR;
8606 bookRequested = TRUE;
8612 char *tags = PGNTags(&gameInfo);
8613 TagsPopUp(tags, CmailMsg());
8617 /* end button procedures */
8620 PrintPosition(fp, move)
8626 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8627 for (j = 0; j < BOARD_SIZE; j++) {
8628 char c = PieceToChar(boards[move][i][j]);
8629 fputc(c == 'x' ? '.' : c, fp);
8630 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8633 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8634 fprintf(fp, "white to play\n");
8636 fprintf(fp, "black to play\n");
8643 if (gameInfo.white != NULL) {
8644 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8650 /* Find last component of program's own name, using some heuristics */
8652 TidyProgramName(prog, host, buf)
8653 char *prog, *host, buf[MSG_SIZ];
8656 int local = (strcmp(host, "localhost") == 0);
8657 while (!local && (p = strchr(prog, ';')) != NULL) {
8659 while (*p == ' ') p++;
8662 if (*prog == '"' || *prog == '\'') {
8663 q = strchr(prog + 1, *prog);
8665 q = strchr(prog, ' ');
8667 if (q == NULL) q = prog + strlen(prog);
8669 while (p >= prog && *p != '/' && *p != '\\') p--;
8671 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8672 memcpy(buf, p, q - p);
8673 buf[q - p] = NULLCHAR;
8681 TimeControlTagValue()
8684 if (!appData.clockMode) {
8686 } else if (movesPerSession > 0) {
8687 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8688 } else if (timeIncrement == 0) {
8689 sprintf(buf, "%ld", timeControl/1000);
8691 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8693 return StrSave(buf);
8699 /* This routine is used only for certain modes */
8700 VariantClass v = gameInfo.variant;
8701 ClearGameInfo(&gameInfo);
8702 gameInfo.variant = v;
8705 case MachinePlaysWhite:
8706 gameInfo.event = StrSave("Computer chess game");
8707 gameInfo.site = StrSave(HostName());
8708 gameInfo.date = PGNDate();
8709 gameInfo.round = StrSave("-");
8710 gameInfo.white = StrSave(first.tidy);
8711 gameInfo.black = StrSave(UserName());
8712 gameInfo.timeControl = TimeControlTagValue();
8715 case MachinePlaysBlack:
8716 gameInfo.event = StrSave("Computer chess game");
8717 gameInfo.site = StrSave(HostName());
8718 gameInfo.date = PGNDate();
8719 gameInfo.round = StrSave("-");
8720 gameInfo.white = StrSave(UserName());
8721 gameInfo.black = StrSave(first.tidy);
8722 gameInfo.timeControl = TimeControlTagValue();
8725 case TwoMachinesPlay:
8726 gameInfo.event = StrSave("Computer chess game");
8727 gameInfo.site = StrSave(HostName());
8728 gameInfo.date = PGNDate();
8729 if (matchGame > 0) {
8731 sprintf(buf, "%d", matchGame);
8732 gameInfo.round = StrSave(buf);
8734 gameInfo.round = StrSave("-");
8736 if (first.twoMachinesColor[0] == 'w') {
8737 gameInfo.white = StrSave(first.tidy);
8738 gameInfo.black = StrSave(second.tidy);
8740 gameInfo.white = StrSave(second.tidy);
8741 gameInfo.black = StrSave(first.tidy);
8743 gameInfo.timeControl = TimeControlTagValue();
8747 gameInfo.event = StrSave("Edited game");
8748 gameInfo.site = StrSave(HostName());
8749 gameInfo.date = PGNDate();
8750 gameInfo.round = StrSave("-");
8751 gameInfo.white = StrSave("-");
8752 gameInfo.black = StrSave("-");
8756 gameInfo.event = StrSave("Edited position");
8757 gameInfo.site = StrSave(HostName());
8758 gameInfo.date = PGNDate();
8759 gameInfo.round = StrSave("-");
8760 gameInfo.white = StrSave("-");
8761 gameInfo.black = StrSave("-");
8764 case IcsPlayingWhite:
8765 case IcsPlayingBlack:
8770 case PlayFromGameFile:
8771 gameInfo.event = StrSave("Game from non-PGN file");
8772 gameInfo.site = StrSave(HostName());
8773 gameInfo.date = PGNDate();
8774 gameInfo.round = StrSave("-");
8775 gameInfo.white = StrSave("?");
8776 gameInfo.black = StrSave("?");
8785 ReplaceComment(index, text)
8791 while (*text == '\n') text++;
8793 while (len > 0 && text[len - 1] == '\n') len--;
8795 if (commentList[index] != NULL)
8796 free(commentList[index]);
8799 commentList[index] = NULL;
8802 commentList[index] = (char *) malloc(len + 2);
8803 strncpy(commentList[index], text, len);
8804 commentList[index][len] = '\n';
8805 commentList[index][len + 1] = NULLCHAR;
8818 if (ch == '\r') continue;
8820 } while (ch != '\0');
8824 AppendComment(index, text)
8832 while (*text == '\n') text++;
8834 while (len > 0 && text[len - 1] == '\n') len--;
8836 if (len == 0) return;
8838 if (commentList[index] != NULL) {
8839 old = commentList[index];
8840 oldlen = strlen(old);
8841 commentList[index] = (char *) malloc(oldlen + len + 2);
8842 strcpy(commentList[index], old);
8844 strncpy(&commentList[index][oldlen], text, len);
8845 commentList[index][oldlen + len] = '\n';
8846 commentList[index][oldlen + len + 1] = NULLCHAR;
8848 commentList[index] = (char *) malloc(len + 2);
8849 strncpy(commentList[index], text, len);
8850 commentList[index][len] = '\n';
8851 commentList[index][len + 1] = NULLCHAR;
8856 SendToProgram(message, cps)
8858 ChessProgramState *cps;
8860 int count, outCount, error;
8863 if (cps->pr == NULL) return;
8866 if (appData.debugMode) {
8869 fprintf(debugFP, "%ld >%-6s: %s",
8870 SubtractTimeMarks(&now, &programStartTime),
8871 cps->which, message);
8874 count = strlen(message);
8875 outCount = OutputToProcess(cps->pr, message, count, &error);
8876 if (outCount < count && !exiting) {
8877 sprintf(buf, _("Error writing to %s chess program"), cps->which);
8878 DisplayFatalError(buf, error, 1);
8883 ReceiveFromProgram(isr, closure, message, count, error)
8892 ChessProgramState *cps = (ChessProgramState *)closure;
8894 if (isr != cps->isr) return; /* Killed intentionally */
8898 _("Error: %s chess program (%s) exited unexpectedly"),
8899 cps->which, cps->program);
8900 RemoveInputSource(cps->isr);
8901 DisplayFatalError(buf, 0, 1);
8904 _("Error reading from %s chess program (%s)"),
8905 cps->which, cps->program);
8906 RemoveInputSource(cps->isr);
8907 DisplayFatalError(buf, error, 1);
8909 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8913 if ((end_str = strchr(message, '\r')) != NULL)
8914 *end_str = NULLCHAR;
8915 if ((end_str = strchr(message, '\n')) != NULL)
8916 *end_str = NULLCHAR;
8918 if (appData.debugMode) {
8921 fprintf(debugFP, "%ld <%-6s: %s\n",
8922 SubtractTimeMarks(&now, &programStartTime),
8923 cps->which, message);
8925 /* if icsEngineAnalyze is active we block all
8926 whisper and kibitz output, because nobody want
8929 if (appData.icsEngineAnalyze) {
8930 if (strstr(message, "whisper") != NULL ||
8931 strstr(message, "kibitz") != NULL ||
8932 strstr(message, "tellics") != NULL) return;
8933 HandleMachineMove(message, cps);
8935 HandleMachineMove(message, cps);
8941 SendTimeControl(cps, mps, tc, inc, sd, st)
8942 ChessProgramState *cps;
8943 int mps, inc, sd, st;
8947 int seconds = (tc / 1000) % 60;
8950 /* Set exact time per move, normally using st command */
8951 if (cps->stKludge) {
8952 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8955 sprintf(buf, "level 1 %d\n", st/60);
8957 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8960 sprintf(buf, "st %d\n", st);
8963 /* Set conventional or incremental time control, using level command */
8965 /* Note old gnuchess bug -- minutes:seconds used to not work.
8966 Fixed in later versions, but still avoid :seconds
8967 when seconds is 0. */
8968 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8970 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8974 SendToProgram(buf, cps);
8976 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8977 /* Orthogonally, limit search to given depth */
8979 if (cps->sdKludge) {
8980 sprintf(buf, "depth\n%d\n", sd);
8982 sprintf(buf, "sd %d\n", sd);
8984 SendToProgram(buf, cps);
8989 SendTimeRemaining(cps, machineWhite)
8990 ChessProgramState *cps;
8991 int /*boolean*/ machineWhite;
8993 char message[MSG_SIZ];
8996 /* Note: this routine must be called when the clocks are stopped
8997 or when they have *just* been set or switched; otherwise
8998 it will be off by the time since the current tick started.
9001 time = whiteTimeRemaining / 10;
9002 otime = blackTimeRemaining / 10;
9004 time = blackTimeRemaining / 10;
9005 otime = whiteTimeRemaining / 10;
9007 if (time <= 0) time = 1;
9008 if (otime <= 0) otime = 1;
9010 sprintf(message, "time %ld\notim %ld\n", time, otime);
9011 SendToProgram(message, cps);
9015 BoolFeature(p, name, loc, cps)
9019 ChessProgramState *cps;
9022 int len = strlen(name);
9024 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9026 sscanf(*p, "%d", &val);
9028 while (**p && **p != ' ') (*p)++;
9029 sprintf(buf, "accepted %s\n", name);
9030 SendToProgram(buf, cps);
9037 IntFeature(p, name, loc, cps)
9041 ChessProgramState *cps;
9044 int len = strlen(name);
9045 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9047 sscanf(*p, "%d", loc);
9048 while (**p && **p != ' ') (*p)++;
9049 sprintf(buf, "accepted %s\n", name);
9050 SendToProgram(buf, cps);
9057 StringFeature(p, name, loc, cps)
9061 ChessProgramState *cps;
9064 int len = strlen(name);
9065 if (strncmp((*p), name, len) == 0
9066 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9068 sscanf(*p, "%[^\"]", loc);
9069 while (**p && **p != '\"') (*p)++;
9070 if (**p == '\"') (*p)++;
9071 sprintf(buf, "accepted %s\n", name);
9072 SendToProgram(buf, cps);
9079 FeatureDone(cps, val)
9080 ChessProgramState* cps;
9083 DelayedEventCallback cb = GetDelayedEvent();
9084 if ((cb == InitBackEnd3 && cps == &first) ||
9085 (cb == TwoMachinesEventIfReady && cps == &second)) {
9086 CancelDelayedEvent();
9087 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9089 cps->initDone = val;
9092 /* Parse feature command from engine */
9094 ParseFeatures(args, cps)
9096 ChessProgramState *cps;
9104 while (*p == ' ') p++;
9105 if (*p == NULLCHAR) return;
9107 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9108 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9109 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9110 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9111 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9112 if (BoolFeature(&p, "reuse", &val, cps)) {
9113 /* Engine can disable reuse, but can't enable it if user said no */
9114 if (!val) cps->reuse = FALSE;
9117 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9118 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9119 if (gameMode == TwoMachinesPlay) {
9120 DisplayTwoMachinesTitle();
9126 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9127 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9128 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9129 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9130 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9131 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9132 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9133 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9134 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9135 if (IntFeature(&p, "done", &val, cps)) {
9136 FeatureDone(cps, val);
9140 /* unknown feature: complain and skip */
9142 while (*q && *q != '=') q++;
9143 sprintf(buf, "rejected %.*s\n", q-p, p);
9144 SendToProgram(buf, cps);
9150 while (*p && *p != '\"') p++;
9151 if (*p == '\"') p++;
9153 while (*p && *p != ' ') p++;
9161 PeriodicUpdatesEvent(newState)
9164 if (newState == appData.periodicUpdates)
9167 appData.periodicUpdates=newState;
9169 /* Display type changes, so update it now */
9172 /* Get the ball rolling again... */
9174 AnalysisPeriodicEvent(1);
9175 StartAnalysisClock();
9180 PonderNextMoveEvent(newState)
9183 if (newState == appData.ponderNextMove) return;
9184 if (gameMode == EditPosition) EditPositionDone();
9186 SendToProgram("hard\n", &first);
9187 if (gameMode == TwoMachinesPlay) {
9188 SendToProgram("hard\n", &second);
9191 SendToProgram("easy\n", &first);
9192 thinkOutput[0] = NULLCHAR;
9193 if (gameMode == TwoMachinesPlay) {
9194 SendToProgram("easy\n", &second);
9197 appData.ponderNextMove = newState;
9201 ShowThinkingEvent(newState)
9204 if (newState == appData.showThinking) return;
9205 if (gameMode == EditPosition) EditPositionDone();
9207 SendToProgram("post\n", &first);
9208 if (gameMode == TwoMachinesPlay) {
9209 SendToProgram("post\n", &second);
9212 SendToProgram("nopost\n", &first);
9213 thinkOutput[0] = NULLCHAR;
9214 if (gameMode == TwoMachinesPlay) {
9215 SendToProgram("nopost\n", &second);
9218 appData.showThinking = newState;
9222 AskQuestionEvent(title, question, replyPrefix, which)
9223 char *title; char *question; char *replyPrefix; char *which;
9225 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9226 if (pr == NoProc) return;
9227 AskQuestion(title, question, replyPrefix, pr);
9231 DisplayMove(moveNumber)
9234 char message[MSG_SIZ];
9236 char cpThinkOutput[MSG_SIZ];
9238 if (moveNumber == forwardMostMove - 1 ||
9239 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9241 strcpy(cpThinkOutput, thinkOutput);
9242 if (strchr(cpThinkOutput, '\n'))
9243 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9245 *cpThinkOutput = NULLCHAR;
9248 if (moveNumber == forwardMostMove - 1 &&
9249 gameInfo.resultDetails != NULL) {
9250 if (gameInfo.resultDetails[0] == NULLCHAR) {
9251 sprintf(res, " %s", PGNResult(gameInfo.result));
9253 sprintf(res, " {%s} %s",
9254 gameInfo.resultDetails, PGNResult(gameInfo.result));
9260 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9261 DisplayMessage(res, cpThinkOutput);
9263 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9264 WhiteOnMove(moveNumber) ? " " : ".. ",
9265 parseList[moveNumber], res);
9266 DisplayMessage(message, cpThinkOutput);
9271 DisplayAnalysisText(text)
9276 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
9277 || appData.icsEngineAnalyze) {
9278 sprintf(buf, "Analysis (%s)", first.tidy);
9279 AnalysisPopUp(buf, text);
9287 while (*str && isspace(*str)) ++str;
9288 while (*str && !isspace(*str)) ++str;
9289 if (!*str) return 1;
9290 while (*str && isspace(*str)) ++str;
9291 if (!*str) return 1;
9300 static char *xtra[] = { "", " (--)", " (++)" };
9303 if (programStats.time == 0) {
9304 programStats.time = 1;
9307 if (programStats.got_only_move) {
9308 strcpy(buf, programStats.movelist);
9310 nps = (u64ToDouble(programStats.nodes) /
9311 ((double)programStats.time /100.0));
9313 cs = programStats.time % 100;
9314 s = programStats.time / 100;
9320 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9321 if (programStats.move_name[0] != NULLCHAR) {
9322 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9324 programStats.nr_moves-programStats.moves_left,
9325 programStats.nr_moves, programStats.move_name,
9326 ((float)programStats.score)/100.0, programStats.movelist,
9327 only_one_move(programStats.movelist)?
9328 xtra[programStats.got_fail] : "",
9329 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9331 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9333 programStats.nr_moves-programStats.moves_left,
9334 programStats.nr_moves, ((float)programStats.score)/100.0,
9335 programStats.movelist,
9336 only_one_move(programStats.movelist)?
9337 xtra[programStats.got_fail] : "",
9338 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9341 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9343 ((float)programStats.score)/100.0,
9344 programStats.movelist,
9345 only_one_move(programStats.movelist)?
9346 xtra[programStats.got_fail] : "",
9347 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9350 DisplayAnalysisText(buf);
9354 DisplayComment(moveNumber, text)
9358 char title[MSG_SIZ];
9360 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9361 strcpy(title, "Comment");
9363 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9364 WhiteOnMove(moveNumber) ? " " : ".. ",
9365 parseList[moveNumber]);
9368 CommentPopUp(title, text);
9371 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9372 * might be busy thinking or pondering. It can be omitted if your
9373 * gnuchess is configured to stop thinking immediately on any user
9374 * input. However, that gnuchess feature depends on the FIONREAD
9375 * ioctl, which does not work properly on some flavors of Unix.
9379 ChessProgramState *cps;
9382 if (!cps->useSigint) return;
9383 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9385 case MachinePlaysWhite:
9386 case MachinePlaysBlack:
9387 case TwoMachinesPlay:
9388 case IcsPlayingWhite:
9389 case IcsPlayingBlack:
9392 /* Skip if we know it isn't thinking */
9393 if (!cps->maybeThinking) return;
9394 if (appData.debugMode)
9395 fprintf(debugFP, "Interrupting %s\n", cps->which);
9396 InterruptChildProcess(cps->pr);
9397 cps->maybeThinking = FALSE;
9402 #endif /*ATTENTION*/
9408 if (whiteTimeRemaining <= 0) {
9411 if (appData.icsActive) {
9412 if (appData.autoCallFlag &&
9413 gameMode == IcsPlayingBlack && !blackFlag) {
9414 SendToICS(ics_prefix);
9415 SendToICS("flag\n");
9419 DisplayTitle(_("Both flags fell"));
9421 DisplayTitle(_("White's flag fell"));
9422 if (appData.autoCallFlag) {
9423 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9430 if (blackTimeRemaining <= 0) {
9433 if (appData.icsActive) {
9434 if (appData.autoCallFlag &&
9435 gameMode == IcsPlayingWhite && !whiteFlag) {
9436 SendToICS(ics_prefix);
9437 SendToICS("flag\n");
9441 DisplayTitle(_("Both flags fell"));
9443 DisplayTitle(_("Black's flag fell"));
9444 if (appData.autoCallFlag) {
9445 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9458 if (!appData.clockMode || appData.icsActive ||
9459 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9461 if (timeIncrement >= 0) {
9462 if (WhiteOnMove(forwardMostMove)) {
9463 blackTimeRemaining += timeIncrement;
9465 whiteTimeRemaining += timeIncrement;
9469 * add time to clocks when time control is achieved
9471 if (movesPerSession) {
9472 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9474 /* White made time control */
9475 whiteTimeRemaining += timeControl;
9478 /* Black made time control */
9479 blackTimeRemaining += timeControl;
9490 int wom = gameMode == EditPosition ?
9491 !blackPlaysFirst : WhiteOnMove(currentMove);
9492 DisplayWhiteClock(whiteTimeRemaining, wom);
9493 DisplayBlackClock(blackTimeRemaining, !wom);
9497 /* Timekeeping seems to be a portability nightmare. I think everyone
9498 has ftime(), but I'm really not sure, so I'm including some ifdefs
9499 to use other calls if you don't. Clocks will be less accurate if
9500 you have neither ftime nor gettimeofday.
9503 /* Get the current time as a TimeMark */
9508 #if HAVE_GETTIMEOFDAY
9510 struct timeval timeVal;
9511 struct timezone timeZone;
9513 gettimeofday(&timeVal, &timeZone);
9514 tm->sec = (long) timeVal.tv_sec;
9515 tm->ms = (int) (timeVal.tv_usec / 1000L);
9517 #else /*!HAVE_GETTIMEOFDAY*/
9520 #include <sys/timeb.h>
9524 tm->sec = (long) timeB.time;
9525 tm->ms = (int) timeB.millitm;
9527 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9528 tm->sec = (long) time(NULL);
9534 /* Return the difference in milliseconds between two
9535 time marks. We assume the difference will fit in a long!
9538 SubtractTimeMarks(tm2, tm1)
9539 TimeMark *tm2, *tm1;
9541 return 1000L*(tm2->sec - tm1->sec) +
9542 (long) (tm2->ms - tm1->ms);
9547 * Code to manage the game clocks.
9549 * In tournament play, black starts the clock and then white makes a move.
9550 * We give the human user a slight advantage if he is playing white---the
9551 * clocks don't run until he makes his first move, so it takes zero time.
9552 * Also, we don't account for network lag, so we could get out of sync
9553 * with GNU Chess's clock -- but then, referees are always right.
9556 static TimeMark tickStartTM;
9557 static long intendedTickLength;
9560 NextTickLength(timeRemaining)
9563 long nominalTickLength, nextTickLength;
9565 if (timeRemaining > 0L && timeRemaining <= 10000L)
9566 nominalTickLength = 100L;
9568 nominalTickLength = 1000L;
9569 nextTickLength = timeRemaining % nominalTickLength;
9570 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9572 return nextTickLength;
9575 /* Stop clocks and reset to a fresh time control */
9579 (void) StopClockTimer();
9580 if (appData.icsActive) {
9581 whiteTimeRemaining = blackTimeRemaining = 0;
9583 whiteTimeRemaining = blackTimeRemaining = timeControl;
9585 if (whiteFlag || blackFlag) {
9587 whiteFlag = blackFlag = FALSE;
9589 DisplayBothClocks();
9592 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9594 /* Decrement running clock by amount of time that has passed */
9599 long lastTickLength, fudge;
9602 if (!appData.clockMode) return;
9603 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9607 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9609 /* Fudge if we woke up a little too soon */
9610 fudge = intendedTickLength - lastTickLength;
9611 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9613 if (WhiteOnMove(forwardMostMove)) {
9614 timeRemaining = whiteTimeRemaining -= lastTickLength;
9615 DisplayWhiteClock(whiteTimeRemaining - fudge,
9616 WhiteOnMove(currentMove));
9618 timeRemaining = blackTimeRemaining -= lastTickLength;
9619 DisplayBlackClock(blackTimeRemaining - fudge,
9620 !WhiteOnMove(currentMove));
9623 if (CheckFlags()) return;
9626 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9627 StartClockTimer(intendedTickLength);
9629 /* if the time remaining has fallen below the alarm threshold, sound the
9630 * alarm. if the alarm has sounded and (due to a takeback or time control
9631 * with increment) the time remaining has increased to a level above the
9632 * threshold, reset the alarm so it can sound again.
9635 if (appData.icsActive && appData.icsAlarm) {
9637 /* make sure we are dealing with the user's clock */
9638 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9639 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9642 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9643 alarmSounded = FALSE;
9644 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9646 alarmSounded = TRUE;
9652 /* A player has just moved, so stop the previously running
9653 clock and (if in clock mode) start the other one.
9654 We redisplay both clocks in case we're in ICS mode, because
9655 ICS gives us an update to both clocks after every move.
9656 Note that this routine is called *after* forwardMostMove
9657 is updated, so the last fractional tick must be subtracted
9658 from the color that is *not* on move now.
9663 long lastTickLength;
9665 int flagged = FALSE;
9669 if (StopClockTimer() && appData.clockMode) {
9670 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9671 if (WhiteOnMove(forwardMostMove)) {
9672 blackTimeRemaining -= lastTickLength;
9674 whiteTimeRemaining -= lastTickLength;
9676 flagged = CheckFlags();
9680 if (flagged || !appData.clockMode) return;
9683 case MachinePlaysBlack:
9684 case MachinePlaysWhite:
9685 case BeginningOfGame:
9686 if (pausing) return;
9690 case PlayFromGameFile:
9699 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9700 whiteTimeRemaining : blackTimeRemaining);
9701 StartClockTimer(intendedTickLength);
9705 /* Stop both clocks */
9709 long lastTickLength;
9712 if (!StopClockTimer()) return;
9713 if (!appData.clockMode) return;
9717 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9718 if (WhiteOnMove(forwardMostMove)) {
9719 whiteTimeRemaining -= lastTickLength;
9720 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9722 blackTimeRemaining -= lastTickLength;
9723 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9728 /* Start clock of player on move. Time may have been reset, so
9729 if clock is already running, stop and restart it. */
9733 (void) StopClockTimer(); /* in case it was running already */
9734 DisplayBothClocks();
9735 if (CheckFlags()) return;
9737 if (!appData.clockMode) return;
9738 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9740 GetTimeMark(&tickStartTM);
9741 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9742 whiteTimeRemaining : blackTimeRemaining);
9743 StartClockTimer(intendedTickLength);
9750 long second, minute, hour, day;
9752 static char buf[32];
9754 if (ms > 0 && ms <= 9900) {
9755 /* convert milliseconds to tenths, rounding up */
9756 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9758 sprintf(buf, " %03.1f ", tenths/10.0);
9762 /* convert milliseconds to seconds, rounding up */
9763 /* use floating point to avoid strangeness of integer division
9764 with negative dividends on many machines */
9765 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9772 day = second / (60 * 60 * 24);
9773 second = second % (60 * 60 * 24);
9774 hour = second / (60 * 60);
9775 second = second % (60 * 60);
9776 minute = second / 60;
9777 second = second % 60;
9780 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9781 sign, day, hour, minute, second);
9783 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9785 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9792 * This is necessary because some C libraries aren't ANSI C compliant yet.
9795 StrStr(string, match)
9796 char *string, *match;
9800 length = strlen(match);
9802 for (i = strlen(string) - length; i >= 0; i--, string++)
9803 if (!strncmp(match, string, length))
9810 StrCaseStr(string, match)
9811 char *string, *match;
9815 length = strlen(match);
9817 for (i = strlen(string) - length; i >= 0; i--, string++) {
9818 for (j = 0; j < length; j++) {
9819 if (ToLower(match[j]) != ToLower(string[j]))
9822 if (j == length) return string;
9836 c1 = ToLower(*s1++);
9837 c2 = ToLower(*s2++);
9838 if (c1 > c2) return 1;
9839 if (c1 < c2) return -1;
9840 if (c1 == NULLCHAR) return 0;
9849 return isupper(c) ? tolower(c) : c;
9857 return islower(c) ? toupper(c) : c;
9859 #endif /* !_amigados */
9867 if ((ret = (char *) malloc(strlen(s) + 1))) {
9874 StrSavePtr(s, savePtr)
9880 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9881 strcpy(*savePtr, s);
9893 clock = time((time_t *)NULL);
9894 tm = localtime(&clock);
9895 sprintf(buf, "%04d.%02d.%02d",
9896 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9897 return StrSave(buf);
9905 int i, j, fromX, fromY, toX, toY;
9911 whiteToPlay = (gameMode == EditPosition) ?
9912 !blackPlaysFirst : (move % 2 == 0);
9915 /* Piece placement data */
9916 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9918 for (j = 0; j < BOARD_SIZE; j++) {
9919 if (boards[move][i][j] == EmptySquare) {
9922 if (emptycount > 0) {
9923 *p++ = '0' + emptycount;
9926 *p++ = PieceToChar(boards[move][i][j]);
9929 if (emptycount > 0) {
9930 *p++ = '0' + emptycount;
9938 *p++ = whiteToPlay ? 'w' : 'b';
9941 /* !!We don't keep track of castling availability, so fake it */
9943 if (boards[move][0][4] == WhiteKing) {
9944 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9945 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9947 if (boards[move][7][4] == BlackKing) {
9948 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9949 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9951 if (q == p) *p++ = '-';
9954 /* En passant target square */
9955 if (move > backwardMostMove) {
9956 fromX = moveList[move - 1][0] - 'a';
9957 fromY = moveList[move - 1][1] - '1';
9958 toX = moveList[move - 1][2] - 'a';
9959 toY = moveList[move - 1][3] - '1';
9960 if (fromY == (whiteToPlay ? 6 : 1) &&
9961 toY == (whiteToPlay ? 4 : 3) &&
9962 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9964 /* 2-square pawn move just happened */
9966 *p++ = whiteToPlay ? '6' : '3';
9974 /* !!We don't keep track of halfmove clock for 50-move rule */
9978 /* Fullmove number */
9979 sprintf(p, "%d", (move / 2) + 1);
9981 return StrSave(buf);
9985 ParseFEN(board, blackPlaysFirst, fen)
9987 int *blackPlaysFirst;
9996 /* Piece placement data */
9997 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10000 if (*p == '/' || *p == ' ') {
10001 if (*p == '/') p++;
10002 emptycount = BOARD_SIZE - j;
10003 while (emptycount--) board[i][j++] = EmptySquare;
10005 } else if (isdigit(*p)) {
10006 emptycount = *p++ - '0';
10007 if (j + emptycount > BOARD_SIZE) return FALSE;
10008 while (emptycount--) board[i][j++] = EmptySquare;
10009 } else if (isalpha(*p)) {
10010 if (j >= BOARD_SIZE) return FALSE;
10011 board[i][j++] = CharToPiece(*p++);
10017 while (*p == '/' || *p == ' ') p++;
10022 *blackPlaysFirst = FALSE;
10025 *blackPlaysFirst = TRUE;
10030 /* !!We ignore the rest of the FEN notation */
10035 EditPositionPasteFEN(char *fen)
10038 Board initial_position;
10040 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10041 DisplayError(_("Bad FEN position in clipboard"), 0);
10044 int savedBlackPlaysFirst = blackPlaysFirst;
10045 EditPositionEvent();
10046 blackPlaysFirst = savedBlackPlaysFirst;
10047 CopyBoard(boards[0], initial_position);
10048 EditPositionDone();
10049 DisplayBothClocks();
10050 DrawPosition(FALSE, boards[currentMove]);