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;
1493 /* For zippy color lines of winboard
1494 * cleanup for gcc compiler */
1500 if (appData.debugMode) {
1502 fprintf(debugFP, "<ICS: ");
1503 show_bytes(debugFP, data, count);
1504 fprintf(debugFP, "\n");
1510 /* If last read ended with a partial line that we couldn't parse,
1511 prepend it to the new read and try again. */
1512 if (leftover_len > 0) {
1513 for (i=0; i<leftover_len; i++)
1514 buf[i] = buf[leftover_start + i];
1517 /* Copy in new characters, removing nulls and \r's */
1518 buf_len = leftover_len;
1519 for (i = 0; i < count; i++) {
1520 if (data[i] != NULLCHAR && data[i] != '\r')
1521 buf[buf_len++] = data[i];
1524 buf[buf_len] = NULLCHAR;
1525 next_out = leftover_len;
1529 while (i < buf_len) {
1530 /* Deal with part of the TELNET option negotiation
1531 protocol. We refuse to do anything beyond the
1532 defaults, except that we allow the WILL ECHO option,
1533 which ICS uses to turn off password echoing when we are
1534 directly connected to it. We reject this option
1535 if localLineEditing mode is on (always on in xboard)
1536 and we are talking to port 23, which might be a real
1537 telnet server that will try to keep WILL ECHO on permanently.
1539 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1540 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1541 unsigned char option;
1543 switch ((unsigned char) buf[++i]) {
1545 if (appData.debugMode)
1546 fprintf(debugFP, "\n<WILL ");
1547 switch (option = (unsigned char) buf[++i]) {
1549 if (appData.debugMode)
1550 fprintf(debugFP, "ECHO ");
1551 /* Reply only if this is a change, according
1552 to the protocol rules. */
1553 if (remoteEchoOption) break;
1554 if (appData.localLineEditing &&
1555 atoi(appData.icsPort) == TN_PORT) {
1556 TelnetRequest(TN_DONT, TN_ECHO);
1559 TelnetRequest(TN_DO, TN_ECHO);
1560 remoteEchoOption = TRUE;
1564 if (appData.debugMode)
1565 fprintf(debugFP, "%d ", option);
1566 /* Whatever this is, we don't want it. */
1567 TelnetRequest(TN_DONT, option);
1572 if (appData.debugMode)
1573 fprintf(debugFP, "\n<WONT ");
1574 switch (option = (unsigned char) buf[++i]) {
1576 if (appData.debugMode)
1577 fprintf(debugFP, "ECHO ");
1578 /* Reply only if this is a change, according
1579 to the protocol rules. */
1580 if (!remoteEchoOption) break;
1582 TelnetRequest(TN_DONT, TN_ECHO);
1583 remoteEchoOption = FALSE;
1586 if (appData.debugMode)
1587 fprintf(debugFP, "%d ", (unsigned char) option);
1588 /* Whatever this is, it must already be turned
1589 off, because we never agree to turn on
1590 anything non-default, so according to the
1591 protocol rules, we don't reply. */
1596 if (appData.debugMode)
1597 fprintf(debugFP, "\n<DO ");
1598 switch (option = (unsigned char) buf[++i]) {
1600 /* Whatever this is, we refuse to do it. */
1601 if (appData.debugMode)
1602 fprintf(debugFP, "%d ", option);
1603 TelnetRequest(TN_WONT, option);
1608 if (appData.debugMode)
1609 fprintf(debugFP, "\n<DONT ");
1610 switch (option = (unsigned char) buf[++i]) {
1612 if (appData.debugMode)
1613 fprintf(debugFP, "%d ", option);
1614 /* Whatever this is, we are already not doing
1615 it, because we never agree to do anything
1616 non-default, so according to the protocol
1617 rules, we don't reply. */
1622 if (appData.debugMode)
1623 fprintf(debugFP, "\n<IAC ");
1624 /* Doubled IAC; pass it through */
1628 if (appData.debugMode)
1629 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1630 /* Drop all other telnet commands on the floor */
1633 if (oldi > next_out)
1634 SendToPlayer(&buf[next_out], oldi - next_out);
1640 /* OK, this at least will *usually* work */
1641 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1645 if (loggedOn && !intfSet) {
1646 if (ics_type == ICS_ICC) {
1648 "/set-quietly interface %s\n/set-quietly style 12\n",
1651 } else if (ics_type == ICS_CHESSNET) {
1652 sprintf(str, "/style 12\n");
1654 strcpy(str, "alias $ @\n$set interface ");
1655 strcat(str, programVersion);
1656 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1658 strcat(str, "$iset nohighlight 1\n");
1660 strcat(str, "$iset lock 1\n$style 12\n");
1666 if (started == STARTED_COMMENT) {
1667 /* Accumulate characters in comment */
1668 parse[parse_pos++] = buf[i];
1669 if (buf[i] == '\n') {
1670 parse[parse_pos] = NULLCHAR;
1671 AppendComment(forwardMostMove, StripHighlight(parse));
1672 started = STARTED_NONE;
1674 /* Don't match patterns against characters in chatter */
1679 if (started == STARTED_CHATTER) {
1680 if (buf[i] != '\n') {
1681 /* Don't match patterns against characters in chatter */
1685 started = STARTED_NONE;
1688 /* Kludge to deal with rcmd protocol */
1689 if (firstTime && looking_at(buf, &i, "\001*")) {
1690 DisplayFatalError(&buf[1], 0, 1);
1696 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1699 if (appData.debugMode)
1700 fprintf(debugFP, "ics_type %d\n", ics_type);
1703 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1704 ics_type = ICS_FICS;
1706 if (appData.debugMode)
1707 fprintf(debugFP, "ics_type %d\n", ics_type);
1710 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1711 ics_type = ICS_CHESSNET;
1713 if (appData.debugMode)
1714 fprintf(debugFP, "ics_type %d\n", ics_type);
1719 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1720 looking_at(buf, &i, "Logging you in as \"*\"") ||
1721 looking_at(buf, &i, "will be \"*\""))) {
1722 strcpy(ics_handle, star_match[0]);
1726 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1728 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1729 DisplayIcsInteractionTitle(buf);
1730 have_set_title = TRUE;
1733 /* skip finger notes */
1734 if (started == STARTED_NONE &&
1735 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1736 (buf[i] == '1' && buf[i+1] == '0')) &&
1737 buf[i+2] == ':' && buf[i+3] == ' ') {
1738 started = STARTED_CHATTER;
1743 /* skip formula vars */
1744 if (started == STARTED_NONE &&
1745 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1746 started = STARTED_CHATTER;
1752 if (appData.zippyTalk || appData.zippyPlay) {
1755 /* Backup address for color zippy lines */
1757 if (loggedOn == TRUE)
1758 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
1759 (appData.zippyPlay && ZippyMatch(buf, &backup)));
1761 if (ZippyControl(buf, &i) ||
1762 ZippyConverse(buf, &i) ||
1763 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1770 if (/* Don't color "message" or "messages" output */
1771 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1772 looking_at(buf, &i, "*. * at *:*: ") ||
1773 looking_at(buf, &i, "--* (*:*): ") ||
1774 /* Regular tells and says */
1775 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1776 looking_at(buf, &i, "* (your partner) tells you: ") ||
1777 looking_at(buf, &i, "* says: ") ||
1778 /* Message notifications (same color as tells) */
1779 looking_at(buf, &i, "* has left a message ") ||
1780 looking_at(buf, &i, "* just sent you a message:\n") ||
1781 /* Whispers and kibitzes */
1782 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1783 looking_at(buf, &i, "* kibitzes: ") ||
1785 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1787 if (tkind == 1 && strchr(star_match[0], ':')) {
1788 /* Avoid "tells you:" spoofs in channels */
1791 if (star_match[0][0] == NULLCHAR ||
1792 strchr(star_match[0], ' ') ||
1793 (tkind == 3 && strchr(star_match[1], ' '))) {
1794 /* Reject bogus matches */
1797 if (appData.colorize) {
1798 if (oldi > next_out) {
1799 SendToPlayer(&buf[next_out], oldi - next_out);
1804 Colorize(ColorTell, FALSE);
1805 curColor = ColorTell;
1808 Colorize(ColorKibitz, FALSE);
1809 curColor = ColorKibitz;
1812 p = strrchr(star_match[1], '(');
1819 Colorize(ColorChannel1, FALSE);
1820 curColor = ColorChannel1;
1822 Colorize(ColorChannel, FALSE);
1823 curColor = ColorChannel;
1827 curColor = ColorNormal;
1831 if (started == STARTED_NONE && appData.autoComment &&
1832 (gameMode == IcsObserving ||
1833 gameMode == IcsPlayingWhite ||
1834 gameMode == IcsPlayingBlack)) {
1835 parse_pos = i - oldi;
1836 memcpy(parse, &buf[oldi], parse_pos);
1837 parse[parse_pos] = NULLCHAR;
1838 started = STARTED_COMMENT;
1839 savingComment = TRUE;
1841 started = STARTED_CHATTER;
1842 savingComment = FALSE;
1849 if (looking_at(buf, &i, "* s-shouts: ") ||
1850 looking_at(buf, &i, "* c-shouts: ")) {
1851 if (appData.colorize) {
1852 if (oldi > next_out) {
1853 SendToPlayer(&buf[next_out], oldi - next_out);
1856 Colorize(ColorSShout, FALSE);
1857 curColor = ColorSShout;
1860 started = STARTED_CHATTER;
1864 if (looking_at(buf, &i, "--->")) {
1869 if (looking_at(buf, &i, "* shouts: ") ||
1870 looking_at(buf, &i, "--> ")) {
1871 if (appData.colorize) {
1872 if (oldi > next_out) {
1873 SendToPlayer(&buf[next_out], oldi - next_out);
1876 Colorize(ColorShout, FALSE);
1877 curColor = ColorShout;
1880 started = STARTED_CHATTER;
1884 if (looking_at( buf, &i, "Challenge:")) {
1885 if (appData.colorize) {
1886 if (oldi > next_out) {
1887 SendToPlayer(&buf[next_out], oldi - next_out);
1890 Colorize(ColorChallenge, FALSE);
1891 curColor = ColorChallenge;
1897 if (looking_at(buf, &i, "* offers you") ||
1898 looking_at(buf, &i, "* offers to be") ||
1899 looking_at(buf, &i, "* would like to") ||
1900 looking_at(buf, &i, "* requests to") ||
1901 looking_at(buf, &i, "Your opponent offers") ||
1902 looking_at(buf, &i, "Your opponent requests")) {
1904 if (appData.colorize) {
1905 if (oldi > next_out) {
1906 SendToPlayer(&buf[next_out], oldi - next_out);
1909 Colorize(ColorRequest, FALSE);
1910 curColor = ColorRequest;
1915 if (looking_at(buf, &i, "* (*) seeking")) {
1916 if (appData.colorize) {
1917 if (oldi > next_out) {
1918 SendToPlayer(&buf[next_out], oldi - next_out);
1921 Colorize(ColorSeek, FALSE);
1922 curColor = ColorSeek;
1927 if (looking_at(buf, &i, "\\ ")) {
1928 if (prevColor != ColorNormal) {
1929 if (oldi > next_out) {
1930 SendToPlayer(&buf[next_out], oldi - next_out);
1933 Colorize(prevColor, TRUE);
1934 curColor = prevColor;
1936 if (savingComment) {
1937 parse_pos = i - oldi;
1938 memcpy(parse, &buf[oldi], parse_pos);
1939 parse[parse_pos] = NULLCHAR;
1940 started = STARTED_COMMENT;
1942 started = STARTED_CHATTER;
1947 if (looking_at(buf, &i, "Black Strength :") ||
1948 looking_at(buf, &i, "<<< style 10 board >>>") ||
1949 looking_at(buf, &i, "<10>") ||
1950 looking_at(buf, &i, "#@#")) {
1951 /* Wrong board style */
1953 SendToICS(ics_prefix);
1954 SendToICS("set style 12\n");
1955 SendToICS(ics_prefix);
1956 SendToICS("refresh\n");
1960 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1962 have_sent_ICS_logon = 1;
1966 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1967 (looking_at(buf, &i, "\n<12> ") ||
1968 looking_at(buf, &i, "<12> "))) {
1970 if (oldi > next_out) {
1971 SendToPlayer(&buf[next_out], oldi - next_out);
1974 started = STARTED_BOARD;
1979 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
1980 looking_at(buf, &i, "<b1> ")) {
1981 if (oldi > next_out) {
1982 SendToPlayer(&buf[next_out], oldi - next_out);
1985 started = STARTED_HOLDINGS;
1990 if (looking_at(buf, &i, "* *vs. * *--- *")) {
1992 /* Header for a move list -- first line */
1994 switch (ics_getting_history) {
1998 case BeginningOfGame:
1999 /* User typed "moves" or "oldmoves" while we
2000 were idle. Pretend we asked for these
2001 moves and soak them up so user can step
2002 through them and/or save them.
2005 gameMode = IcsObserving;
2008 ics_getting_history = H_GOT_UNREQ_HEADER;
2010 case EditGame: /*?*/
2011 case EditPosition: /*?*/
2012 /* Should above feature work in these modes too? */
2013 /* For now it doesn't */
2014 ics_getting_history = H_GOT_UNWANTED_HEADER;
2017 ics_getting_history = H_GOT_UNWANTED_HEADER;
2022 /* Is this the right one? */
2023 if (gameInfo.white && gameInfo.black &&
2024 strcmp(gameInfo.white, star_match[0]) == 0 &&
2025 strcmp(gameInfo.black, star_match[2]) == 0) {
2027 ics_getting_history = H_GOT_REQ_HEADER;
2030 case H_GOT_REQ_HEADER:
2031 case H_GOT_UNREQ_HEADER:
2032 case H_GOT_UNWANTED_HEADER:
2033 case H_GETTING_MOVES:
2034 /* Should not happen */
2035 DisplayError(_("Error gathering move list: two headers"), 0);
2036 ics_getting_history = H_FALSE;
2040 /* Save player ratings into gameInfo if needed */
2041 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2042 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2043 (gameInfo.whiteRating == -1 ||
2044 gameInfo.blackRating == -1)) {
2046 gameInfo.whiteRating = string_to_rating(star_match[1]);
2047 gameInfo.blackRating = string_to_rating(star_match[3]);
2048 if (appData.debugMode)
2049 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2050 gameInfo.whiteRating, gameInfo.blackRating);
2055 if (looking_at(buf, &i,
2056 "* * match, initial time: * minute*, increment: * second")) {
2057 /* Header for a move list -- second line */
2058 /* Initial board will follow if this is a wild game */
2060 if (gameInfo.event != NULL) free(gameInfo.event);
2061 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2062 gameInfo.event = StrSave(str);
2063 gameInfo.variant = StringToVariant(gameInfo.event);
2067 if (looking_at(buf, &i, "Move ")) {
2068 /* Beginning of a move list */
2069 switch (ics_getting_history) {
2071 /* Normally should not happen */
2072 /* Maybe user hit reset while we were parsing */
2075 /* Happens if we are ignoring a move list that is not
2076 * the one we just requested. Common if the user
2077 * tries to observe two games without turning off
2080 case H_GETTING_MOVES:
2081 /* Should not happen */
2082 DisplayError(_("Error gathering move list: nested"), 0);
2083 ics_getting_history = H_FALSE;
2085 case H_GOT_REQ_HEADER:
2086 ics_getting_history = H_GETTING_MOVES;
2087 started = STARTED_MOVES;
2089 if (oldi > next_out) {
2090 SendToPlayer(&buf[next_out], oldi - next_out);
2093 case H_GOT_UNREQ_HEADER:
2094 ics_getting_history = H_GETTING_MOVES;
2095 started = STARTED_MOVES_NOHIDE;
2098 case H_GOT_UNWANTED_HEADER:
2099 ics_getting_history = H_FALSE;
2105 if (looking_at(buf, &i, "% ") ||
2106 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2107 && looking_at(buf, &i, "}*"))) {
2108 savingComment = FALSE;
2111 case STARTED_MOVES_NOHIDE:
2112 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2113 parse[parse_pos + i - oldi] = NULLCHAR;
2114 ParseGameHistory(parse);
2116 if (appData.zippyPlay && first.initDone) {
2117 FeedMovesToProgram(&first, forwardMostMove);
2118 if (gameMode == IcsPlayingWhite) {
2119 if (WhiteOnMove(forwardMostMove)) {
2120 if (first.sendTime) {
2121 if (first.useColors) {
2122 SendToProgram("black\n", &first);
2124 SendTimeRemaining(&first, TRUE);
2126 if (first.useColors) {
2127 SendToProgram("white\ngo\n", &first);
2129 SendToProgram("go\n", &first);
2131 first.maybeThinking = TRUE;
2133 if (first.usePlayother) {
2134 if (first.sendTime) {
2135 SendTimeRemaining(&first, TRUE);
2137 SendToProgram("playother\n", &first);
2143 } else if (gameMode == IcsPlayingBlack) {
2144 if (!WhiteOnMove(forwardMostMove)) {
2145 if (first.sendTime) {
2146 if (first.useColors) {
2147 SendToProgram("white\n", &first);
2149 SendTimeRemaining(&first, FALSE);
2151 if (first.useColors) {
2152 SendToProgram("black\ngo\n", &first);
2154 SendToProgram("go\n", &first);
2156 first.maybeThinking = TRUE;
2158 if (first.usePlayother) {
2159 if (first.sendTime) {
2160 SendTimeRemaining(&first, FALSE);
2162 SendToProgram("playother\n", &first);
2171 if (gameMode == IcsObserving && ics_gamenum == -1) {
2172 /* Moves came from oldmoves or moves command
2173 while we weren't doing anything else.
2175 currentMove = forwardMostMove;
2176 ClearHighlights();/*!!could figure this out*/
2177 flipView = appData.flipView;
2178 DrawPosition(FALSE, boards[currentMove]);
2179 DisplayBothClocks();
2180 sprintf(str, "%s vs. %s",
2181 gameInfo.white, gameInfo.black);
2185 /* Moves were history of an active game */
2186 if (gameInfo.resultDetails != NULL) {
2187 free(gameInfo.resultDetails);
2188 gameInfo.resultDetails = NULL;
2191 HistorySet(parseList, backwardMostMove,
2192 forwardMostMove, currentMove-1);
2193 DisplayMove(currentMove - 1);
2194 if (started == STARTED_MOVES) next_out = i;
2195 started = STARTED_NONE;
2196 ics_getting_history = H_FALSE;
2199 case STARTED_OBSERVE:
2200 started = STARTED_NONE;
2201 SendToICS(ics_prefix);
2202 SendToICS("refresh\n");
2211 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2212 started == STARTED_HOLDINGS ||
2213 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2214 /* Accumulate characters in move list or board */
2215 parse[parse_pos++] = buf[i];
2218 /* Start of game messages. Mostly we detect start of game
2219 when the first board image arrives. On some versions
2220 of the ICS, though, we need to do a "refresh" after starting
2221 to observe in order to get the current board right away. */
2222 if (looking_at(buf, &i, "Adding game * to observation list")) {
2223 started = STARTED_OBSERVE;
2227 /* Handle auto-observe */
2228 if (appData.autoObserve &&
2229 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2230 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2232 /* Choose the player that was highlighted, if any. */
2233 if (star_match[0][0] == '\033' ||
2234 star_match[1][0] != '\033') {
2235 player = star_match[0];
2237 player = star_match[2];
2239 sprintf(str, "%sobserve %s\n",
2240 ics_prefix, StripHighlightAndTitle(player));
2243 /* Save ratings from notify string */
2244 strcpy(player1Name, star_match[0]);
2245 player1Rating = string_to_rating(star_match[1]);
2246 strcpy(player2Name, star_match[2]);
2247 player2Rating = string_to_rating(star_match[3]);
2249 if (appData.debugMode)
2251 "Ratings from 'Game notification:' %s %d, %s %d\n",
2252 player1Name, player1Rating,
2253 player2Name, player2Rating);
2258 /* Deal with automatic examine mode after a game,
2259 and with IcsObserving -> IcsExamining transition */
2260 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2261 looking_at(buf, &i, "has made you an examiner of game *")) {
2263 int gamenum = atoi(star_match[0]);
2264 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2265 gamenum == ics_gamenum) {
2266 /* We were already playing or observing this game;
2267 no need to refetch history */
2268 gameMode = IcsExamining;
2270 pauseExamForwardMostMove = forwardMostMove;
2271 } else if (currentMove < forwardMostMove) {
2272 ForwardInner(forwardMostMove);
2275 /* I don't think this case really can happen */
2276 SendToICS(ics_prefix);
2277 SendToICS("refresh\n");
2282 /* Error messages */
2283 if (ics_user_moved) {
2284 if (looking_at(buf, &i, "Illegal move") ||
2285 looking_at(buf, &i, "Not a legal move") ||
2286 looking_at(buf, &i, "Your king is in check") ||
2287 looking_at(buf, &i, "It isn't your turn") ||
2288 looking_at(buf, &i, "It is not your move")) {
2291 if (forwardMostMove > backwardMostMove) {
2292 currentMove = --forwardMostMove;
2293 DisplayMove(currentMove - 1); /* before DMError */
2294 DisplayMoveError("Illegal move (rejected by ICS)");
2295 DrawPosition(FALSE, boards[currentMove]);
2297 DisplayBothClocks();
2303 if (looking_at(buf, &i, "still have time") ||
2304 looking_at(buf, &i, "not out of time") ||
2305 looking_at(buf, &i, "either player is out of time") ||
2306 looking_at(buf, &i, "has timeseal; checking")) {
2307 /* We must have called his flag a little too soon */
2308 whiteFlag = blackFlag = FALSE;
2312 if (looking_at(buf, &i, "added * seconds to") ||
2313 looking_at(buf, &i, "seconds were added to")) {
2314 /* Update the clocks */
2315 SendToICS(ics_prefix);
2316 SendToICS("refresh\n");
2320 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2321 ics_clock_paused = TRUE;
2326 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2327 ics_clock_paused = FALSE;
2332 /* Grab player ratings from the Creating: message.
2333 Note we have to check for the special case when
2334 the ICS inserts things like [white] or [black]. */
2335 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2336 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2338 0 player 1 name (not necessarily white)
2340 2 empty, white, or black (IGNORED)
2341 3 player 2 name (not necessarily black)
2344 The names/ratings are sorted out when the game
2345 actually starts (below).
2347 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2348 player1Rating = string_to_rating(star_match[1]);
2349 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2350 player2Rating = string_to_rating(star_match[4]);
2352 if (appData.debugMode)
2354 "Ratings from 'Creating:' %s %d, %s %d\n",
2355 player1Name, player1Rating,
2356 player2Name, player2Rating);
2361 /* Improved generic start/end-of-game messages */
2362 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2363 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2364 /* If tkind == 0: */
2365 /* star_match[0] is the game number */
2366 /* [1] is the white player's name */
2367 /* [2] is the black player's name */
2368 /* For end-of-game: */
2369 /* [3] is the reason for the game end */
2370 /* [4] is a PGN end game-token, preceded by " " */
2371 /* For start-of-game: */
2372 /* [3] begins with "Creating" or "Continuing" */
2373 /* [4] is " *" or empty (don't care). */
2374 int gamenum = atoi(star_match[0]);
2375 char *whitename, *blackname, *why, *endtoken;
2376 ChessMove endtype = (ChessMove) 0;
2379 whitename = star_match[1];
2380 blackname = star_match[2];
2381 why = star_match[3];
2382 endtoken = star_match[4];
2384 whitename = star_match[1];
2385 blackname = star_match[3];
2386 why = star_match[5];
2387 endtoken = star_match[6];
2390 /* Game start messages */
2391 if (strncmp(why, "Creating ", 9) == 0 ||
2392 strncmp(why, "Continuing ", 11) == 0) {
2393 gs_gamenum = gamenum;
2394 strcpy(gs_kind, strchr(why, ' ') + 1);
2396 if (appData.zippyPlay) {
2397 ZippyGameStart(whitename, blackname);
2403 /* Game end messages */
2404 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2405 ics_gamenum != gamenum) {
2408 while (endtoken[0] == ' ') endtoken++;
2409 switch (endtoken[0]) {
2412 endtype = GameUnfinished;
2415 endtype = BlackWins;
2418 if (endtoken[1] == '/')
2419 endtype = GameIsDrawn;
2421 endtype = WhiteWins;
2424 GameEnds(endtype, why, GE_ICS);
2426 if (appData.zippyPlay && first.initDone) {
2427 ZippyGameEnd(endtype, why);
2428 if (first.pr == NULL) {
2429 /* Start the next process early so that we'll
2430 be ready for the next challenge */
2431 StartChessProgram(&first);
2433 /* Send "new" early, in case this command takes
2434 a long time to finish, so that we'll be ready
2435 for the next challenge. */
2442 if (looking_at(buf, &i, "Removing game * from observation") ||
2443 looking_at(buf, &i, "no longer observing game *") ||
2444 looking_at(buf, &i, "Game * (*) has no examiners")) {
2445 if (gameMode == IcsObserving &&
2446 atoi(star_match[0]) == ics_gamenum)
2448 /* icsEngineAnalyze */
2449 if (appData.icsEngineAnalyze) {
2456 ics_user_moved = FALSE;
2461 if (looking_at(buf, &i, "no longer examining game *")) {
2462 if (gameMode == IcsExamining &&
2463 atoi(star_match[0]) == ics_gamenum)
2467 ics_user_moved = FALSE;
2472 /* Advance leftover_start past any newlines we find,
2473 so only partial lines can get reparsed */
2474 if (looking_at(buf, &i, "\n")) {
2475 prevColor = curColor;
2476 if (curColor != ColorNormal) {
2477 if (oldi > next_out) {
2478 SendToPlayer(&buf[next_out], oldi - next_out);
2481 Colorize(ColorNormal, FALSE);
2482 curColor = ColorNormal;
2484 if (started == STARTED_BOARD) {
2485 started = STARTED_NONE;
2486 parse[parse_pos] = NULLCHAR;
2487 ParseBoard12(parse);
2490 /* Send premove here */
2491 if (appData.premove) {
2493 if (currentMove == 0 &&
2494 gameMode == IcsPlayingWhite &&
2495 appData.premoveWhite) {
2496 sprintf(str, "%s%s\n", ics_prefix,
2497 appData.premoveWhiteText);
2498 if (appData.debugMode)
2499 fprintf(debugFP, "Sending premove:\n");
2501 } else if (currentMove == 1 &&
2502 gameMode == IcsPlayingBlack &&
2503 appData.premoveBlack) {
2504 sprintf(str, "%s%s\n", ics_prefix,
2505 appData.premoveBlackText);
2506 if (appData.debugMode)
2507 fprintf(debugFP, "Sending premove:\n");
2509 } else if (gotPremove) {
2511 ClearPremoveHighlights();
2512 if (appData.debugMode)
2513 fprintf(debugFP, "Sending premove:\n");
2514 UserMoveEvent(premoveFromX, premoveFromY,
2515 premoveToX, premoveToY,
2520 /* Usually suppress following prompt */
2521 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2522 if (looking_at(buf, &i, "*% ")) {
2523 savingComment = FALSE;
2527 } else if (started == STARTED_HOLDINGS) {
2529 char new_piece[MSG_SIZ];
2530 started = STARTED_NONE;
2531 parse[parse_pos] = NULLCHAR;
2532 if (appData.debugMode)
2533 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2534 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2535 gamenum == ics_gamenum) {
2536 if (gameInfo.variant == VariantNormal) {
2537 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2538 /* Get a move list just to see the header, which
2539 will tell us whether this is really bug or zh */
2540 if (ics_getting_history == H_FALSE) {
2541 ics_getting_history = H_REQUESTED;
2542 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2546 new_piece[0] = NULLCHAR;
2547 sscanf(parse, "game %d white [%s black [%s <- %s",
2548 &gamenum, white_holding, black_holding,
2550 white_holding[strlen(white_holding)-1] = NULLCHAR;
2551 black_holding[strlen(black_holding)-1] = NULLCHAR;
2553 if (appData.zippyPlay && first.initDone) {
2554 ZippyHoldings(white_holding, black_holding,
2558 if (tinyLayout || smallLayout) {
2559 char wh[16], bh[16];
2560 PackHolding(wh, white_holding);
2561 PackHolding(bh, black_holding);
2562 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2563 gameInfo.white, gameInfo.black);
2565 sprintf(str, "%s [%s] vs. %s [%s]",
2566 gameInfo.white, white_holding,
2567 gameInfo.black, black_holding);
2569 DrawPosition(FALSE, NULL);
2572 /* Suppress following prompt */
2573 if (looking_at(buf, &i, "*% ")) {
2574 savingComment = FALSE;
2581 i++; /* skip unparsed character and loop back */
2584 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2585 started != STARTED_HOLDINGS && i > next_out) {
2586 SendToPlayer(&buf[next_out], i - next_out);
2590 leftover_len = buf_len - leftover_start;
2591 /* if buffer ends with something we couldn't parse,
2592 reparse it after appending the next read */
2594 } else if (count == 0) {
2595 RemoveInputSource(isr);
2596 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2598 DisplayFatalError(_("Error reading from ICS"), error, 1);
2603 /* Board style 12 looks like this:
2605 <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
2607 * The "<12> " is stripped before it gets to this routine. The two
2608 * trailing 0's (flip state and clock ticking) are later addition, and
2609 * some chess servers may not have them, or may have only the first.
2610 * Additional trailing fields may be added in the future.
2613 #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"
2615 #define RELATION_OBSERVING_PLAYED 0
2616 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2617 #define RELATION_PLAYING_MYMOVE 1
2618 #define RELATION_PLAYING_NOTMYMOVE -1
2619 #define RELATION_EXAMINING 2
2620 #define RELATION_ISOLATED_BOARD -3
2621 #define RELATION_STARTING_POSITION -4 /* FICS only */
2624 ParseBoard12(string)
2627 GameMode newGameMode;
2628 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
2629 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
2630 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2631 char to_play, board_chars[72];
2632 char move_str[500], str[500], elapsed_time[500];
2633 char black[32], white[32];
2635 int prevMove = currentMove;
2638 int fromX, fromY, toX, toY;
2641 fromX = fromY = toX = toY = -1;
2645 if (appData.debugMode)
2646 fprintf(debugFP, _("Parsing board: %s\n"), string);
2648 move_str[0] = NULLCHAR;
2649 elapsed_time[0] = NULLCHAR;
2650 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2651 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2652 &gamenum, white, black, &relation, &basetime, &increment,
2653 &white_stren, &black_stren, &white_time, &black_time,
2654 &moveNum, str, elapsed_time, move_str, &ics_flip,
2658 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2659 DisplayError(str, 0);
2663 /* Convert the move number to internal form */
2664 moveNum = (moveNum - 1) * 2;
2665 if (to_play == 'B') moveNum++;
2666 if (moveNum >= MAX_MOVES) {
2667 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2673 case RELATION_OBSERVING_PLAYED:
2674 case RELATION_OBSERVING_STATIC:
2675 if (gamenum == -1) {
2676 /* Old ICC buglet */
2677 relation = RELATION_OBSERVING_STATIC;
2679 newGameMode = IcsObserving;
2681 case RELATION_PLAYING_MYMOVE:
2682 case RELATION_PLAYING_NOTMYMOVE:
2684 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2685 IcsPlayingWhite : IcsPlayingBlack;
2687 case RELATION_EXAMINING:
2688 newGameMode = IcsExamining;
2690 case RELATION_ISOLATED_BOARD:
2692 /* Just display this board. If user was doing something else,
2693 we will forget about it until the next board comes. */
2694 newGameMode = IcsIdle;
2696 case RELATION_STARTING_POSITION:
2697 newGameMode = gameMode;
2701 /* Modify behavior for initial board display on move listing
2704 switch (ics_getting_history) {
2708 case H_GOT_REQ_HEADER:
2709 case H_GOT_UNREQ_HEADER:
2710 /* This is the initial position of the current game */
2711 gamenum = ics_gamenum;
2712 moveNum = 0; /* old ICS bug workaround */
2713 if (to_play == 'B') {
2714 startedFromSetupPosition = TRUE;
2715 blackPlaysFirst = TRUE;
2717 if (forwardMostMove == 0) forwardMostMove = 1;
2718 if (backwardMostMove == 0) backwardMostMove = 1;
2719 if (currentMove == 0) currentMove = 1;
2721 newGameMode = gameMode;
2722 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2724 case H_GOT_UNWANTED_HEADER:
2725 /* This is an initial board that we don't want */
2727 case H_GETTING_MOVES:
2728 /* Should not happen */
2729 DisplayError(_("Error gathering move list: extra board"), 0);
2730 ics_getting_history = H_FALSE;
2734 /* Take action if this is the first board of a new game, or of a
2735 different game than is currently being displayed. */
2736 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2737 relation == RELATION_ISOLATED_BOARD) {
2739 /* Forget the old game and get the history (if any) of the new one */
2740 if (gameMode != BeginningOfGame) {
2744 if (appData.autoRaiseBoard) BoardToTop();
2746 if (gamenum == -1) {
2747 newGameMode = IcsIdle;
2748 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2749 appData.getMoveList) {
2750 /* Need to get game history */
2751 ics_getting_history = H_REQUESTED;
2752 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2756 /* Initially flip the board to have black on the bottom if playing
2757 black or if the ICS flip flag is set, but let the user change
2758 it with the Flip View button. */
2759 flipView = appData.autoFlipView ?
2760 (newGameMode == IcsPlayingBlack) || ics_flip :
2763 /* Done with values from previous mode; copy in new ones */
2764 gameMode = newGameMode;
2766 ics_gamenum = gamenum;
2767 if (gamenum == gs_gamenum) {
2768 int klen = strlen(gs_kind);
2769 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2770 sprintf(str, "ICS %s", gs_kind);
2771 gameInfo.event = StrSave(str);
2773 gameInfo.event = StrSave("ICS game");
2775 gameInfo.site = StrSave(appData.icsHost);
2776 gameInfo.date = PGNDate();
2777 gameInfo.round = StrSave("-");
2778 gameInfo.white = StrSave(white);
2779 gameInfo.black = StrSave(black);
2780 timeControl = basetime * 60 * 1000;
2781 timeIncrement = increment * 1000;
2782 movesPerSession = 0;
2783 gameInfo.timeControl = TimeControlTagValue();
2784 gameInfo.variant = StringToVariant(gameInfo.event);
2786 /* Do we have the ratings? */
2787 if (strcmp(player1Name, white) == 0 &&
2788 strcmp(player2Name, black) == 0) {
2789 if (appData.debugMode)
2790 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2791 player1Rating, player2Rating);
2792 gameInfo.whiteRating = player1Rating;
2793 gameInfo.blackRating = player2Rating;
2794 } else if (strcmp(player2Name, white) == 0 &&
2795 strcmp(player1Name, black) == 0) {
2796 if (appData.debugMode)
2797 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2798 player2Rating, player1Rating);
2799 gameInfo.whiteRating = player2Rating;
2800 gameInfo.blackRating = player1Rating;
2802 player1Name[0] = player2Name[0] = NULLCHAR;
2804 /* Silence shouts if requested */
2805 if (appData.quietPlay &&
2806 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2807 SendToICS(ics_prefix);
2808 SendToICS("set shout 0\n");
2812 /* Deal with midgame name changes */
2814 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2815 if (gameInfo.white) free(gameInfo.white);
2816 gameInfo.white = StrSave(white);
2818 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2819 if (gameInfo.black) free(gameInfo.black);
2820 gameInfo.black = StrSave(black);
2824 /* Throw away game result if anything actually changes in examine mode */
2825 if (gameMode == IcsExamining && !newGame) {
2826 gameInfo.result = GameUnfinished;
2827 if (gameInfo.resultDetails != NULL) {
2828 free(gameInfo.resultDetails);
2829 gameInfo.resultDetails = NULL;
2833 /* In pausing && IcsExamining mode, we ignore boards coming
2834 in if they are in a different variation than we are. */
2835 if (pauseExamInvalid) return;
2836 if (pausing && gameMode == IcsExamining) {
2837 if (moveNum <= pauseExamForwardMostMove) {
2838 pauseExamInvalid = TRUE;
2839 forwardMostMove = pauseExamForwardMostMove;
2844 /* Parse the board */
2845 for (k = 0; k < 8; k++)
2846 for (j = 0; j < 8; j++)
2847 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2848 CopyBoard(boards[moveNum], board);
2850 startedFromSetupPosition =
2851 !CompareBoards(board, initialPosition);
2854 if (ics_getting_history == H_GOT_REQ_HEADER ||
2855 ics_getting_history == H_GOT_UNREQ_HEADER) {
2856 /* This was an initial position from a move list, not
2857 the current position */
2861 /* Update currentMove and known move number limits */
2862 newMove = newGame || moveNum > forwardMostMove;
2864 /* If we found takebacks during icsEngineAnalyze
2865 try send to engine */
2866 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
2867 takeback = forwardMostMove - moveNum;
2868 for (i = 0; i < takeback; i++) {
2869 if (appData.debugMode) fprintf(debugFP, "take back move\n");
2870 SendToProgram("undo\n", &first);
2874 forwardMostMove = backwardMostMove = currentMove = moveNum;
2875 if (gameMode == IcsExamining && moveNum == 0) {
2876 /* Workaround for ICS limitation: we are not told the wild
2877 type when starting to examine a game. But if we ask for
2878 the move list, the move list header will tell us */
2879 ics_getting_history = H_REQUESTED;
2880 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2883 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2884 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2885 forwardMostMove = moveNum;
2886 if (!pausing || currentMove > forwardMostMove)
2887 currentMove = forwardMostMove;
2889 /* New part of history that is not contiguous with old part */
2890 if (pausing && gameMode == IcsExamining) {
2891 pauseExamInvalid = TRUE;
2892 forwardMostMove = pauseExamForwardMostMove;
2895 forwardMostMove = backwardMostMove = currentMove = moveNum;
2896 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2897 ics_getting_history = H_REQUESTED;
2898 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2903 /* Update the clocks */
2904 if (strchr(elapsed_time, '.')) {
2906 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2907 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2909 /* Time is in seconds */
2910 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2911 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2916 if (appData.zippyPlay && newGame &&
2917 gameMode != IcsObserving && gameMode != IcsIdle &&
2918 gameMode != IcsExamining)
2919 ZippyFirstBoard(moveNum, basetime, increment);
2922 /* Put the move on the move list, first converting
2923 to canonical algebraic form. */
2925 if (moveNum <= backwardMostMove) {
2926 /* We don't know what the board looked like before
2928 strcpy(parseList[moveNum - 1], move_str);
2929 strcat(parseList[moveNum - 1], " ");
2930 strcat(parseList[moveNum - 1], elapsed_time);
2931 moveList[moveNum - 1][0] = NULLCHAR;
2932 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2933 &fromX, &fromY, &toX, &toY, &promoChar)) {
2934 (void) CoordsToAlgebraic(boards[moveNum - 1],
2935 PosFlags(moveNum - 1), EP_UNKNOWN,
2936 fromY, fromX, toY, toX, promoChar,
2937 parseList[moveNum-1]);
2938 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2944 strcat(parseList[moveNum - 1], "+");
2947 strcat(parseList[moveNum - 1], "#");
2950 strcat(parseList[moveNum - 1], " ");
2951 strcat(parseList[moveNum - 1], elapsed_time);
2952 /* currentMoveString is set as a side-effect of ParseOneMove */
2953 strcpy(moveList[moveNum - 1], currentMoveString);
2954 strcat(moveList[moveNum - 1], "\n");
2955 } else if (strcmp(move_str, "none") == 0) {
2956 /* Again, we don't know what the board looked like;
2957 this is really the start of the game. */
2958 parseList[moveNum - 1][0] = NULLCHAR;
2959 moveList[moveNum - 1][0] = NULLCHAR;
2960 backwardMostMove = moveNum;
2961 startedFromSetupPosition = TRUE;
2962 fromX = fromY = toX = toY = -1;
2964 /* Move from ICS was illegal!? Punt. */
2966 if (appData.testLegality && appData.debugMode) {
2967 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2968 DisplayError(str, 0);
2971 strcpy(parseList[moveNum - 1], move_str);
2972 strcat(parseList[moveNum - 1], " ");
2973 strcat(parseList[moveNum - 1], elapsed_time);
2974 moveList[moveNum - 1][0] = NULLCHAR;
2975 fromX = fromY = toX = toY = -1;
2979 /* Send move to chess program (BEFORE animating it). */
2980 if (appData.zippyPlay && !newGame && newMove &&
2981 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2983 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2984 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2985 if (moveList[moveNum - 1][0] == NULLCHAR) {
2986 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2988 DisplayError(str, 0);
2990 if (first.sendTime) {
2991 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2993 SendMoveToProgram(moveNum - 1, &first);
2996 if (first.useColors) {
2997 SendToProgram(gameMode == IcsPlayingWhite ?
2999 "black\ngo\n", &first);
3001 SendToProgram("go\n", &first);
3003 first.maybeThinking = TRUE;
3006 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3007 if (moveList[moveNum - 1][0] == NULLCHAR) {
3008 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3009 DisplayError(str, 0);
3011 SendMoveToProgram(moveNum - 1, &first);
3018 if (moveNum > 0 && !gotPremove) {
3019 /* If move comes from a remote source, animate it. If it
3020 isn't remote, it will have already been animated. */
3021 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3022 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3024 if (!pausing && appData.highlightLastMove) {
3025 SetHighlights(fromX, fromY, toX, toY);
3029 /* Start the clocks */
3030 whiteFlag = blackFlag = FALSE;
3031 appData.clockMode = !(basetime == 0 && increment == 0);
3033 ics_clock_paused = TRUE;
3035 } else if (ticking == 1) {
3036 ics_clock_paused = FALSE;
3038 if (gameMode == IcsIdle ||
3039 relation == RELATION_OBSERVING_STATIC ||
3040 relation == RELATION_EXAMINING ||
3042 DisplayBothClocks();
3046 /* Display opponents and material strengths */
3047 if (gameInfo.variant != VariantBughouse &&
3048 gameInfo.variant != VariantCrazyhouse) {
3049 if (tinyLayout || smallLayout) {
3050 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3051 gameInfo.white, white_stren, gameInfo.black, black_stren,
3052 basetime, increment);
3054 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3055 gameInfo.white, white_stren, gameInfo.black, black_stren,
3056 basetime, increment);
3062 /* Display the board */
3065 if (appData.premove)
3067 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3068 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3069 ClearPremoveHighlights();
3071 DrawPosition(FALSE, boards[currentMove]);
3072 DisplayMove(moveNum - 1);
3073 if (appData.ringBellAfterMoves && !ics_user_moved)
3077 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3084 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3085 ics_getting_history = H_REQUESTED;
3086 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3092 AnalysisPeriodicEvent(force)
3095 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3096 && !force) || !appData.periodicUpdates)
3099 /* Send . command to Crafty to collect stats */
3100 SendToProgram(".\n", &first);
3102 /* Don't send another until we get a response (this makes
3103 us stop sending to old Crafty's which don't understand
3104 the "." command (sending illegal cmds resets node count & time,
3105 which looks bad)) */
3106 programStats.ok_to_send = 0;
3110 SendMoveToProgram(moveNum, cps)
3112 ChessProgramState *cps;
3115 if (cps->useUsermove) {
3116 SendToProgram("usermove ", cps);
3120 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3121 int len = space - parseList[moveNum];
3122 memcpy(buf, parseList[moveNum], len);
3124 buf[len] = NULLCHAR;
3126 sprintf(buf, "%s\n", parseList[moveNum]);
3128 SendToProgram(buf, cps);
3130 SendToProgram(moveList[moveNum], cps);
3135 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3137 int fromX, fromY, toX, toY;
3139 char user_move[MSG_SIZ];
3143 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3144 (int)moveType, fromX, fromY, toX, toY);
3145 DisplayError(user_move + strlen("say "), 0);
3147 case WhiteKingSideCastle:
3148 case BlackKingSideCastle:
3149 case WhiteQueenSideCastleWild:
3150 case BlackQueenSideCastleWild:
3151 sprintf(user_move, "o-o\n");
3153 case WhiteQueenSideCastle:
3154 case BlackQueenSideCastle:
3155 case WhiteKingSideCastleWild:
3156 case BlackKingSideCastleWild:
3157 sprintf(user_move, "o-o-o\n");
3159 case WhitePromotionQueen:
3160 case BlackPromotionQueen:
3161 case WhitePromotionRook:
3162 case BlackPromotionRook:
3163 case WhitePromotionBishop:
3164 case BlackPromotionBishop:
3165 case WhitePromotionKnight:
3166 case BlackPromotionKnight:
3167 case WhitePromotionKing:
3168 case BlackPromotionKing:
3169 sprintf(user_move, "%c%c%c%c=%c\n",
3170 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3171 PieceToChar(PromoPiece(moveType)));
3175 sprintf(user_move, "%c@%c%c\n",
3176 ToUpper(PieceToChar((ChessSquare) fromX)),
3177 'a' + toX, '1' + toY);
3180 case WhiteCapturesEnPassant:
3181 case BlackCapturesEnPassant:
3182 case IllegalMove: /* could be a variant we don't quite understand */
3183 sprintf(user_move, "%c%c%c%c\n",
3184 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3187 SendToICS(user_move);
3191 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3196 if (rf == DROP_RANK) {
3197 sprintf(move, "%c@%c%c\n",
3198 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3200 if (promoChar == 'x' || promoChar == NULLCHAR) {
3201 sprintf(move, "%c%c%c%c\n",
3202 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3204 sprintf(move, "%c%c%c%c%c\n",
3205 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3211 ProcessICSInitScript(f)
3216 while (fgets(buf, MSG_SIZ, f)) {
3217 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3224 /* Parser for moves from gnuchess, ICS, or user typein box */
3226 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3229 ChessMove *moveType;
3230 int *fromX, *fromY, *toX, *toY;
3233 *moveType = yylexstr(moveNum, move);
3234 switch (*moveType) {
3235 case WhitePromotionQueen:
3236 case BlackPromotionQueen:
3237 case WhitePromotionRook:
3238 case BlackPromotionRook:
3239 case WhitePromotionBishop:
3240 case BlackPromotionBishop:
3241 case WhitePromotionKnight:
3242 case BlackPromotionKnight:
3243 case WhitePromotionKing:
3244 case BlackPromotionKing:
3246 case WhiteCapturesEnPassant:
3247 case BlackCapturesEnPassant:
3248 case WhiteKingSideCastle:
3249 case WhiteQueenSideCastle:
3250 case BlackKingSideCastle:
3251 case BlackQueenSideCastle:
3252 case WhiteKingSideCastleWild:
3253 case WhiteQueenSideCastleWild:
3254 case BlackKingSideCastleWild:
3255 case BlackQueenSideCastleWild:
3256 case IllegalMove: /* bug or odd chess variant */
3257 *fromX = currentMoveString[0] - 'a';
3258 *fromY = currentMoveString[1] - '1';
3259 *toX = currentMoveString[2] - 'a';
3260 *toY = currentMoveString[3] - '1';
3261 *promoChar = currentMoveString[4];
3262 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3263 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3264 *fromX = *fromY = *toX = *toY = 0;
3267 if (appData.testLegality) {
3268 return (*moveType != IllegalMove);
3270 return !(fromX == fromY && toX == toY);
3275 *fromX = *moveType == WhiteDrop ?
3276 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3277 (int) CharToPiece(ToLower(currentMoveString[0]));
3279 *toX = currentMoveString[2] - 'a';
3280 *toY = currentMoveString[3] - '1';
3281 *promoChar = NULLCHAR;
3285 case ImpossibleMove:
3286 case (ChessMove) 0: /* end of file */
3296 *fromX = *fromY = *toX = *toY = 0;
3297 *promoChar = NULLCHAR;
3304 InitPosition(redraw)
3307 currentMove = forwardMostMove = backwardMostMove = 0;
3308 switch (gameInfo.variant) {
3310 CopyBoard(boards[0], initialPosition);
3312 case VariantTwoKings:
3313 CopyBoard(boards[0], twoKingsPosition);
3314 startedFromSetupPosition = TRUE;
3316 case VariantWildCastle:
3317 CopyBoard(boards[0], initialPosition);
3318 /* !!?shuffle with kings guaranteed to be on d or e file */
3320 case VariantNoCastle:
3321 CopyBoard(boards[0], initialPosition);
3322 /* !!?unconstrained back-rank shuffle */
3324 case VariantFischeRandom:
3325 CopyBoard(boards[0], initialPosition);
3326 /* !!shuffle according to FR rules */
3330 DrawPosition(FALSE, boards[currentMove]);
3334 SendBoard(cps, moveNum)
3335 ChessProgramState *cps;
3338 char message[MSG_SIZ];
3340 if (cps->useSetboard) {
3341 char* fen = PositionToFEN(moveNum);
3342 sprintf(message, "setboard %s\n", fen);
3343 SendToProgram(message, cps);
3349 /* Kludge to set black to move, avoiding the troublesome and now
3350 * deprecated "black" command.
3352 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3354 SendToProgram("edit\n", cps);
3355 SendToProgram("#\n", cps);
3356 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3357 bp = &boards[moveNum][i][0];
3358 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3359 if ((int) *bp < (int) BlackPawn) {
3360 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3362 SendToProgram(message, cps);
3367 SendToProgram("c\n", cps);
3368 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3369 bp = &boards[moveNum][i][0];
3370 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3371 if (((int) *bp != (int) EmptySquare)
3372 && ((int) *bp >= (int) BlackPawn)) {
3373 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3375 SendToProgram(message, cps);
3380 SendToProgram(".\n", cps);
3385 IsPromotion(fromX, fromY, toX, toY)
3386 int fromX, fromY, toX, toY;
3388 return gameMode != EditPosition &&
3389 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3390 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3391 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3396 PieceForSquare (x, y)
3400 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3403 return boards[currentMove][y][x];
3407 OKToStartUserMove(x, y)
3410 ChessSquare from_piece;
3413 if (matchMode) return FALSE;
3414 if (gameMode == EditPosition) return TRUE;
3416 if (x >= 0 && y >= 0)
3417 from_piece = boards[currentMove][y][x];
3419 from_piece = EmptySquare;
3421 if (from_piece == EmptySquare) return FALSE;
3423 white_piece = (int)from_piece >= (int)WhitePawn &&
3424 (int)from_piece <= (int)WhiteKing;
3427 case PlayFromGameFile:
3429 case TwoMachinesPlay:
3437 case MachinePlaysWhite:
3438 case IcsPlayingBlack:
3439 if (appData.zippyPlay) return FALSE;
3441 DisplayMoveError(_("You are playing Black"));
3446 case MachinePlaysBlack:
3447 case IcsPlayingWhite:
3448 if (appData.zippyPlay) return FALSE;
3450 DisplayMoveError(_("You are playing White"));
3456 if (!white_piece && WhiteOnMove(currentMove)) {
3457 DisplayMoveError(_("It is White's turn"));
3460 if (white_piece && !WhiteOnMove(currentMove)) {
3461 DisplayMoveError(_("It is Black's turn"));
3464 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3465 /* Editing correspondence game history */
3466 /* Could disallow this or prompt for confirmation */
3469 if (currentMove < forwardMostMove) {
3470 /* Discarding moves */
3471 /* Could prompt for confirmation here,
3472 but I don't think that's such a good idea */
3473 forwardMostMove = currentMove;
3477 case BeginningOfGame:
3478 if (appData.icsActive) return FALSE;
3479 if (!appData.noChessProgram) {
3481 DisplayMoveError(_("You are playing White"));
3488 if (!white_piece && WhiteOnMove(currentMove)) {
3489 DisplayMoveError(_("It is White's turn"));
3492 if (white_piece && !WhiteOnMove(currentMove)) {
3493 DisplayMoveError(_("It is Black's turn"));
3502 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3503 && gameMode != AnalyzeFile && gameMode != Training) {
3504 DisplayMoveError(_("Displayed position is not current"));
3510 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3511 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3512 int lastLoadGameUseList = FALSE;
3513 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3514 ChessMove lastLoadGameStart = (ChessMove) 0;
3518 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3519 int fromX, fromY, toX, toY;
3524 if (fromX < 0 || fromY < 0) return;
3525 if ((fromX == toX) && (fromY == toY)) {
3529 /* Check if the user is playing in turn. This is complicated because we
3530 let the user "pick up" a piece before it is his turn. So the piece he
3531 tried to pick up may have been captured by the time he puts it down!
3532 Therefore we use the color the user is supposed to be playing in this
3533 test, not the color of the piece that is currently on the starting
3534 square---except in EditGame mode, where the user is playing both
3535 sides; fortunately there the capture race can't happen. (It can
3536 now happen in IcsExamining mode, but that's just too bad. The user
3537 will get a somewhat confusing message in that case.)
3541 case PlayFromGameFile:
3543 case TwoMachinesPlay:
3547 /* We switched into a game mode where moves are not accepted,
3548 perhaps while the mouse button was down. */
3551 case MachinePlaysWhite:
3552 /* User is moving for Black */
3553 if (WhiteOnMove(currentMove)) {
3554 DisplayMoveError(_("It is White's turn"));
3559 case MachinePlaysBlack:
3560 /* User is moving for White */
3561 if (!WhiteOnMove(currentMove)) {
3562 DisplayMoveError(_("It is Black's turn"));
3569 case BeginningOfGame:
3572 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3573 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3574 /* User is moving for Black */
3575 if (WhiteOnMove(currentMove)) {
3576 DisplayMoveError(_("It is White's turn"));
3580 /* User is moving for White */
3581 if (!WhiteOnMove(currentMove)) {
3582 DisplayMoveError(_("It is Black's turn"));
3588 case IcsPlayingBlack:
3589 /* User is moving for Black */
3590 if (WhiteOnMove(currentMove)) {
3591 if (!appData.premove) {
3592 DisplayMoveError(_("It is White's turn"));
3593 } else if (toX >= 0 && toY >= 0) {
3596 premoveFromX = fromX;
3597 premoveFromY = fromY;
3598 premovePromoChar = promoChar;
3600 if (appData.debugMode)
3601 fprintf(debugFP, "Got premove: fromX %d,"
3602 "fromY %d, toX %d, toY %d\n",
3603 fromX, fromY, toX, toY);
3609 case IcsPlayingWhite:
3610 /* User is moving for White */
3611 if (!WhiteOnMove(currentMove)) {
3612 if (!appData.premove) {
3613 DisplayMoveError(_("It is Black's turn"));
3614 } else if (toX >= 0 && toY >= 0) {
3617 premoveFromX = fromX;
3618 premoveFromY = fromY;
3619 premovePromoChar = promoChar;
3621 if (appData.debugMode)
3622 fprintf(debugFP, "Got premove: fromX %d,"
3623 "fromY %d, toX %d, toY %d\n",
3624 fromX, fromY, toX, toY);
3634 if (toX == -2 || toY == -2) {
3635 boards[0][fromY][fromX] = EmptySquare;
3636 DrawPosition(FALSE, boards[currentMove]);
3637 } else if (toX >= 0 && toY >= 0) {
3638 boards[0][toY][toX] = boards[0][fromY][fromX];
3639 boards[0][fromY][fromX] = EmptySquare;
3640 DrawPosition(FALSE, boards[currentMove]);
3645 if (toX < 0 || toY < 0) return;
3646 userOfferedDraw = FALSE;
3648 if (appData.testLegality) {
3649 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3650 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3651 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3652 DisplayMoveError(_("Illegal move"));
3656 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3659 if (gameMode == Training) {
3660 /* compare the move played on the board to the next move in the
3661 * game. If they match, display the move and the opponent's response.
3662 * If they don't match, display an error message.
3666 CopyBoard(testBoard, boards[currentMove]);
3667 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3669 if (CompareBoards(testBoard, boards[currentMove+1])) {
3670 ForwardInner(currentMove+1);
3672 /* Autoplay the opponent's response.
3673 * if appData.animate was TRUE when Training mode was entered,
3674 * the response will be animated.
3676 saveAnimate = appData.animate;
3677 appData.animate = animateTraining;
3678 ForwardInner(currentMove+1);
3679 appData.animate = saveAnimate;
3681 /* check for the end of the game */
3682 if (currentMove >= forwardMostMove) {
3683 gameMode = PlayFromGameFile;
3685 SetTrainingModeOff();
3686 DisplayInformation(_("End of game"));
3689 DisplayError(_("Incorrect move"), 0);
3694 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3697 /* Common tail of UserMoveEvent and DropMenuEvent */
3699 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3701 int fromX, fromY, toX, toY;
3702 /*char*/int promoChar;
3704 /* Ok, now we know that the move is good, so we can kill
3705 the previous line in Analysis Mode */
3706 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3707 forwardMostMove = currentMove;
3710 /* If we need the chess program but it's dead, restart it */
3711 ResurrectChessProgram();
3713 /* A user move restarts a paused game*/
3717 thinkOutput[0] = NULLCHAR;
3719 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3721 if (gameMode == BeginningOfGame) {
3722 if (appData.noChessProgram) {
3723 gameMode = EditGame;
3727 gameMode = MachinePlaysBlack;
3729 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3731 if (first.sendName) {
3732 sprintf(buf, "name %s\n", gameInfo.white);
3733 SendToProgram(buf, &first);
3739 /* Relay move to ICS or chess engine */
3740 if (appData.icsActive) {
3741 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3742 gameMode == IcsExamining) {
3743 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3747 if (first.sendTime && (gameMode == BeginningOfGame ||
3748 gameMode == MachinePlaysWhite ||
3749 gameMode == MachinePlaysBlack)) {
3750 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3752 SendMoveToProgram(forwardMostMove-1, &first);
3753 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3754 first.maybeThinking = TRUE;
3756 if (currentMove == cmailOldMove + 1) {
3757 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3761 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3765 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3771 if (WhiteOnMove(currentMove)) {
3772 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3774 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3778 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3783 case MachinePlaysBlack:
3784 case MachinePlaysWhite:
3785 /* disable certain menu options while machine is thinking */
3786 SetMachineThinkingEnables();
3795 HandleMachineMove(message, cps)
3797 ChessProgramState *cps;
3799 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3800 char realname[MSG_SIZ];
3801 int fromX, fromY, toX, toY;
3808 * Kludge to ignore BEL characters
3810 while (*message == '\007') message++;
3813 * Look for book output
3815 if (cps == &first && bookRequested) {
3816 if (message[0] == '\t' || message[0] == ' ') {
3817 /* Part of the book output is here; append it */
3818 strcat(bookOutput, message);
3819 strcat(bookOutput, " \n");
3821 } else if (bookOutput[0] != NULLCHAR) {
3822 /* All of book output has arrived; display it */
3823 char *p = bookOutput;
3824 while (*p != NULLCHAR) {
3825 if (*p == '\t') *p = ' ';
3828 DisplayInformation(bookOutput);
3829 bookRequested = FALSE;
3830 /* Fall through to parse the current output */
3835 * Look for machine move.
3837 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3838 strcmp(buf2, "...") == 0) ||
3839 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3840 strcmp(buf1, "move") == 0)) {
3842 /* This method is only useful on engines that support ping */
3843 if (cps->lastPing != cps->lastPong) {
3844 if (gameMode == BeginningOfGame) {
3845 /* Extra move from before last new; ignore */
3846 if (appData.debugMode) {
3847 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3850 if (appData.debugMode) {
3851 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3852 cps->which, gameMode);
3854 SendToProgram("undo\n", cps);
3860 case BeginningOfGame:
3861 /* Extra move from before last reset; ignore */
3862 if (appData.debugMode) {
3863 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3870 /* Extra move after we tried to stop. The mode test is
3871 not a reliable way of detecting this problem, but it's
3872 the best we can do on engines that don't support ping.
3874 if (appData.debugMode) {
3875 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3876 cps->which, gameMode);
3878 SendToProgram("undo\n", cps);
3881 case MachinePlaysWhite:
3882 case IcsPlayingWhite:
3883 machineWhite = TRUE;
3886 case MachinePlaysBlack:
3887 case IcsPlayingBlack:
3888 machineWhite = FALSE;
3891 case TwoMachinesPlay:
3892 machineWhite = (cps->twoMachinesColor[0] == 'w');
3895 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3896 if (appData.debugMode) {
3898 "Ignoring move out of turn by %s, gameMode %d"
3899 ", forwardMost %d\n",
3900 cps->which, gameMode, forwardMostMove);
3905 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3906 &fromX, &fromY, &toX, &toY, &promoChar)) {
3907 /* Machine move could not be parsed; ignore it. */
3908 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3909 machineMove, cps->which);
3910 DisplayError(buf1, 0);
3911 if (gameMode == TwoMachinesPlay) {
3912 GameEnds(machineWhite ? BlackWins : WhiteWins,
3913 "Forfeit due to illegal move", GE_XBOARD);
3918 hintRequested = FALSE;
3919 lastHint[0] = NULLCHAR;
3920 bookRequested = FALSE;
3921 /* Program may be pondering now */
3922 cps->maybeThinking = TRUE;
3923 if (cps->sendTime == 2) cps->sendTime = 1;
3924 if (cps->offeredDraw) cps->offeredDraw--;
3927 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3929 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3933 /* currentMoveString is set as a side-effect of ParseOneMove */
3934 strcpy(machineMove, currentMoveString);
3935 strcat(machineMove, "\n");
3936 strcpy(moveList[forwardMostMove], machineMove);
3938 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3940 if (gameMode == TwoMachinesPlay) {
3941 if (cps->other->sendTime) {
3942 SendTimeRemaining(cps->other,
3943 cps->other->twoMachinesColor[0] == 'w');
3945 SendMoveToProgram(forwardMostMove-1, cps->other);
3948 if (cps->other->useColors) {
3949 SendToProgram(cps->other->twoMachinesColor, cps->other);
3951 SendToProgram("go\n", cps->other);
3953 cps->other->maybeThinking = TRUE;
3956 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3957 if (!pausing && appData.ringBellAfterMoves) {
3961 * Reenable menu items that were disabled while
3962 * machine was thinking
3964 if (gameMode != TwoMachinesPlay)
3965 SetUserThinkingEnables();
3969 /* Set special modes for chess engines. Later something general
3970 * could be added here; for now there is just one kludge feature,
3971 * needed because Crafty 15.10 and earlier don't ignore SIGINT
3972 * when "xboard" is given as an interactive command.
3974 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3975 cps->useSigint = FALSE;
3976 cps->useSigterm = FALSE;
3980 * Look for communication commands
3982 if (!strncmp(message, "telluser ", 9)) {
3983 DisplayNote(message + 9);
3986 if (!strncmp(message, "tellusererror ", 14)) {
3987 DisplayError(message + 14, 0);
3990 if (!strncmp(message, "tellopponent ", 13)) {
3991 if (appData.icsActive) {
3993 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3997 DisplayNote(message + 13);
4001 if (!strncmp(message, "tellothers ", 11)) {
4002 if (appData.icsActive) {
4004 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4010 if (!strncmp(message, "tellall ", 8)) {
4011 if (appData.icsActive) {
4013 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4017 DisplayNote(message + 8);
4021 if (strncmp(message, "warning", 7) == 0) {
4022 /* Undocumented feature, use tellusererror in new code */
4023 DisplayError(message, 0);
4026 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4027 strcpy(realname, cps->tidy);
4028 strcat(realname, " query");
4029 AskQuestion(realname, buf2, buf1, cps->pr);
4032 /* Commands from the engine directly to ICS. We don't allow these to be
4033 * sent until we are logged on. Crafty kibitzes have been known to
4034 * interfere with the login process.
4037 if (!strncmp(message, "tellics ", 8)) {
4038 SendToICS(message + 8);
4042 if (!strncmp(message, "tellicsnoalias ", 15)) {
4043 SendToICS(ics_prefix);
4044 SendToICS(message + 15);
4048 /* The following are for backward compatibility only */
4049 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4050 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4051 SendToICS(ics_prefix);
4057 if (strncmp(message, "feature ", 8) == 0) {
4058 ParseFeatures(message+8, cps);
4060 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4064 * If the move is illegal, cancel it and redraw the board.
4065 * Also deal with other error cases. Matching is rather loose
4066 * here to accommodate engines written before the spec.
4068 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4069 strncmp(message, "Error", 5) == 0) {
4070 if (StrStr(message, "name") ||
4071 StrStr(message, "rating") || StrStr(message, "?") ||
4072 StrStr(message, "result") || StrStr(message, "board") ||
4073 StrStr(message, "bk") || StrStr(message, "computer") ||
4074 StrStr(message, "variant") || StrStr(message, "hint") ||
4075 StrStr(message, "random") || StrStr(message, "depth") ||
4076 StrStr(message, "accepted")) {
4079 if (StrStr(message, "protover")) {
4080 /* Program is responding to input, so it's apparently done
4081 initializing, and this error message indicates it is
4082 protocol version 1. So we don't need to wait any longer
4083 for it to initialize and send feature commands. */
4084 FeatureDone(cps, 1);
4085 cps->protocolVersion = 1;
4088 cps->maybeThinking = FALSE;
4090 if (StrStr(message, "draw")) {
4091 /* Program doesn't have "draw" command */
4092 cps->sendDrawOffers = 0;
4095 if (cps->sendTime != 1 &&
4096 (StrStr(message, "time") || StrStr(message, "otim"))) {
4097 /* Program apparently doesn't have "time" or "otim" command */
4101 if (StrStr(message, "analyze")) {
4102 cps->analysisSupport = FALSE;
4103 cps->analyzing = FALSE;
4105 sprintf(buf2, "%s does not support analysis", cps->tidy);
4106 DisplayError(buf2, 0);
4109 if (StrStr(message, "(no matching move)st")) {
4110 /* Special kludge for GNU Chess 4 only */
4111 cps->stKludge = TRUE;
4112 SendTimeControl(cps, movesPerSession, timeControl,
4113 timeIncrement, appData.searchDepth,
4117 if (StrStr(message, "(no matching move)sd")) {
4118 /* Special kludge for GNU Chess 4 only */
4119 cps->sdKludge = TRUE;
4120 SendTimeControl(cps, movesPerSession, timeControl,
4121 timeIncrement, appData.searchDepth,
4125 if (!StrStr(message, "llegal")) return;
4126 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4127 gameMode == IcsIdle) return;
4128 if (forwardMostMove <= backwardMostMove) return;
4130 /* Following removed: it caused a bug where a real illegal move
4131 message in analyze mored would be ignored. */
4132 if (cps == &first && programStats.ok_to_send == 0) {
4133 /* Bogus message from Crafty responding to "." This filtering
4134 can miss some of the bad messages, but fortunately the bug
4135 is fixed in current Crafty versions, so it doesn't matter. */
4139 if (pausing) PauseEvent();
4140 if (gameMode == PlayFromGameFile) {
4141 /* Stop reading this game file */
4142 gameMode = EditGame;
4145 currentMove = --forwardMostMove;
4146 DisplayMove(currentMove-1); /* before DisplayMoveError */
4148 DisplayBothClocks();
4149 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
4150 parseList[currentMove], cps->which);
4151 DisplayMoveError(buf1);
4152 DrawPosition(FALSE, boards[currentMove]);
4155 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4156 /* Program has a broken "time" command that
4157 outputs a string not ending in newline.
4163 * If chess program startup fails, exit with an error message.
4164 * Attempts to recover here are futile.
4166 if ((StrStr(message, "unknown host") != NULL)
4167 || (StrStr(message, "No remote directory") != NULL)
4168 || (StrStr(message, "not found") != NULL)
4169 || (StrStr(message, "No such file") != NULL)
4170 || (StrStr(message, "can't alloc") != NULL)
4171 || (StrStr(message, "Permission denied") != NULL)) {
4173 cps->maybeThinking = FALSE;
4174 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
4175 cps->which, cps->program, cps->host, message);
4176 RemoveInputSource(cps->isr);
4177 DisplayFatalError(buf1, 0, 1);
4182 * Look for hint output
4184 if (sscanf(message, "Hint: %s", buf1) == 1) {
4185 if (cps == &first && hintRequested) {
4186 hintRequested = FALSE;
4187 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4188 &fromX, &fromY, &toX, &toY, &promoChar)) {
4189 (void) CoordsToAlgebraic(boards[forwardMostMove],
4190 PosFlags(forwardMostMove), EP_UNKNOWN,
4191 fromY, fromX, toY, toX, promoChar, buf1);
4192 sprintf(buf2, "Hint: %s", buf1);
4193 DisplayInformation(buf2);
4195 /* Hint move could not be parsed!? */
4197 _("Illegal hint move \"%s\"\nfrom %s chess program"),
4199 DisplayError(buf2, 0);
4202 strcpy(lastHint, buf1);
4208 * Ignore other messages if game is not in progress
4210 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4211 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4214 * look for win, lose, draw, or draw offer
4216 if (strncmp(message, "1-0", 3) == 0) {
4217 char *p, *q, *r = "";
4218 p = strchr(message, '{');
4226 GameEnds(WhiteWins, r, GE_ENGINE);
4228 } else if (strncmp(message, "0-1", 3) == 0) {
4229 char *p, *q, *r = "";
4230 p = strchr(message, '{');
4238 /* Kludge for Arasan 4.1 bug */
4239 if (strcmp(r, "Black resigns") == 0) {
4240 GameEnds(WhiteWins, r, GE_ENGINE);
4243 GameEnds(BlackWins, r, GE_ENGINE);
4245 } else if (strncmp(message, "1/2", 3) == 0) {
4246 char *p, *q, *r = "";
4247 p = strchr(message, '{');
4255 GameEnds(GameIsDrawn, r, GE_ENGINE);
4258 } else if (strncmp(message, "White resign", 12) == 0) {
4259 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4261 } else if (strncmp(message, "Black resign", 12) == 0) {
4262 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4264 } else if (strncmp(message, "White", 5) == 0 &&
4265 message[5] != '(' &&
4266 StrStr(message, "Black") == NULL) {
4267 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4269 } else if (strncmp(message, "Black", 5) == 0 &&
4270 message[5] != '(') {
4271 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4273 } else if (strcmp(message, "resign") == 0 ||
4274 strcmp(message, "computer resigns") == 0) {
4276 case MachinePlaysBlack:
4277 case IcsPlayingBlack:
4278 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4280 case MachinePlaysWhite:
4281 case IcsPlayingWhite:
4282 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4284 case TwoMachinesPlay:
4285 if (cps->twoMachinesColor[0] == 'w')
4286 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4288 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4295 } else if (strncmp(message, "opponent mates", 14) == 0) {
4297 case MachinePlaysBlack:
4298 case IcsPlayingBlack:
4299 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4301 case MachinePlaysWhite:
4302 case IcsPlayingWhite:
4303 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4305 case TwoMachinesPlay:
4306 if (cps->twoMachinesColor[0] == 'w')
4307 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4309 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4316 } else if (strncmp(message, "computer mates", 14) == 0) {
4318 case MachinePlaysBlack:
4319 case IcsPlayingBlack:
4320 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4322 case MachinePlaysWhite:
4323 case IcsPlayingWhite:
4324 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4326 case TwoMachinesPlay:
4327 if (cps->twoMachinesColor[0] == 'w')
4328 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4330 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4337 } else if (strncmp(message, "checkmate", 9) == 0) {
4338 if (WhiteOnMove(forwardMostMove)) {
4339 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4341 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4344 } else if (strstr(message, "Draw") != NULL ||
4345 strstr(message, "game is a draw") != NULL) {
4346 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4348 } else if (strstr(message, "offer") != NULL &&
4349 strstr(message, "draw") != NULL) {
4351 if (appData.zippyPlay && first.initDone) {
4352 /* Relay offer to ICS */
4353 SendToICS(ics_prefix);
4354 SendToICS("draw\n");
4357 cps->offeredDraw = 2; /* valid until this engine moves twice */
4358 if (gameMode == TwoMachinesPlay) {
4359 if (cps->other->offeredDraw) {
4360 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4362 if (cps->other->sendDrawOffers) {
4363 SendToProgram("draw\n", cps->other);
4366 } else if (gameMode == MachinePlaysWhite ||
4367 gameMode == MachinePlaysBlack) {
4368 if (userOfferedDraw) {
4369 DisplayInformation(_("Machine accepts your draw offer"));
4370 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4372 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
4379 * Look for thinking output
4381 if (appData.showThinking) {
4382 int plylev, mvleft, mvtot, curscore, time;
4383 char mvname[MOVE_LEN];
4387 int prefixHint = FALSE;
4388 mvname[0] = NULLCHAR;
4391 case MachinePlaysBlack:
4392 case IcsPlayingBlack:
4393 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4395 case MachinePlaysWhite:
4396 case IcsPlayingWhite:
4397 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4402 /* icsEngineAnalyze */
4404 if (!appData.icsEngineAnalyze) ignore = TRUE;
4406 case TwoMachinesPlay:
4407 if ((cps->twoMachinesColor[0] == 'w') !=
4408 WhiteOnMove(forwardMostMove)) {
4419 if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
4420 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4422 if (plyext != ' ' && plyext != '\t') {
4425 programStats.depth = plylev;
4426 programStats.nodes = nodes;
4427 programStats.time = time;
4428 programStats.score = curscore;
4429 programStats.got_only_move = 0;
4431 /* Buffer overflow protection */
4432 if (buf1[0] != NULLCHAR) {
4433 if (strlen(buf1) >= sizeof(programStats.movelist)
4434 && appData.debugMode) {
4436 "PV is too long; using the first %d bytes.\n",
4437 sizeof(programStats.movelist) - 1);
4439 strncpy(programStats.movelist, buf1,
4440 sizeof(programStats.movelist));
4441 programStats.movelist[sizeof(programStats.movelist) - 1]
4444 sprintf(programStats.movelist, " no PV\n");
4447 if (programStats.seen_stat) {
4448 programStats.ok_to_send = 1;
4451 if (strchr(programStats.movelist, '(') != NULL) {
4452 programStats.line_is_book = 1;
4453 programStats.nr_moves = 0;
4454 programStats.moves_left = 0;
4456 programStats.line_is_book = 0;
4459 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4461 (gameMode == TwoMachinesPlay ?
4462 ToUpper(cps->twoMachinesColor[0]) : ' '),
4463 ((double) curscore) / 100.0,
4464 prefixHint ? lastHint : "",
4465 prefixHint ? " " : "", buf1);
4467 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
4468 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4469 DisplayMove(currentMove - 1);
4474 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4475 /* crafty (9.25+) says "(only move) <move>"
4476 * if there is only 1 legal move
4478 sscanf(p, "(only move) %s", buf1);
4479 sprintf(thinkOutput, "%s (only move)", buf1);
4480 sprintf(programStats.movelist, "%s (only move)", buf1);
4481 programStats.depth = 1;
4482 programStats.nr_moves = 1;
4483 programStats.moves_left = 1;
4484 programStats.nodes = 1;
4485 programStats.time = 1;
4486 programStats.got_only_move = 1;
4488 /* Not really, but we also use this member to
4489 mean "line isn't going to change" (Crafty
4490 isn't searching, so stats won't change) */
4491 programStats.line_is_book = 1;
4493 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4494 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4495 DisplayMove(currentMove - 1);
4499 } else if (sscanf(message,"stat01: %d" u64Display "%d %d %d %s",
4500 &time, &nodes, &plylev, &mvleft,
4501 &mvtot, mvname) >= 5) {
4502 /* The stat01: line is from Crafty (9.29+) in response
4503 to the "." command */
4504 programStats.seen_stat = 1;
4505 cps->maybeThinking = TRUE;
4507 if (programStats.got_only_move || !appData.periodicUpdates)
4510 programStats.depth = plylev;
4511 programStats.time = time;
4512 programStats.nodes = nodes;
4513 programStats.moves_left = mvleft;
4514 programStats.nr_moves = mvtot;
4515 strcpy(programStats.move_name, mvname);
4516 programStats.ok_to_send = 1;
4520 } else if (strncmp(message,"++",2) == 0) {
4521 /* Crafty 9.29+ outputs this */
4522 programStats.got_fail = 2;
4525 } else if (strncmp(message,"--",2) == 0) {
4526 /* Crafty 9.29+ outputs this */
4527 programStats.got_fail = 1;
4530 } else if (thinkOutput[0] != NULLCHAR &&
4531 strncmp(message, " ", 4) == 0) {
4533 while (*p && *p == ' ') p++;
4534 strcat(thinkOutput, " ");
4535 strcat(thinkOutput, p);
4536 strcat(programStats.movelist, " ");
4537 strcat(programStats.movelist, p);
4538 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4539 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4540 DisplayMove(currentMove - 1);
4550 /* Parse a game score from the character string "game", and
4551 record it as the history of the current game. The game
4552 score is NOT assumed to start from the standard position.
4553 The display is not updated in any way.
4556 ParseGameHistory(game)
4560 int fromX, fromY, toX, toY, boardIndex;
4565 if (appData.debugMode)
4566 fprintf(debugFP, "Parsing game history: %s\n", game);
4568 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4569 gameInfo.site = StrSave(appData.icsHost);
4570 gameInfo.date = PGNDate();
4571 gameInfo.round = StrSave("-");
4573 /* Parse out names of players */
4574 while (*game == ' ') game++;
4576 while (*game != ' ') *p++ = *game++;
4578 gameInfo.white = StrSave(buf);
4579 while (*game == ' ') game++;
4581 while (*game != ' ' && *game != '\n') *p++ = *game++;
4583 gameInfo.black = StrSave(buf);
4586 boardIndex = blackPlaysFirst ? 1 : 0;
4589 yyboardindex = boardIndex;
4590 moveType = (ChessMove) yylex();
4592 case WhitePromotionQueen:
4593 case BlackPromotionQueen:
4594 case WhitePromotionRook:
4595 case BlackPromotionRook:
4596 case WhitePromotionBishop:
4597 case BlackPromotionBishop:
4598 case WhitePromotionKnight:
4599 case BlackPromotionKnight:
4600 case WhitePromotionKing:
4601 case BlackPromotionKing:
4603 case WhiteCapturesEnPassant:
4604 case BlackCapturesEnPassant:
4605 case WhiteKingSideCastle:
4606 case WhiteQueenSideCastle:
4607 case BlackKingSideCastle:
4608 case BlackQueenSideCastle:
4609 case WhiteKingSideCastleWild:
4610 case WhiteQueenSideCastleWild:
4611 case BlackKingSideCastleWild:
4612 case BlackQueenSideCastleWild:
4613 case IllegalMove: /* maybe suicide chess, etc. */
4614 fromX = currentMoveString[0] - 'a';
4615 fromY = currentMoveString[1] - '1';
4616 toX = currentMoveString[2] - 'a';
4617 toY = currentMoveString[3] - '1';
4618 promoChar = currentMoveString[4];
4622 fromX = moveType == WhiteDrop ?
4623 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4624 (int) CharToPiece(ToLower(currentMoveString[0]));
4626 toX = currentMoveString[2] - 'a';
4627 toY = currentMoveString[3] - '1';
4628 promoChar = NULLCHAR;
4632 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4633 DisplayError(buf, 0);
4635 case ImpossibleMove:
4637 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4638 DisplayError(buf, 0);
4640 case (ChessMove) 0: /* end of file */
4641 if (boardIndex < backwardMostMove) {
4642 /* Oops, gap. How did that happen? */
4643 DisplayError(_("Gap in move list"), 0);
4646 backwardMostMove = blackPlaysFirst ? 1 : 0;
4647 if (boardIndex > forwardMostMove) {
4648 forwardMostMove = boardIndex;
4652 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4653 strcat(parseList[boardIndex-1], " ");
4654 strcat(parseList[boardIndex-1], yy_text);
4666 case GameUnfinished:
4667 if (gameMode == IcsExamining) {
4668 if (boardIndex < backwardMostMove) {
4669 /* Oops, gap. How did that happen? */
4672 backwardMostMove = blackPlaysFirst ? 1 : 0;
4675 gameInfo.result = moveType;
4676 p = strchr(yy_text, '{');
4677 if (p == NULL) p = strchr(yy_text, '(');
4680 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4682 q = strchr(p, *p == '{' ? '}' : ')');
4683 if (q != NULL) *q = NULLCHAR;
4686 gameInfo.resultDetails = StrSave(p);
4689 if (boardIndex >= forwardMostMove &&
4690 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4691 backwardMostMove = blackPlaysFirst ? 1 : 0;
4694 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4695 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4696 parseList[boardIndex]);
4697 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4698 /* currentMoveString is set as a side-effect of yylex */
4699 strcpy(moveList[boardIndex], currentMoveString);
4700 strcat(moveList[boardIndex], "\n");
4702 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4703 switch (MateTest(boards[boardIndex],
4704 PosFlags(boardIndex), EP_UNKNOWN)) {
4710 strcat(parseList[boardIndex - 1], "+");
4713 strcat(parseList[boardIndex - 1], "#");
4720 /* Apply a move to the given board */
4722 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4723 int fromX, fromY, toX, toY;
4727 ChessSquare captured = board[toY][toX];
4728 if (fromY == DROP_RANK) {
4730 board[toY][toX] = (ChessSquare) fromX;
4731 } else if (fromX == toX && fromY == toY) {
4733 } else if (fromY == 0 && fromX == 4
4734 && board[fromY][fromX] == WhiteKing
4735 && toY == 0 && toX == 6) {
4736 board[fromY][fromX] = EmptySquare;
4737 board[toY][toX] = WhiteKing;
4738 board[fromY][7] = EmptySquare;
4739 board[toY][5] = WhiteRook;
4740 } else if (fromY == 0 && fromX == 4
4741 && board[fromY][fromX] == WhiteKing
4742 && toY == 0 && toX == 2) {
4743 board[fromY][fromX] = EmptySquare;
4744 board[toY][toX] = WhiteKing;
4745 board[fromY][0] = EmptySquare;
4746 board[toY][3] = WhiteRook;
4747 } else if (fromY == 0 && fromX == 3
4748 && board[fromY][fromX] == WhiteKing
4749 && toY == 0 && toX == 5) {
4750 board[fromY][fromX] = EmptySquare;
4751 board[toY][toX] = WhiteKing;
4752 board[fromY][7] = EmptySquare;
4753 board[toY][4] = WhiteRook;
4754 } else if (fromY == 0 && fromX == 3
4755 && board[fromY][fromX] == WhiteKing
4756 && toY == 0 && toX == 1) {
4757 board[fromY][fromX] = EmptySquare;
4758 board[toY][toX] = WhiteKing;
4759 board[fromY][0] = EmptySquare;
4760 board[toY][2] = WhiteRook;
4761 } else if (board[fromY][fromX] == WhitePawn
4763 /* white pawn promotion */
4764 board[7][toX] = CharToPiece(ToUpper(promoChar));
4765 if (board[7][toX] == EmptySquare) {
4766 board[7][toX] = WhiteQueen;
4768 board[fromY][fromX] = EmptySquare;
4769 } else if ((fromY == 4)
4771 && (board[fromY][fromX] == WhitePawn)
4772 && (board[toY][toX] == EmptySquare)) {
4773 board[fromY][fromX] = EmptySquare;
4774 board[toY][toX] = WhitePawn;
4775 captured = board[toY - 1][toX];
4776 board[toY - 1][toX] = EmptySquare;
4777 } else if (fromY == 7 && fromX == 4
4778 && board[fromY][fromX] == BlackKing
4779 && toY == 7 && toX == 6) {
4780 board[fromY][fromX] = EmptySquare;
4781 board[toY][toX] = BlackKing;
4782 board[fromY][7] = EmptySquare;
4783 board[toY][5] = BlackRook;
4784 } else if (fromY == 7 && fromX == 4
4785 && board[fromY][fromX] == BlackKing
4786 && toY == 7 && toX == 2) {
4787 board[fromY][fromX] = EmptySquare;
4788 board[toY][toX] = BlackKing;
4789 board[fromY][0] = EmptySquare;
4790 board[toY][3] = BlackRook;
4791 } else if (fromY == 7 && fromX == 3
4792 && board[fromY][fromX] == BlackKing
4793 && toY == 7 && toX == 5) {
4794 board[fromY][fromX] = EmptySquare;
4795 board[toY][toX] = BlackKing;
4796 board[fromY][7] = EmptySquare;
4797 board[toY][4] = BlackRook;
4798 } else if (fromY == 7 && fromX == 3
4799 && board[fromY][fromX] == BlackKing
4800 && toY == 7 && toX == 1) {
4801 board[fromY][fromX] = EmptySquare;
4802 board[toY][toX] = BlackKing;
4803 board[fromY][0] = EmptySquare;
4804 board[toY][2] = BlackRook;
4805 } else if (board[fromY][fromX] == BlackPawn
4807 /* black pawn promotion */
4808 board[0][toX] = CharToPiece(ToLower(promoChar));
4809 if (board[0][toX] == EmptySquare) {
4810 board[0][toX] = BlackQueen;
4812 board[fromY][fromX] = EmptySquare;
4813 } else if ((fromY == 3)
4815 && (board[fromY][fromX] == BlackPawn)
4816 && (board[toY][toX] == EmptySquare)) {
4817 board[fromY][fromX] = EmptySquare;
4818 board[toY][toX] = BlackPawn;
4819 captured = board[toY + 1][toX];
4820 board[toY + 1][toX] = EmptySquare;
4822 board[toY][toX] = board[fromY][fromX];
4823 board[fromY][fromX] = EmptySquare;
4825 if (gameInfo.variant == VariantCrazyhouse) {
4827 /* !!A lot more code needs to be written to support holdings */
4828 if (fromY == DROP_RANK) {
4829 /* Delete from holdings */
4830 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4832 if (captured != EmptySquare) {
4833 /* Add to holdings */
4834 if (captured < BlackPawn) {
4835 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4837 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4841 } else if (gameInfo.variant == VariantAtomic) {
4842 if (captured != EmptySquare) {
4844 for (y = toY-1; y <= toY+1; y++) {
4845 for (x = toX-1; x <= toX+1; x++) {
4846 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4847 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4848 board[y][x] = EmptySquare;
4852 board[toY][toX] = EmptySquare;
4857 /* Updates forwardMostMove */
4859 MakeMove(fromX, fromY, toX, toY, promoChar)
4860 int fromX, fromY, toX, toY;
4864 if (forwardMostMove >= MAX_MOVES) {
4865 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4870 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4871 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4872 if (commentList[forwardMostMove] != NULL) {
4873 free(commentList[forwardMostMove]);
4874 commentList[forwardMostMove] = NULL;
4876 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4877 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4878 gameInfo.result = GameUnfinished;
4879 if (gameInfo.resultDetails != NULL) {
4880 free(gameInfo.resultDetails);
4881 gameInfo.resultDetails = NULL;
4883 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4884 moveList[forwardMostMove - 1]);
4885 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4886 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4887 fromY, fromX, toY, toX, promoChar,
4888 parseList[forwardMostMove - 1]);
4889 switch (MateTest(boards[forwardMostMove],
4890 PosFlags(forwardMostMove), EP_UNKNOWN)){
4896 strcat(parseList[forwardMostMove - 1], "+");
4899 strcat(parseList[forwardMostMove - 1], "#");
4904 /* Updates currentMove if not pausing */
4906 ShowMove(fromX, fromY, toX, toY)
4908 int instant = (gameMode == PlayFromGameFile) ?
4909 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4910 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4912 if (forwardMostMove == currentMove + 1) {
4913 AnimateMove(boards[forwardMostMove - 1],
4914 fromX, fromY, toX, toY);
4916 if (appData.highlightLastMove) {
4917 SetHighlights(fromX, fromY, toX, toY);
4920 currentMove = forwardMostMove;
4923 if (instant) return;
4924 DisplayMove(currentMove - 1);
4925 DrawPosition(FALSE, boards[currentMove]);
4926 DisplayBothClocks();
4927 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4932 InitChessProgram(cps)
4933 ChessProgramState *cps;
4936 if (appData.noChessProgram) return;
4937 hintRequested = FALSE;
4938 bookRequested = FALSE;
4939 SendToProgram(cps->initString, cps);
4940 if (gameInfo.variant != VariantNormal &&
4941 gameInfo.variant != VariantLoadable) {
4942 char *v = VariantName(gameInfo.variant);
4943 if (StrStr(cps->variants, v) == NULL) {
4944 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4945 DisplayFatalError(buf, 0, 1);
4948 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4949 SendToProgram(buf, cps);
4952 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4953 SendToProgram(buf, cps);
4955 cps->maybeThinking = FALSE;
4956 cps->offeredDraw = 0;
4957 if (!appData.icsActive) {
4958 SendTimeControl(cps, movesPerSession, timeControl,
4959 timeIncrement, appData.searchDepth,
4962 if (appData.showThinking) {
4963 SendToProgram("post\n", cps);
4965 SendToProgram("hard\n", cps);
4966 if (!appData.ponderNextMove) {
4967 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4968 it without being sure what state we are in first. "hard"
4969 is not a toggle, so that one is OK.
4971 SendToProgram("easy\n", cps);
4974 sprintf(buf, "ping %d\n", ++cps->lastPing);
4975 SendToProgram(buf, cps);
4977 cps->initDone = TRUE;
4982 StartChessProgram(cps)
4983 ChessProgramState *cps;
4988 if (appData.noChessProgram) return;
4989 cps->initDone = FALSE;
4991 if (strcmp(cps->host, "localhost") == 0) {
4992 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4993 } else if (*appData.remoteShell == NULLCHAR) {
4994 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4996 if (*appData.remoteUser == NULLCHAR) {
4997 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
5000 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5001 cps->host, appData.remoteUser, cps->program);
5003 err = StartChildProcess(buf, "", &cps->pr);
5007 sprintf(buf, "Startup failure on '%s'", cps->program);
5008 DisplayFatalError(buf, err, 1);
5014 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5015 if (cps->protocolVersion > 1) {
5016 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5017 SendToProgram(buf, cps);
5019 SendToProgram("xboard\n", cps);
5025 TwoMachinesEventIfReady P((void))
5027 if (first.lastPing != first.lastPong) {
5028 DisplayMessage("", "Waiting for first chess program");
5029 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5032 if (second.lastPing != second.lastPong) {
5033 DisplayMessage("", "Waiting for second chess program");
5034 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5042 NextMatchGame P((void))
5045 if (*appData.loadGameFile != NULLCHAR) {
5046 LoadGameFromFile(appData.loadGameFile,
5047 appData.loadGameIndex,
5048 appData.loadGameFile, FALSE);
5049 } else if (*appData.loadPositionFile != NULLCHAR) {
5050 LoadPositionFromFile(appData.loadPositionFile,
5051 appData.loadPositionIndex,
5052 appData.loadPositionFile);
5054 TwoMachinesEventIfReady();
5058 GameEnds(result, resultDetails, whosays)
5060 char *resultDetails;
5063 GameMode nextGameMode;
5066 if (appData.debugMode) {
5067 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5068 result, resultDetails ? resultDetails : "(null)", whosays);
5071 if (appData.icsActive && whosays == GE_ENGINE) {
5072 /* If we are playing on ICS, the server decides when the
5073 game is over, but the engine can offer to draw, claim
5077 if (appData.zippyPlay && first.initDone) {
5078 if (result == GameIsDrawn) {
5079 /* In case draw still needs to be claimed */
5080 SendToICS(ics_prefix);
5081 SendToICS("draw\n");
5082 } else if (StrCaseStr(resultDetails, "resign")) {
5083 SendToICS(ics_prefix);
5084 SendToICS("resign\n");
5091 /* If we're loading the game from a file, stop */
5092 if (whosays == GE_FILE) {
5093 (void) StopLoadGameTimer();
5097 /* Cancel draw offers */
5098 first.offeredDraw = second.offeredDraw = 0;
5100 /* If this is an ICS game, only ICS can really say it's done;
5101 if not, anyone can. */
5102 isIcsGame = (gameMode == IcsPlayingWhite ||
5103 gameMode == IcsPlayingBlack ||
5104 gameMode == IcsObserving ||
5105 gameMode == IcsExamining);
5107 if (!isIcsGame || whosays == GE_ICS) {
5108 /* OK -- not an ICS game, or ICS said it was done */
5110 if (!isIcsGame && !appData.noChessProgram)
5111 SetUserThinkingEnables();
5113 if (resultDetails != NULL) {
5114 gameInfo.result = result;
5115 gameInfo.resultDetails = StrSave(resultDetails);
5117 /* Tell program how game ended in case it is learning */
5118 if (gameMode == MachinePlaysWhite ||
5119 gameMode == MachinePlaysBlack ||
5120 gameMode == TwoMachinesPlay ||
5121 gameMode == IcsPlayingWhite ||
5122 gameMode == IcsPlayingBlack ||
5123 gameMode == BeginningOfGame) {
5125 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5127 if (first.pr != NoProc) {
5128 SendToProgram(buf, &first);
5130 if (second.pr != NoProc &&
5131 gameMode == TwoMachinesPlay) {
5132 SendToProgram(buf, &second);
5136 /* display last move only if game was not loaded from file */
5137 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5138 DisplayMove(currentMove - 1);
5140 if (forwardMostMove != 0) {
5141 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5142 if (*appData.saveGameFile != NULLCHAR) {
5143 SaveGameToFile(appData.saveGameFile, TRUE);
5144 } else if (appData.autoSaveGames) {
5147 if (*appData.savePositionFile != NULLCHAR) {
5148 SavePositionToFile(appData.savePositionFile);
5154 if (appData.icsActive) {
5155 if (appData.quietPlay &&
5156 (gameMode == IcsPlayingWhite ||
5157 gameMode == IcsPlayingBlack)) {
5158 SendToICS(ics_prefix);
5159 SendToICS("set shout 1\n");
5161 nextGameMode = IcsIdle;
5162 ics_user_moved = FALSE;
5163 /* clean up premove. It's ugly when the game has ended and the
5164 * premove highlights are still on the board.
5168 ClearPremoveHighlights();
5169 DrawPosition(FALSE, boards[currentMove]);
5171 if (whosays == GE_ICS) {
5174 if (gameMode == IcsPlayingWhite)
5176 else if(gameMode == IcsPlayingBlack)
5180 if (gameMode == IcsPlayingBlack)
5182 else if(gameMode == IcsPlayingWhite)
5189 PlayIcsUnfinishedSound();
5192 } else if (gameMode == EditGame ||
5193 gameMode == PlayFromGameFile ||
5194 gameMode == AnalyzeMode ||
5195 gameMode == AnalyzeFile) {
5196 nextGameMode = gameMode;
5198 nextGameMode = EndOfGame;
5203 nextGameMode = gameMode;
5206 if (appData.noChessProgram) {
5207 gameMode = nextGameMode;
5213 /* Put first chess program into idle state */
5214 if (first.pr != NoProc &&
5215 (gameMode == MachinePlaysWhite ||
5216 gameMode == MachinePlaysBlack ||
5217 gameMode == TwoMachinesPlay ||
5218 gameMode == IcsPlayingWhite ||
5219 gameMode == IcsPlayingBlack ||
5220 gameMode == BeginningOfGame)) {
5221 SendToProgram("force\n", &first);
5222 if (first.usePing) {
5224 sprintf(buf, "ping %d\n", ++first.lastPing);
5225 SendToProgram(buf, &first);
5228 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5229 /* Kill off first chess program */
5230 if (first.isr != NULL)
5231 RemoveInputSource(first.isr);
5234 if (first.pr != NoProc) {
5236 SendToProgram("quit\n", &first);
5237 DestroyChildProcess(first.pr, first.useSigterm);
5242 /* Put second chess program into idle state */
5243 if (second.pr != NoProc &&
5244 gameMode == TwoMachinesPlay) {
5245 SendToProgram("force\n", &second);
5246 if (second.usePing) {
5248 sprintf(buf, "ping %d\n", ++second.lastPing);
5249 SendToProgram(buf, &second);
5252 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5253 /* Kill off second chess program */
5254 if (second.isr != NULL)
5255 RemoveInputSource(second.isr);
5258 if (second.pr != NoProc) {
5259 SendToProgram("quit\n", &second);
5260 DestroyChildProcess(second.pr, second.useSigterm);
5265 if (matchMode && gameMode == TwoMachinesPlay) {
5268 if (first.twoMachinesColor[0] == 'w') {
5275 if (first.twoMachinesColor[0] == 'b') {
5284 if (matchGame < appData.matchGames) {
5286 tmp = first.twoMachinesColor;
5287 first.twoMachinesColor = second.twoMachinesColor;
5288 second.twoMachinesColor = tmp;
5289 gameMode = nextGameMode;
5291 ScheduleDelayedEvent(NextMatchGame, 10000);
5295 gameMode = nextGameMode;
5296 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5297 first.tidy, second.tidy,
5298 first.matchWins, second.matchWins,
5299 appData.matchGames - (first.matchWins + second.matchWins));
5300 DisplayFatalError(buf, 0, 0);
5303 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5304 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5306 gameMode = nextGameMode;
5310 /* Assumes program was just initialized (initString sent).
5311 Leaves program in force mode. */
5313 FeedMovesToProgram(cps, upto)
5314 ChessProgramState *cps;
5319 if (appData.debugMode)
5320 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5321 startedFromSetupPosition ? "position and " : "",
5322 backwardMostMove, upto, cps->which);
5323 SendToProgram("force\n", cps);
5324 if (startedFromSetupPosition) {
5325 SendBoard(cps, backwardMostMove);
5327 for (i = backwardMostMove; i < upto; i++) {
5328 SendMoveToProgram(i, cps);
5334 ResurrectChessProgram()
5336 /* The chess program may have exited.
5337 If so, restart it and feed it all the moves made so far. */
5339 if (appData.noChessProgram || first.pr != NoProc) return;
5341 StartChessProgram(&first);
5342 InitChessProgram(&first);
5343 FeedMovesToProgram(&first, currentMove);
5345 if (!first.sendTime) {
5346 /* can't tell gnuchess what its clock should read,
5347 so we bow to its notion. */
5349 timeRemaining[0][currentMove] = whiteTimeRemaining;
5350 timeRemaining[1][currentMove] = blackTimeRemaining;
5353 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
5354 appData.icsEngineAnalyze) && first.analysisSupport) {
5355 SendToProgram("analyze\n", &first);
5356 first.analyzing = TRUE;
5369 if (appData.debugMode) {
5370 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5371 redraw, init, gameMode);
5374 pausing = pauseExamInvalid = FALSE;
5375 startedFromSetupPosition = blackPlaysFirst = FALSE;
5377 whiteFlag = blackFlag = FALSE;
5378 userOfferedDraw = FALSE;
5379 hintRequested = bookRequested = FALSE;
5380 first.maybeThinking = FALSE;
5381 second.maybeThinking = FALSE;
5382 thinkOutput[0] = NULLCHAR;
5383 lastHint[0] = NULLCHAR;
5384 ClearGameInfo(&gameInfo);
5385 gameInfo.variant = StringToVariant(appData.variant);
5386 ics_user_moved = ics_clock_paused = FALSE;
5387 ics_getting_history = H_FALSE;
5389 white_holding[0] = black_holding[0] = NULLCHAR;
5390 ClearProgramStats();
5394 flipView = appData.flipView;
5395 ClearPremoveHighlights();
5397 alarmSounded = FALSE;
5399 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5401 gameMode = BeginningOfGame;
5403 InitPosition(redraw);
5404 for (i = 0; i < MAX_MOVES; i++) {
5405 if (commentList[i] != NULL) {
5406 free(commentList[i]);
5407 commentList[i] = NULL;
5411 timeRemaining[0][0] = whiteTimeRemaining;
5412 timeRemaining[1][0] = blackTimeRemaining;
5413 if (first.pr == NULL) {
5414 StartChessProgram(&first);
5416 if (init) InitChessProgram(&first);
5418 DisplayMessage("", "");
5419 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5426 if (!AutoPlayOneMove())
5428 if (matchMode || appData.timeDelay == 0)
5430 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5432 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5441 int fromX, fromY, toX, toY;
5443 if (appData.debugMode) {
5444 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5447 if (gameMode != PlayFromGameFile)
5450 if (currentMove >= forwardMostMove) {
5451 gameMode = EditGame;
5456 toX = moveList[currentMove][2] - 'a';
5457 toY = moveList[currentMove][3] - '1';
5459 if (moveList[currentMove][1] == '@') {
5460 if (appData.highlightLastMove) {
5461 SetHighlights(-1, -1, toX, toY);
5464 fromX = moveList[currentMove][0] - 'a';
5465 fromY = moveList[currentMove][1] - '1';
5466 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5468 if (appData.highlightLastMove) {
5469 SetHighlights(fromX, fromY, toX, toY);
5472 DisplayMove(currentMove);
5473 SendMoveToProgram(currentMove++, &first);
5474 DisplayBothClocks();
5475 DrawPosition(FALSE, boards[currentMove]);
5476 if (commentList[currentMove] != NULL) {
5477 DisplayComment(currentMove - 1, commentList[currentMove]);
5484 LoadGameOneMove(readAhead)
5485 ChessMove readAhead;
5487 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5488 char promoChar = NULLCHAR;
5493 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5494 gameMode != AnalyzeMode && gameMode != Training) {
5499 yyboardindex = forwardMostMove;
5500 if (readAhead != (ChessMove)0) {
5501 moveType = readAhead;
5503 if (gameFileFP == NULL)
5505 moveType = (ChessMove) yylex();
5511 if (appData.debugMode)
5512 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5514 if (*p == '{' || *p == '[' || *p == '(') {
5515 p[strlen(p) - 1] = NULLCHAR;
5519 /* append the comment but don't display it */
5520 while (*p == '\n') p++;
5521 AppendComment(currentMove, p);
5524 case WhiteCapturesEnPassant:
5525 case BlackCapturesEnPassant:
5526 case WhitePromotionQueen:
5527 case BlackPromotionQueen:
5528 case WhitePromotionRook:
5529 case BlackPromotionRook:
5530 case WhitePromotionBishop:
5531 case BlackPromotionBishop:
5532 case WhitePromotionKnight:
5533 case BlackPromotionKnight:
5534 case WhitePromotionKing:
5535 case BlackPromotionKing:
5537 case WhiteKingSideCastle:
5538 case WhiteQueenSideCastle:
5539 case BlackKingSideCastle:
5540 case BlackQueenSideCastle:
5541 case WhiteKingSideCastleWild:
5542 case WhiteQueenSideCastleWild:
5543 case BlackKingSideCastleWild:
5544 case BlackQueenSideCastleWild:
5545 if (appData.debugMode)
5546 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5547 fromX = currentMoveString[0] - 'a';
5548 fromY = currentMoveString[1] - '1';
5549 toX = currentMoveString[2] - 'a';
5550 toY = currentMoveString[3] - '1';
5551 promoChar = currentMoveString[4];
5556 if (appData.debugMode)
5557 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5558 fromX = moveType == WhiteDrop ?
5559 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5560 (int) CharToPiece(ToLower(currentMoveString[0]));
5562 toX = currentMoveString[2] - 'a';
5563 toY = currentMoveString[3] - '1';
5569 case GameUnfinished:
5570 if (appData.debugMode)
5571 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5572 p = strchr(yy_text, '{');
5573 if (p == NULL) p = strchr(yy_text, '(');
5576 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5578 q = strchr(p, *p == '{' ? '}' : ')');
5579 if (q != NULL) *q = NULLCHAR;
5582 GameEnds(moveType, p, GE_FILE);
5584 if (cmailMsgLoaded) {
5586 flipView = WhiteOnMove(currentMove);
5587 if (moveType == GameUnfinished) flipView = !flipView;
5588 if (appData.debugMode)
5589 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5593 case (ChessMove) 0: /* end of file */
5594 if (appData.debugMode)
5595 fprintf(debugFP, "Parser hit end of file\n");
5596 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5602 if (WhiteOnMove(currentMove)) {
5603 GameEnds(BlackWins, "Black mates", GE_FILE);
5605 GameEnds(WhiteWins, "White mates", GE_FILE);
5609 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5616 if (lastLoadGameStart == GNUChessGame) {
5617 /* GNUChessGames have numbers, but they aren't move numbers */
5618 if (appData.debugMode)
5619 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5620 yy_text, (int) moveType);
5621 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5623 /* else fall thru */
5628 /* Reached start of next game in file */
5629 if (appData.debugMode)
5630 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5631 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5637 if (WhiteOnMove(currentMove)) {
5638 GameEnds(BlackWins, "Black mates", GE_FILE);
5640 GameEnds(WhiteWins, "White mates", GE_FILE);
5644 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5650 case PositionDiagram: /* should not happen; ignore */
5651 case ElapsedTime: /* ignore */
5652 case NAG: /* ignore */
5653 if (appData.debugMode)
5654 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5655 yy_text, (int) moveType);
5656 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5659 if (appData.testLegality) {
5660 if (appData.debugMode)
5661 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5662 sprintf(move, _("Illegal move: %d.%s%s"),
5663 (forwardMostMove / 2) + 1,
5664 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5665 DisplayError(move, 0);
5668 if (appData.debugMode)
5669 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5670 yy_text, currentMoveString);
5671 fromX = currentMoveString[0] - 'a';
5672 fromY = currentMoveString[1] - '1';
5673 toX = currentMoveString[2] - 'a';
5674 toY = currentMoveString[3] - '1';
5675 promoChar = currentMoveString[4];
5680 if (appData.debugMode)
5681 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5682 sprintf(move, _("Ambiguous move: %d.%s%s"),
5683 (forwardMostMove / 2) + 1,
5684 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5685 DisplayError(move, 0);
5690 case ImpossibleMove:
5691 if (appData.debugMode)
5692 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5693 sprintf(move, _("Illegal move: %d.%s%s"),
5694 (forwardMostMove / 2) + 1,
5695 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5696 DisplayError(move, 0);
5702 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5703 DrawPosition(FALSE, boards[currentMove]);
5704 DisplayBothClocks();
5705 if (!appData.matchMode && commentList[currentMove] != NULL)
5706 DisplayComment(currentMove - 1, commentList[currentMove]);
5708 (void) StopLoadGameTimer();
5710 cmailOldMove = forwardMostMove;
5713 /* currentMoveString is set as a side-effect of yylex */
5714 strcat(currentMoveString, "\n");
5715 strcpy(moveList[forwardMostMove], currentMoveString);
5717 thinkOutput[0] = NULLCHAR;
5718 MakeMove(fromX, fromY, toX, toY, promoChar);
5719 currentMove = forwardMostMove;
5724 /* Load the nth game from the given file */
5726 LoadGameFromFile(filename, n, title, useList)
5730 /*Boolean*/ int useList;
5735 if (strcmp(filename, "-") == 0) {
5739 f = fopen(filename, "rb");
5741 sprintf(buf, _("Can't open \"%s\""), filename);
5742 DisplayError(buf, errno);
5746 if (fseek(f, 0, 0) == -1) {
5747 /* f is not seekable; probably a pipe */
5750 if (useList && n == 0) {
5751 int error = GameListBuild(f);
5753 DisplayError(_("Cannot build game list"), error);
5754 } else if (!ListEmpty(&gameList) &&
5755 ((ListGame *) gameList.tailPred)->number > 1) {
5756 GameListPopUp(f, title);
5763 return LoadGame(f, n, title, FALSE);
5768 MakeRegisteredMove()
5770 int fromX, fromY, toX, toY;
5772 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5773 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5776 if (appData.debugMode)
5777 fprintf(debugFP, "Restoring %s for game %d\n",
5778 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5780 thinkOutput[0] = NULLCHAR;
5781 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5782 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5783 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5784 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5785 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5786 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5787 MakeMove(fromX, fromY, toX, toY, promoChar);
5788 ShowMove(fromX, fromY, toX, toY);
5790 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5797 if (WhiteOnMove(currentMove)) {
5798 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5800 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5805 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5812 if (WhiteOnMove(currentMove)) {
5813 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5815 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5820 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5831 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5833 CmailLoadGame(f, gameNumber, title, useList)
5841 if (gameNumber > nCmailGames) {
5842 DisplayError(_("No more games in this message"), 0);
5845 if (f == lastLoadGameFP) {
5846 int offset = gameNumber - lastLoadGameNumber;
5848 cmailMsg[0] = NULLCHAR;
5849 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5850 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5851 nCmailMovesRegistered--;
5853 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5854 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5855 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5858 if (! RegisterMove()) return FALSE;
5862 retVal = LoadGame(f, gameNumber, title, useList);
5864 /* Make move registered during previous look at this game, if any */
5865 MakeRegisteredMove();
5867 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5868 commentList[currentMove]
5869 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5870 DisplayComment(currentMove - 1, commentList[currentMove]);
5876 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5881 int gameNumber = lastLoadGameNumber + offset;
5882 if (lastLoadGameFP == NULL) {
5883 DisplayError(_("No game has been loaded yet"), 0);
5886 if (gameNumber <= 0) {
5887 DisplayError(_("Can't back up any further"), 0);
5890 if (cmailMsgLoaded) {
5891 return CmailLoadGame(lastLoadGameFP, gameNumber,
5892 lastLoadGameTitle, lastLoadGameUseList);
5894 return LoadGame(lastLoadGameFP, gameNumber,
5895 lastLoadGameTitle, lastLoadGameUseList);
5901 /* Load the nth game from open file f */
5903 LoadGame(f, gameNumber, title, useList)
5911 int gn = gameNumber;
5912 ListGame *lg = NULL;
5915 GameMode oldGameMode;
5917 if (appData.debugMode)
5918 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5920 if (gameMode == Training )
5921 SetTrainingModeOff();
5923 oldGameMode = gameMode;
5924 if (gameMode != BeginningOfGame) {
5929 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5930 fclose(lastLoadGameFP);
5934 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5937 fseek(f, lg->offset, 0);
5938 GameListHighlight(gameNumber);
5942 DisplayError(_("Game number out of range"), 0);
5947 if (fseek(f, 0, 0) == -1) {
5948 if (f == lastLoadGameFP ?
5949 gameNumber == lastLoadGameNumber + 1 :
5953 DisplayError(_("Can't seek on game file"), 0);
5959 lastLoadGameNumber = gameNumber;
5960 strcpy(lastLoadGameTitle, title);
5961 lastLoadGameUseList = useList;
5966 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5967 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5968 lg->gameInfo.black);
5970 } else if (*title != NULLCHAR) {
5971 if (gameNumber > 1) {
5972 sprintf(buf, "%s %d", title, gameNumber);
5975 DisplayTitle(title);
5979 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5980 gameMode = PlayFromGameFile;
5984 currentMove = forwardMostMove = backwardMostMove = 0;
5985 CopyBoard(boards[0], initialPosition);
5989 * Skip the first gn-1 games in the file.
5990 * Also skip over anything that precedes an identifiable
5991 * start of game marker, to avoid being confused by
5992 * garbage at the start of the file. Currently
5993 * recognized start of game markers are the move number "1",
5994 * the pattern "gnuchess .* game", the pattern
5995 * "^[#;%] [^ ]* game file", and a PGN tag block.
5996 * A game that starts with one of the latter two patterns
5997 * will also have a move number 1, possibly
5998 * following a position diagram.
5999 * 5-4-02: Let's try being more lenient and allowing a game to
6000 * start with an unnumbered move. Does that break anything?
6002 cm = lastLoadGameStart = (ChessMove) 0;
6004 yyboardindex = forwardMostMove;
6005 cm = (ChessMove) yylex();
6008 if (cmailMsgLoaded) {
6009 nCmailGames = CMAIL_MAX_GAMES - gn;
6012 DisplayError(_("Game not found in file"), 0);
6019 lastLoadGameStart = cm;
6023 switch (lastLoadGameStart) {
6030 gn--; /* count this game */
6031 lastLoadGameStart = cm;
6040 switch (lastLoadGameStart) {
6045 gn--; /* count this game */
6046 lastLoadGameStart = cm;
6049 lastLoadGameStart = cm; /* game counted already */
6057 yyboardindex = forwardMostMove;
6058 cm = (ChessMove) yylex();
6059 } while (cm == PGNTag || cm == Comment);
6066 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6067 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6068 != CMAIL_OLD_RESULT) {
6070 cmailResult[ CMAIL_MAX_GAMES
6071 - gn - 1] = CMAIL_OLD_RESULT;
6077 /* Only a NormalMove can be at the start of a game
6078 * without a position diagram. */
6079 if (lastLoadGameStart == (ChessMove) 0) {
6081 lastLoadGameStart = MoveNumberOne;
6090 if (appData.debugMode)
6091 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6093 if (cm == XBoardGame) {
6094 /* Skip any header junk before position diagram and/or move 1 */
6096 yyboardindex = forwardMostMove;
6097 cm = (ChessMove) yylex();
6099 if (cm == (ChessMove) 0 ||
6100 cm == GNUChessGame || cm == XBoardGame) {
6101 /* Empty game; pretend end-of-file and handle later */
6106 if (cm == MoveNumberOne || cm == PositionDiagram ||
6107 cm == PGNTag || cm == Comment)
6110 } else if (cm == GNUChessGame) {
6111 if (gameInfo.event != NULL) {
6112 free(gameInfo.event);
6114 gameInfo.event = StrSave(yy_text);
6117 startedFromSetupPosition = FALSE;
6118 while (cm == PGNTag) {
6119 if (appData.debugMode)
6120 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6121 err = ParsePGNTag(yy_text, &gameInfo);
6122 if (!err) numPGNTags++;
6124 if (gameInfo.fen != NULL) {
6125 Board initial_position;
6126 startedFromSetupPosition = TRUE;
6127 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6129 DisplayError(_("Bad FEN position in file"), 0);
6132 CopyBoard(boards[0], initial_position);
6133 if (blackPlaysFirst) {
6134 currentMove = forwardMostMove = backwardMostMove = 1;
6135 CopyBoard(boards[1], initial_position);
6136 strcpy(moveList[0], "");
6137 strcpy(parseList[0], "");
6138 timeRemaining[0][1] = whiteTimeRemaining;
6139 timeRemaining[1][1] = blackTimeRemaining;
6140 if (commentList[0] != NULL) {
6141 commentList[1] = commentList[0];
6142 commentList[0] = NULL;
6145 currentMove = forwardMostMove = backwardMostMove = 0;
6147 yyboardindex = forwardMostMove;
6149 gameInfo.fen = NULL;
6152 yyboardindex = forwardMostMove;
6153 cm = (ChessMove) yylex();
6155 /* Handle comments interspersed among the tags */
6156 while (cm == Comment) {
6158 if (appData.debugMode)
6159 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6161 if (*p == '{' || *p == '[' || *p == '(') {
6162 p[strlen(p) - 1] = NULLCHAR;
6165 while (*p == '\n') p++;
6166 AppendComment(currentMove, p);
6167 yyboardindex = forwardMostMove;
6168 cm = (ChessMove) yylex();
6172 /* don't rely on existence of Event tag since if game was
6173 * pasted from clipboard the Event tag may not exist
6175 if (numPGNTags > 0){
6177 if (gameInfo.variant == VariantNormal) {
6178 gameInfo.variant = StringToVariant(gameInfo.event);
6181 tags = PGNTags(&gameInfo);
6182 TagsPopUp(tags, CmailMsg());
6186 /* Make something up, but don't display it now */
6191 if (cm == PositionDiagram) {
6194 Board initial_position;
6196 if (appData.debugMode)
6197 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6199 if (!startedFromSetupPosition) {
6201 for (i = BOARD_SIZE - 1; i >= 0; i--)
6202 for (j = 0; j < BOARD_SIZE; p++)
6212 initial_position[i][j++] = CharToPiece(*p);
6215 while (*p == ' ' || *p == '\t' ||
6216 *p == '\n' || *p == '\r') p++;
6218 if (strncmp(p, "black", strlen("black"))==0)
6219 blackPlaysFirst = TRUE;
6221 blackPlaysFirst = FALSE;
6222 startedFromSetupPosition = TRUE;
6224 CopyBoard(boards[0], initial_position);
6225 if (blackPlaysFirst) {
6226 currentMove = forwardMostMove = backwardMostMove = 1;
6227 CopyBoard(boards[1], initial_position);
6228 strcpy(moveList[0], "");
6229 strcpy(parseList[0], "");
6230 timeRemaining[0][1] = whiteTimeRemaining;
6231 timeRemaining[1][1] = blackTimeRemaining;
6232 if (commentList[0] != NULL) {
6233 commentList[1] = commentList[0];
6234 commentList[0] = NULL;
6237 currentMove = forwardMostMove = backwardMostMove = 0;
6240 yyboardindex = forwardMostMove;
6241 cm = (ChessMove) yylex();
6244 if (first.pr == NoProc) {
6245 StartChessProgram(&first);
6247 InitChessProgram(&first);
6248 SendToProgram("force\n", &first);
6249 if (startedFromSetupPosition) {
6250 SendBoard(&first, forwardMostMove);
6251 DisplayBothClocks();
6254 while (cm == Comment) {
6256 if (appData.debugMode)
6257 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6259 if (*p == '{' || *p == '[' || *p == '(') {
6260 p[strlen(p) - 1] = NULLCHAR;
6263 while (*p == '\n') p++;
6264 AppendComment(currentMove, p);
6265 yyboardindex = forwardMostMove;
6266 cm = (ChessMove) yylex();
6269 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6270 cm == WhiteWins || cm == BlackWins ||
6271 cm == GameIsDrawn || cm == GameUnfinished) {
6272 DisplayMessage("", _("No moves in game"));
6273 if (cmailMsgLoaded) {
6274 if (appData.debugMode)
6275 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6279 DrawPosition(FALSE, boards[currentMove]);
6280 DisplayBothClocks();
6281 gameMode = EditGame;
6288 if (commentList[currentMove] != NULL) {
6289 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6290 DisplayComment(currentMove - 1, commentList[currentMove]);
6293 if (!matchMode && appData.timeDelay != 0)
6294 DrawPosition(FALSE, boards[currentMove]);
6296 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6297 programStats.ok_to_send = 1;
6300 /* if the first token after the PGN tags is a move
6301 * and not move number 1, retrieve it from the parser
6303 if (cm != MoveNumberOne)
6304 LoadGameOneMove(cm);
6306 /* load the remaining moves from the file */
6307 while (LoadGameOneMove((ChessMove)0)) {
6308 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6309 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6312 /* rewind to the start of the game */
6313 currentMove = backwardMostMove;
6315 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6317 if (oldGameMode == AnalyzeFile ||
6318 oldGameMode == AnalyzeMode) {
6322 if (matchMode || appData.timeDelay == 0) {
6324 gameMode = EditGame;
6326 } else if (appData.timeDelay > 0) {
6330 if (appData.debugMode)
6331 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6335 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6337 ReloadPosition(offset)
6340 int positionNumber = lastLoadPositionNumber + offset;
6341 if (lastLoadPositionFP == NULL) {
6342 DisplayError(_("No position has been loaded yet"), 0);
6345 if (positionNumber <= 0) {
6346 DisplayError(_("Can't back up any further"), 0);
6349 return LoadPosition(lastLoadPositionFP, positionNumber,
6350 lastLoadPositionTitle);
6353 /* Load the nth position from the given file */
6355 LoadPositionFromFile(filename, n, title)
6363 if (strcmp(filename, "-") == 0) {
6364 return LoadPosition(stdin, n, "stdin");
6366 f = fopen(filename, "rb");
6368 sprintf(buf, _("Can't open \"%s\""), filename);
6369 DisplayError(buf, errno);
6372 return LoadPosition(f, n, title);
6377 /* Load the nth position from the given open file, and close it */
6379 LoadPosition(f, positionNumber, title)
6384 char *p, line[MSG_SIZ];
6385 Board initial_position;
6386 int i, j, fenMode, pn;
6388 if (gameMode == Training )
6389 SetTrainingModeOff();
6391 if (gameMode != BeginningOfGame) {
6394 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6395 fclose(lastLoadPositionFP);
6397 if (positionNumber == 0) positionNumber = 1;
6398 lastLoadPositionFP = f;
6399 lastLoadPositionNumber = positionNumber;
6400 strcpy(lastLoadPositionTitle, title);
6401 if (first.pr == NoProc) {
6402 StartChessProgram(&first);
6403 InitChessProgram(&first);
6405 pn = positionNumber;
6406 if (positionNumber < 0) {
6407 /* Negative position number means to seek to that byte offset */
6408 if (fseek(f, -positionNumber, 0) == -1) {
6409 DisplayError(_("Can't seek on position file"), 0);
6414 if (fseek(f, 0, 0) == -1) {
6415 if (f == lastLoadPositionFP ?
6416 positionNumber == lastLoadPositionNumber + 1 :
6417 positionNumber == 1) {
6420 DisplayError(_("Can't seek on position file"), 0);
6425 /* See if this file is FEN or old-style xboard */
6426 if (fgets(line, MSG_SIZ, f) == NULL) {
6427 DisplayError(_("Position not found in file"), 0);
6435 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6436 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6437 case '1': case '2': case '3': case '4': case '5': case '6':
6444 if (fenMode || line[0] == '#') pn--;
6446 /* skip postions before number pn */
6447 if (fgets(line, MSG_SIZ, f) == NULL) {
6449 DisplayError(_("Position not found in file"), 0);
6452 if (fenMode || line[0] == '#') pn--;
6457 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6458 DisplayError(_("Bad FEN position in file"), 0);
6462 (void) fgets(line, MSG_SIZ, f);
6463 (void) fgets(line, MSG_SIZ, f);
6465 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6466 (void) fgets(line, MSG_SIZ, f);
6467 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6470 initial_position[i][j++] = CharToPiece(*p);
6474 blackPlaysFirst = FALSE;
6476 (void) fgets(line, MSG_SIZ, f);
6477 if (strncmp(line, "black", strlen("black"))==0)
6478 blackPlaysFirst = TRUE;
6481 startedFromSetupPosition = TRUE;
6483 SendToProgram("force\n", &first);
6484 CopyBoard(boards[0], initial_position);
6485 if (blackPlaysFirst) {
6486 currentMove = forwardMostMove = backwardMostMove = 1;
6487 strcpy(moveList[0], "");
6488 strcpy(parseList[0], "");
6489 CopyBoard(boards[1], initial_position);
6490 DisplayMessage("", _("Black to play"));
6492 currentMove = forwardMostMove = backwardMostMove = 0;
6493 DisplayMessage("", _("White to play"));
6495 SendBoard(&first, forwardMostMove);
6497 if (positionNumber > 1) {
6498 sprintf(line, "%s %d", title, positionNumber);
6501 DisplayTitle(title);
6503 gameMode = EditGame;
6506 timeRemaining[0][1] = whiteTimeRemaining;
6507 timeRemaining[1][1] = blackTimeRemaining;
6508 DrawPosition(FALSE, boards[currentMove]);
6515 CopyPlayerNameIntoFileName(dest, src)
6518 while (*src != NULLCHAR && *src != ',') {
6523 *(*dest)++ = *src++;
6528 char *DefaultFileName(ext)
6531 static char def[MSG_SIZ];
6534 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6536 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6538 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6547 /* Save the current game to the given file */
6549 SaveGameToFile(filename, append)
6556 if (strcmp(filename, "-") == 0) {
6557 return SaveGame(stdout, 0, NULL);
6559 f = fopen(filename, append ? "a" : "w");
6561 sprintf(buf, _("Can't open \"%s\""), filename);
6562 DisplayError(buf, errno);
6565 return SaveGame(f, 0, NULL);
6574 static char buf[MSG_SIZ];
6577 p = strchr(str, ' ');
6578 if (p == NULL) return str;
6579 strncpy(buf, str, p - str);
6580 buf[p - str] = NULLCHAR;
6584 #define PGN_MAX_LINE 75
6586 /* Save game in PGN style and close the file */
6591 int i, offset, linelen, newblock;
6595 int movelen, numlen, blank;
6597 tm = time((time_t *) NULL);
6599 PrintPGNTags(f, &gameInfo);
6601 if (backwardMostMove > 0 || startedFromSetupPosition) {
6602 char *fen = PositionToFEN(backwardMostMove);
6603 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6604 fprintf(f, "\n{--------------\n");
6605 PrintPosition(f, backwardMostMove);
6606 fprintf(f, "--------------}\n");
6612 i = backwardMostMove;
6613 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6617 while (i < forwardMostMove) {
6618 /* Print comments preceding this move */
6619 if (commentList[i] != NULL) {
6620 if (linelen > 0) fprintf(f, "\n");
6621 fprintf(f, "{\n%s}\n", commentList[i]);
6626 /* Format move number */
6628 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6631 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6633 numtext[0] = NULLCHAR;
6636 numlen = strlen(numtext);
6639 /* Print move number */
6640 blank = linelen > 0 && numlen > 0;
6641 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6650 fprintf(f, numtext);
6654 movetext = SavePart(parseList[i]);
6655 movelen = strlen(movetext);
6658 blank = linelen > 0 && movelen > 0;
6659 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6668 fprintf(f, movetext);
6674 /* Start a new line */
6675 if (linelen > 0) fprintf(f, "\n");
6677 /* Print comments after last move */
6678 if (commentList[i] != NULL) {
6679 fprintf(f, "{\n%s}\n", commentList[i]);
6683 if (gameInfo.resultDetails != NULL &&
6684 gameInfo.resultDetails[0] != NULLCHAR) {
6685 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6686 PGNResult(gameInfo.result));
6688 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6695 /* Save game in old style and close the file */
6703 tm = time((time_t *) NULL);
6705 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6708 if (backwardMostMove > 0 || startedFromSetupPosition) {
6709 fprintf(f, "\n[--------------\n");
6710 PrintPosition(f, backwardMostMove);
6711 fprintf(f, "--------------]\n");
6716 i = backwardMostMove;
6717 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6719 while (i < forwardMostMove) {
6720 if (commentList[i] != NULL) {
6721 fprintf(f, "[%s]\n", commentList[i]);
6725 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6728 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6730 if (commentList[i] != NULL) {
6734 if (i >= forwardMostMove) {
6738 fprintf(f, "%s\n", parseList[i]);
6743 if (commentList[i] != NULL) {
6744 fprintf(f, "[%s]\n", commentList[i]);
6747 /* This isn't really the old style, but it's close enough */
6748 if (gameInfo.resultDetails != NULL &&
6749 gameInfo.resultDetails[0] != NULLCHAR) {
6750 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6751 gameInfo.resultDetails);
6753 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6760 /* Save the current game to open file f and close the file */
6762 SaveGame(f, dummy, dummy2)
6767 if (gameMode == EditPosition) EditPositionDone();
6768 if (appData.oldSaveStyle)
6769 return SaveGameOldStyle(f);
6771 return SaveGamePGN(f);
6774 /* Save the current position to the given file */
6776 SavePositionToFile(filename)
6782 if (strcmp(filename, "-") == 0) {
6783 return SavePosition(stdout, 0, NULL);
6785 f = fopen(filename, "a");
6787 sprintf(buf, _("Can't open \"%s\""), filename);
6788 DisplayError(buf, errno);
6791 SavePosition(f, 0, NULL);
6797 /* Save the current position to the given open file and close the file */
6799 SavePosition(f, dummy, dummy2)
6807 if (appData.oldSaveStyle) {
6808 tm = time((time_t *) NULL);
6810 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6812 fprintf(f, "[--------------\n");
6813 PrintPosition(f, currentMove);
6814 fprintf(f, "--------------]\n");
6816 fen = PositionToFEN(currentMove);
6817 fprintf(f, "%s\n", fen);
6825 ReloadCmailMsgEvent(unregister)
6829 static char *inFilename = NULL;
6830 static char *outFilename;
6832 struct stat inbuf, outbuf;
6835 /* Any registered moves are unregistered if unregister is set, */
6836 /* i.e. invoked by the signal handler */
6838 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6839 cmailMoveRegistered[i] = FALSE;
6840 if (cmailCommentList[i] != NULL) {
6841 free(cmailCommentList[i]);
6842 cmailCommentList[i] = NULL;
6845 nCmailMovesRegistered = 0;
6848 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6849 cmailResult[i] = CMAIL_NOT_RESULT;
6853 if (inFilename == NULL) {
6854 /* Because the filenames are static they only get malloced once */
6855 /* and they never get freed */
6856 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6857 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6859 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6860 sprintf(outFilename, "%s.out", appData.cmailGameName);
6863 status = stat(outFilename, &outbuf);
6865 cmailMailedMove = FALSE;
6867 status = stat(inFilename, &inbuf);
6868 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6871 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6872 counts the games, notes how each one terminated, etc.
6874 It would be nice to remove this kludge and instead gather all
6875 the information while building the game list. (And to keep it
6876 in the game list nodes instead of having a bunch of fixed-size
6877 parallel arrays.) Note this will require getting each game's
6878 termination from the PGN tags, as the game list builder does
6879 not process the game moves. --mann
6881 cmailMsgLoaded = TRUE;
6882 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6884 /* Load first game in the file or popup game menu */
6885 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6895 char string[MSG_SIZ];
6897 if ( cmailMailedMove
6898 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6899 return TRUE; /* Allow free viewing */
6902 /* Unregister move to ensure that we don't leave RegisterMove */
6903 /* with the move registered when the conditions for registering no */
6905 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6906 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6907 nCmailMovesRegistered --;
6909 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6911 free(cmailCommentList[lastLoadGameNumber - 1]);
6912 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6916 if (cmailOldMove == -1) {
6917 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6921 if (currentMove > cmailOldMove + 1) {
6922 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6926 if (currentMove < cmailOldMove) {
6927 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6931 if (forwardMostMove > currentMove) {
6932 /* Silently truncate extra moves */
6936 if ( (currentMove == cmailOldMove + 1)
6937 || ( (currentMove == cmailOldMove)
6938 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6939 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6940 if (gameInfo.result != GameUnfinished) {
6941 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6944 if (commentList[currentMove] != NULL) {
6945 cmailCommentList[lastLoadGameNumber - 1]
6946 = StrSave(commentList[currentMove]);
6948 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6950 if (appData.debugMode)
6951 fprintf(debugFP, "Saving %s for game %d\n",
6952 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6955 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6957 f = fopen(string, "w");
6958 if (appData.oldSaveStyle) {
6959 SaveGameOldStyle(f); /* also closes the file */
6961 sprintf(string, "%s.pos.out", appData.cmailGameName);
6962 f = fopen(string, "w");
6963 SavePosition(f, 0, NULL); /* also closes the file */
6965 fprintf(f, "{--------------\n");
6966 PrintPosition(f, currentMove);
6967 fprintf(f, "--------------}\n\n");
6969 SaveGame(f, 0, NULL); /* also closes the file*/
6972 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6973 nCmailMovesRegistered ++;
6974 } else if (nCmailGames == 1) {
6975 DisplayError(_("You have not made a move yet"), 0);
6986 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6987 FILE *commandOutput;
6988 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6989 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6995 if (! cmailMsgLoaded) {
6996 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
7000 if (nCmailGames == nCmailResults) {
7001 DisplayError(_("No unfinished games"), 0);
7005 #if CMAIL_PROHIBIT_REMAIL
7006 if (cmailMailedMove) {
7007 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);
7008 DisplayError(msg, 0);
7013 if (! (cmailMailedMove || RegisterMove())) return;
7015 if ( cmailMailedMove
7016 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7017 sprintf(string, partCommandString,
7018 appData.debugMode ? " -v" : "", appData.cmailGameName);
7019 commandOutput = popen(string, "r");
7021 if (commandOutput == NULL) {
7022 DisplayError(_("Failed to invoke cmail"), 0);
7024 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7025 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7028 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7029 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7030 nBytes = MSG_SIZ - 1;
7032 (void) memcpy(msg, buffer, nBytes);
7034 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7036 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7037 cmailMailedMove = TRUE; /* Prevent >1 moves */
7040 for (i = 0; i < nCmailGames; i ++) {
7041 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7046 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7048 sprintf(buffer, "%s/%s.%s.archive",
7050 appData.cmailGameName,
7052 LoadGameFromFile(buffer, 1, buffer, FALSE);
7053 cmailMsgLoaded = FALSE;
7057 DisplayInformation(msg);
7058 pclose(commandOutput);
7061 if ((*cmailMsg) != '\0') {
7062 DisplayInformation(cmailMsg);
7076 int prependComma = 0;
7078 char string[MSG_SIZ]; /* Space for game-list */
7081 if (!cmailMsgLoaded) return "";
7083 if (cmailMailedMove) {
7084 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7086 /* Create a list of games left */
7087 sprintf(string, "[");
7088 for (i = 0; i < nCmailGames; i ++) {
7089 if (! ( cmailMoveRegistered[i]
7090 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7092 sprintf(number, ",%d", i + 1);
7094 sprintf(number, "%d", i + 1);
7098 strcat(string, number);
7101 strcat(string, "]");
7103 if (nCmailMovesRegistered + nCmailResults == 0) {
7104 switch (nCmailGames) {
7107 _("Still need to make move for game\n"));
7112 _("Still need to make moves for both games\n"));
7117 _("Still need to make moves for all %d games\n"),
7122 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7125 _("Still need to make a move for game %s\n"),
7130 if (nCmailResults == nCmailGames) {
7131 sprintf(cmailMsg, _("No unfinished games\n"));
7133 sprintf(cmailMsg, _("Ready to send mail\n"));
7139 _("Still need to make moves for games %s\n"),
7151 if (gameMode == Training)
7152 SetTrainingModeOff();
7155 cmailMsgLoaded = FALSE;
7156 if (appData.icsActive) {
7157 SendToICS(ics_prefix);
7158 SendToICS("refresh\n");
7162 static int exiting = 0;
7170 /* Give up on clean exit */
7174 /* Keep trying for clean exit */
7178 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7180 if (telnetISR != NULL) {
7181 RemoveInputSource(telnetISR);
7183 if (icsPR != NoProc) {
7184 DestroyChildProcess(icsPR, TRUE);
7186 /* Save game if resource set and not already saved by GameEnds() */
7187 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7188 if (*appData.saveGameFile != NULLCHAR) {
7189 SaveGameToFile(appData.saveGameFile, TRUE);
7190 } else if (appData.autoSaveGames) {
7193 if (*appData.savePositionFile != NULLCHAR) {
7194 SavePositionToFile(appData.savePositionFile);
7197 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7199 /* Kill off chess programs */
7200 if (first.pr != NoProc) {
7202 SendToProgram("quit\n", &first);
7203 DestroyChildProcess(first.pr, first.useSigterm);
7205 if (second.pr != NoProc) {
7206 SendToProgram("quit\n", &second);
7207 DestroyChildProcess(second.pr, second.useSigterm);
7209 if (first.isr != NULL) {
7210 RemoveInputSource(first.isr);
7212 if (second.isr != NULL) {
7213 RemoveInputSource(second.isr);
7223 if (appData.debugMode)
7224 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7228 if (gameMode == MachinePlaysWhite ||
7229 gameMode == MachinePlaysBlack) {
7232 DisplayBothClocks();
7234 if (gameMode == PlayFromGameFile) {
7235 if (appData.timeDelay >= 0)
7237 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7239 SendToICS(ics_prefix);
7240 SendToICS("refresh\n");
7241 } else if (currentMove < forwardMostMove) {
7242 ForwardInner(forwardMostMove);
7244 pauseExamInvalid = FALSE;
7250 pauseExamForwardMostMove = forwardMostMove;
7251 pauseExamInvalid = FALSE;
7254 case IcsPlayingWhite:
7255 case IcsPlayingBlack:
7259 case PlayFromGameFile:
7260 (void) StopLoadGameTimer();
7264 case BeginningOfGame:
7265 if (appData.icsActive) return;
7266 /* else fall through */
7267 case MachinePlaysWhite:
7268 case MachinePlaysBlack:
7269 case TwoMachinesPlay:
7270 if (forwardMostMove == 0)
7271 return; /* don't pause if no one has moved */
7272 if ((gameMode == MachinePlaysWhite &&
7273 !WhiteOnMove(forwardMostMove)) ||
7274 (gameMode == MachinePlaysBlack &&
7275 WhiteOnMove(forwardMostMove))) {
7288 char title[MSG_SIZ];
7290 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7291 strcpy(title, _("Edit comment"));
7293 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7294 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7295 parseList[currentMove - 1]);
7298 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7305 char *tags = PGNTags(&gameInfo);
7306 EditTagsPopUp(tags);
7313 if (appData.noChessProgram || gameMode == AnalyzeMode)
7316 if (gameMode != AnalyzeFile) {
7317 if (!appData.icsEngineAnalyze) {
7319 if (gameMode != EditGame) return;
7321 ResurrectChessProgram();
7322 SendToProgram("analyze\n", &first);
7323 first.analyzing = TRUE;
7324 /*first.maybeThinking = TRUE;*/
7325 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7326 AnalysisPopUp(_("Analysis"),
7327 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7329 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
7334 StartAnalysisClock();
7335 GetTimeMark(&lastNodeCountTime);
7342 if (appData.noChessProgram || gameMode == AnalyzeFile)
7345 if (gameMode != AnalyzeMode) {
7347 if (gameMode != EditGame) return;
7348 ResurrectChessProgram();
7349 SendToProgram("analyze\n", &first);
7350 first.analyzing = TRUE;
7351 /*first.maybeThinking = TRUE;*/
7352 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7353 AnalysisPopUp(_("Analysis"),
7354 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7356 gameMode = AnalyzeFile;
7361 StartAnalysisClock();
7362 GetTimeMark(&lastNodeCountTime);
7371 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7375 if (gameMode == PlayFromGameFile ||
7376 gameMode == TwoMachinesPlay ||
7377 gameMode == Training ||
7378 gameMode == AnalyzeMode ||
7379 gameMode == EndOfGame)
7382 if (gameMode == EditPosition)
7385 if (!WhiteOnMove(currentMove)) {
7386 DisplayError(_("It is not White's turn"), 0);
7390 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7393 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7394 gameMode == AnalyzeFile)
7397 ResurrectChessProgram(); /* in case it isn't running */
7398 gameMode = MachinePlaysWhite;
7402 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7404 if (first.sendName) {
7405 sprintf(buf, "name %s\n", gameInfo.black);
7406 SendToProgram(buf, &first);
7408 if (first.sendTime) {
7409 if (first.useColors) {
7410 SendToProgram("black\n", &first); /*gnu kludge*/
7412 SendTimeRemaining(&first, TRUE);
7414 if (first.useColors) {
7415 SendToProgram("white\ngo\n", &first);
7417 SendToProgram("go\n", &first);
7419 SetMachineThinkingEnables();
7420 first.maybeThinking = TRUE;
7423 if (appData.autoFlipView && !flipView) {
7424 flipView = !flipView;
7425 DrawPosition(FALSE, NULL);
7434 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7438 if (gameMode == PlayFromGameFile ||
7439 gameMode == TwoMachinesPlay ||
7440 gameMode == Training ||
7441 gameMode == AnalyzeMode ||
7442 gameMode == EndOfGame)
7445 if (gameMode == EditPosition)
7448 if (WhiteOnMove(currentMove)) {
7449 DisplayError(_("It is not Black's turn"), 0);
7453 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7456 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7457 gameMode == AnalyzeFile)
7460 ResurrectChessProgram(); /* in case it isn't running */
7461 gameMode = MachinePlaysBlack;
7465 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7467 if (first.sendName) {
7468 sprintf(buf, "name %s\n", gameInfo.white);
7469 SendToProgram(buf, &first);
7471 if (first.sendTime) {
7472 if (first.useColors) {
7473 SendToProgram("white\n", &first); /*gnu kludge*/
7475 SendTimeRemaining(&first, FALSE);
7477 if (first.useColors) {
7478 SendToProgram("black\ngo\n", &first);
7480 SendToProgram("go\n", &first);
7482 SetMachineThinkingEnables();
7483 first.maybeThinking = TRUE;
7486 if (appData.autoFlipView && flipView) {
7487 flipView = !flipView;
7488 DrawPosition(FALSE, NULL);
7494 DisplayTwoMachinesTitle()
7497 if (appData.matchGames > 0) {
7498 if (first.twoMachinesColor[0] == 'w') {
7499 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7500 gameInfo.white, gameInfo.black,
7501 first.matchWins, second.matchWins,
7502 matchGame - 1 - (first.matchWins + second.matchWins));
7504 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7505 gameInfo.white, gameInfo.black,
7506 second.matchWins, first.matchWins,
7507 matchGame - 1 - (first.matchWins + second.matchWins));
7510 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7516 TwoMachinesEvent P((void))
7520 ChessProgramState *onmove;
7522 if (appData.noChessProgram) return;
7525 case TwoMachinesPlay:
7527 case MachinePlaysWhite:
7528 case MachinePlaysBlack:
7529 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7530 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7534 case BeginningOfGame:
7535 case PlayFromGameFile:
7538 if (gameMode != EditGame) return;
7552 forwardMostMove = currentMove;
7553 ResurrectChessProgram(); /* in case first program isn't running */
7555 if (second.pr == NULL) {
7556 StartChessProgram(&second);
7557 if (second.protocolVersion == 1) {
7558 TwoMachinesEventIfReady();
7560 /* kludge: allow timeout for initial "feature" command */
7562 DisplayMessage("", _("Starting second chess program"));
7563 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7567 DisplayMessage("", "");
7568 InitChessProgram(&second);
7569 SendToProgram("force\n", &second);
7570 if (startedFromSetupPosition) {
7571 SendBoard(&second, backwardMostMove);
7573 for (i = backwardMostMove; i < forwardMostMove; i++) {
7574 SendMoveToProgram(i, &second);
7577 gameMode = TwoMachinesPlay;
7581 DisplayTwoMachinesTitle();
7583 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7589 SendToProgram(first.computerString, &first);
7590 if (first.sendName) {
7591 sprintf(buf, "name %s\n", second.tidy);
7592 SendToProgram(buf, &first);
7594 SendToProgram(second.computerString, &second);
7595 if (second.sendName) {
7596 sprintf(buf, "name %s\n", first.tidy);
7597 SendToProgram(buf, &second);
7600 if (!first.sendTime || !second.sendTime) {
7602 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7603 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7605 if (onmove->sendTime) {
7606 if (onmove->useColors) {
7607 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7609 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7611 if (onmove->useColors) {
7612 SendToProgram(onmove->twoMachinesColor, onmove);
7614 SendToProgram("go\n", onmove);
7615 onmove->maybeThinking = TRUE;
7616 SetMachineThinkingEnables();
7624 if (gameMode == Training) {
7625 SetTrainingModeOff();
7626 gameMode = PlayFromGameFile;
7627 DisplayMessage("", _("Training mode off"));
7629 gameMode = Training;
7630 animateTraining = appData.animate;
7632 /* make sure we are not already at the end of the game */
7633 if (currentMove < forwardMostMove) {
7634 SetTrainingModeOn();
7635 DisplayMessage("", _("Training mode on"));
7637 gameMode = PlayFromGameFile;
7638 DisplayError(_("Already at end of game"), 0);
7647 if (!appData.icsActive) return;
7649 case IcsPlayingWhite:
7650 case IcsPlayingBlack:
7653 case BeginningOfGame:
7687 SetTrainingModeOff();
7689 case MachinePlaysWhite:
7690 case MachinePlaysBlack:
7691 case BeginningOfGame:
7692 SendToProgram("force\n", &first);
7693 SetUserThinkingEnables();
7695 case PlayFromGameFile:
7696 (void) StopLoadGameTimer();
7697 if (gameFileFP != NULL) {
7707 SendToProgram("force\n", &first);
7709 case TwoMachinesPlay:
7710 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7711 ResurrectChessProgram();
7712 SetUserThinkingEnables();
7715 ResurrectChessProgram();
7717 case IcsPlayingBlack:
7718 case IcsPlayingWhite:
7719 DisplayError(_("Warning: You are still playing a game"), 0);
7722 DisplayError(_("Warning: You are still observing a game"), 0);
7725 DisplayError(_("Warning: You are still examining a game"), 0);
7736 first.offeredDraw = second.offeredDraw = 0;
7738 if (gameMode == PlayFromGameFile) {
7739 whiteTimeRemaining = timeRemaining[0][currentMove];
7740 blackTimeRemaining = timeRemaining[1][currentMove];
7744 if (gameMode == MachinePlaysWhite ||
7745 gameMode == MachinePlaysBlack ||
7746 gameMode == TwoMachinesPlay ||
7747 gameMode == EndOfGame) {
7748 i = forwardMostMove;
7749 while (i > currentMove) {
7750 SendToProgram("undo\n", &first);
7753 whiteTimeRemaining = timeRemaining[0][currentMove];
7754 blackTimeRemaining = timeRemaining[1][currentMove];
7755 DisplayBothClocks();
7756 if (whiteFlag || blackFlag) {
7757 whiteFlag = blackFlag = 0;
7762 gameMode = EditGame;
7771 if (gameMode == EditPosition) {
7777 if (gameMode != EditGame) return;
7779 gameMode = EditPosition;
7782 if (currentMove > 0)
7783 CopyBoard(boards[0], boards[currentMove]);
7785 blackPlaysFirst = !WhiteOnMove(currentMove);
7787 currentMove = forwardMostMove = backwardMostMove = 0;
7788 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7795 /* icsEngineAnalyze - possible call from other functions */
7796 if (appData.icsEngineAnalyze) {
7797 appData.icsEngineAnalyze = FALSE;
7798 DisplayMessage("","Close ICS engine analyze...");
7800 if (first.analysisSupport && first.analyzing) {
7801 SendToProgram("exit\n", &first);
7802 first.analyzing = FALSE;
7805 thinkOutput[0] = NULLCHAR;
7811 startedFromSetupPosition = TRUE;
7812 InitChessProgram(&first);
7813 SendToProgram("force\n", &first);
7814 if (blackPlaysFirst) {
7815 strcpy(moveList[0], "");
7816 strcpy(parseList[0], "");
7817 currentMove = forwardMostMove = backwardMostMove = 1;
7818 CopyBoard(boards[1], boards[0]);
7820 currentMove = forwardMostMove = backwardMostMove = 0;
7822 SendBoard(&first, forwardMostMove);
7824 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7825 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7826 gameMode = EditGame;
7828 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7831 /* Pause for `ms' milliseconds */
7832 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7842 } while (SubtractTimeMarks(&m2, &m1) < ms);
7845 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7847 SendMultiLineToICS(buf)
7850 char temp[MSG_SIZ+1], *p;
7857 strncpy(temp, buf, len);
7862 if (*p == '\n' || *p == '\r')
7869 SendToPlayer(temp, strlen(temp));
7873 SetWhiteToPlayEvent()
7875 if (gameMode == EditPosition) {
7876 blackPlaysFirst = FALSE;
7877 DisplayBothClocks(); /* works because currentMove is 0 */
7878 } else if (gameMode == IcsExamining) {
7879 SendToICS(ics_prefix);
7880 SendToICS("tomove white\n");
7885 SetBlackToPlayEvent()
7887 if (gameMode == EditPosition) {
7888 blackPlaysFirst = TRUE;
7889 currentMove = 1; /* kludge */
7890 DisplayBothClocks();
7892 } else if (gameMode == IcsExamining) {
7893 SendToICS(ics_prefix);
7894 SendToICS("tomove black\n");
7899 EditPositionMenuEvent(selection, x, y)
7900 ChessSquare selection;
7905 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7907 switch (selection) {
7909 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7910 SendToICS(ics_prefix);
7911 SendToICS("bsetup clear\n");
7912 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7913 SendToICS(ics_prefix);
7914 SendToICS("clearboard\n");
7916 for (x = 0; x < BOARD_SIZE; x++) {
7917 for (y = 0; y < BOARD_SIZE; y++) {
7918 if (gameMode == IcsExamining) {
7919 if (boards[currentMove][y][x] != EmptySquare) {
7920 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7925 boards[0][y][x] = EmptySquare;
7930 if (gameMode == EditPosition) {
7931 DrawPosition(FALSE, boards[0]);
7936 SetWhiteToPlayEvent();
7940 SetBlackToPlayEvent();
7944 if (gameMode == IcsExamining) {
7945 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7948 boards[0][y][x] = EmptySquare;
7949 DrawPosition(FALSE, boards[0]);
7954 if (gameMode == IcsExamining) {
7955 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7956 PieceToChar(selection), 'a' + x, '1' + y);
7959 boards[0][y][x] = selection;
7960 DrawPosition(FALSE, boards[0]);
7968 DropMenuEvent(selection, x, y)
7969 ChessSquare selection;
7975 case IcsPlayingWhite:
7976 case MachinePlaysBlack:
7977 if (!WhiteOnMove(currentMove)) {
7978 DisplayMoveError(_("It is Black's turn"));
7981 moveType = WhiteDrop;
7983 case IcsPlayingBlack:
7984 case MachinePlaysWhite:
7985 if (WhiteOnMove(currentMove)) {
7986 DisplayMoveError(_("It is White's turn"));
7989 moveType = BlackDrop;
7992 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7998 if (moveType == BlackDrop && selection < BlackPawn) {
7999 selection = (ChessSquare) ((int) selection
8000 + (int) BlackPawn - (int) WhitePawn);
8002 if (boards[currentMove][y][x] != EmptySquare) {
8003 DisplayMoveError(_("That square is occupied"));
8007 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8013 /* Accept a pending offer of any kind from opponent */
8015 if (appData.icsActive) {
8016 SendToICS(ics_prefix);
8017 SendToICS("accept\n");
8018 } else if (cmailMsgLoaded) {
8019 if (currentMove == cmailOldMove &&
8020 commentList[cmailOldMove] != NULL &&
8021 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8022 "Black offers a draw" : "White offers a draw")) {
8024 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8025 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8027 DisplayError(_("There is no pending offer on this move"), 0);
8028 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8031 /* Not used for offers from chess program */
8038 /* Decline a pending offer of any kind from opponent */
8040 if (appData.icsActive) {
8041 SendToICS(ics_prefix);
8042 SendToICS("decline\n");
8043 } else if (cmailMsgLoaded) {
8044 if (currentMove == cmailOldMove &&
8045 commentList[cmailOldMove] != NULL &&
8046 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8047 "Black offers a draw" : "White offers a draw")) {
8049 AppendComment(cmailOldMove, "Draw declined");
8050 DisplayComment(cmailOldMove - 1, "Draw declined");
8053 DisplayError(_("There is no pending offer on this move"), 0);
8056 /* Not used for offers from chess program */
8063 /* Issue ICS rematch command */
8064 if (appData.icsActive) {
8065 SendToICS(ics_prefix);
8066 SendToICS("rematch\n");
8073 /* Call your opponent's flag (claim a win on time) */
8074 if (appData.icsActive) {
8075 SendToICS(ics_prefix);
8076 SendToICS("flag\n");
8081 case MachinePlaysWhite:
8084 GameEnds(GameIsDrawn, "Both players ran out of time",
8087 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8089 DisplayError(_("Your opponent is not out of time"), 0);
8092 case MachinePlaysBlack:
8095 GameEnds(GameIsDrawn, "Both players ran out of time",
8098 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8100 DisplayError(_("Your opponent is not out of time"), 0);
8110 /* Offer draw or accept pending draw offer from opponent */
8112 if (appData.icsActive) {
8113 /* Note: tournament rules require draw offers to be
8114 made after you make your move but before you punch
8115 your clock. Currently ICS doesn't let you do that;
8116 instead, you immediately punch your clock after making
8117 a move, but you can offer a draw at any time. */
8119 SendToICS(ics_prefix);
8120 SendToICS("draw\n");
8121 } else if (cmailMsgLoaded) {
8122 if (currentMove == cmailOldMove &&
8123 commentList[cmailOldMove] != NULL &&
8124 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8125 "Black offers a draw" : "White offers a draw")) {
8126 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8127 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8128 } else if (currentMove == cmailOldMove + 1) {
8129 char *offer = WhiteOnMove(cmailOldMove) ?
8130 "White offers a draw" : "Black offers a draw";
8131 AppendComment(currentMove, offer);
8132 DisplayComment(currentMove - 1, offer);
8133 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8135 DisplayError(_("You must make your move before offering a draw"), 0);
8136 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8138 } else if (first.offeredDraw) {
8139 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8141 if (first.sendDrawOffers) {
8142 SendToProgram("draw\n", &first);
8143 userOfferedDraw = TRUE;
8151 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8153 if (appData.icsActive) {
8154 SendToICS(ics_prefix);
8155 SendToICS("adjourn\n");
8157 /* Currently GNU Chess doesn't offer or accept Adjourns */
8165 /* Offer Abort or accept pending Abort offer from opponent */
8167 if (appData.icsActive) {
8168 SendToICS(ics_prefix);
8169 SendToICS("abort\n");
8171 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8178 /* Resign. You can do this even if it's not your turn. */
8180 if (appData.icsActive) {
8181 SendToICS(ics_prefix);
8182 SendToICS("resign\n");
8185 case MachinePlaysWhite:
8186 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8188 case MachinePlaysBlack:
8189 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8192 if (cmailMsgLoaded) {
8194 if (WhiteOnMove(cmailOldMove)) {
8195 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8197 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8199 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8210 StopObservingEvent()
8212 /* Stop observing current games */
8213 SendToICS(ics_prefix);
8214 SendToICS("unobserve\n");
8218 StopExaminingEvent()
8220 /* Stop observing current game */
8221 SendToICS(ics_prefix);
8222 SendToICS("unexamine\n");
8226 ForwardInner(target)
8231 if (appData.debugMode)
8232 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8233 target, currentMove, forwardMostMove);
8235 if (gameMode == EditPosition)
8238 if (gameMode == PlayFromGameFile && !pausing)
8241 if (gameMode == IcsExamining && pausing)
8242 limit = pauseExamForwardMostMove;
8244 limit = forwardMostMove;
8246 if (target > limit) target = limit;
8248 if (target > 0 && moveList[target - 1][0]) {
8249 int fromX, fromY, toX, toY;
8250 toX = moveList[target - 1][2] - 'a';
8251 toY = moveList[target - 1][3] - '1';
8252 if (moveList[target - 1][1] == '@') {
8253 if (appData.highlightLastMove) {
8254 SetHighlights(-1, -1, toX, toY);
8257 fromX = moveList[target - 1][0] - 'a';
8258 fromY = moveList[target - 1][1] - '1';
8259 if (target == currentMove + 1) {
8260 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8262 if (appData.highlightLastMove) {
8263 SetHighlights(fromX, fromY, toX, toY);
8267 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8268 gameMode == Training || gameMode == PlayFromGameFile ||
8269 gameMode == AnalyzeFile) {
8270 while (currentMove < target) {
8271 SendMoveToProgram(currentMove++, &first);
8274 currentMove = target;
8277 if (gameMode == EditGame || gameMode == EndOfGame) {
8278 whiteTimeRemaining = timeRemaining[0][currentMove];
8279 blackTimeRemaining = timeRemaining[1][currentMove];
8281 DisplayBothClocks();
8282 DisplayMove(currentMove - 1);
8283 DrawPosition(FALSE, boards[currentMove]);
8284 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8285 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8286 DisplayComment(currentMove - 1, commentList[currentMove]);
8294 if (gameMode == IcsExamining && !pausing) {
8295 SendToICS(ics_prefix);
8296 SendToICS("forward\n");
8298 ForwardInner(currentMove + 1);
8305 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8306 /* to optimze, we temporarily turn off analysis mode while we feed
8307 * the remaining moves to the engine. Otherwise we get analysis output
8310 if (first.analysisSupport) {
8311 SendToProgram("exit\nforce\n", &first);
8312 first.analyzing = FALSE;
8316 if (gameMode == IcsExamining && !pausing) {
8317 SendToICS(ics_prefix);
8318 SendToICS("forward 999999\n");
8320 ForwardInner(forwardMostMove);
8323 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8324 /* we have fed all the moves, so reactivate analysis mode */
8325 SendToProgram("analyze\n", &first);
8326 first.analyzing = TRUE;
8327 /*first.maybeThinking = TRUE;*/
8328 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8333 BackwardInner(target)
8336 if (appData.debugMode)
8337 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8338 target, currentMove, forwardMostMove);
8340 if (gameMode == EditPosition) return;
8341 if (currentMove <= backwardMostMove) {
8343 DrawPosition(FALSE, boards[currentMove]);
8346 if (gameMode == PlayFromGameFile && !pausing)
8349 if (moveList[target][0]) {
8350 int fromX, fromY, toX, toY;
8351 toX = moveList[target][2] - 'a';
8352 toY = moveList[target][3] - '1';
8353 if (moveList[target][1] == '@') {
8354 if (appData.highlightLastMove) {
8355 SetHighlights(-1, -1, toX, toY);
8358 fromX = moveList[target][0] - 'a';
8359 fromY = moveList[target][1] - '1';
8360 if (target == currentMove - 1) {
8361 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8363 if (appData.highlightLastMove) {
8364 SetHighlights(fromX, fromY, toX, toY);
8368 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8369 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8370 while (currentMove > target) {
8371 SendToProgram("undo\n", &first);
8375 currentMove = target;
8378 if (gameMode == EditGame || gameMode == EndOfGame) {
8379 whiteTimeRemaining = timeRemaining[0][currentMove];
8380 blackTimeRemaining = timeRemaining[1][currentMove];
8382 DisplayBothClocks();
8383 DisplayMove(currentMove - 1);
8384 DrawPosition(FALSE, boards[currentMove]);
8385 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8386 if (commentList[currentMove] != NULL) {
8387 DisplayComment(currentMove - 1, commentList[currentMove]);
8394 if (gameMode == IcsExamining && !pausing) {
8395 SendToICS(ics_prefix);
8396 SendToICS("backward\n");
8398 BackwardInner(currentMove - 1);
8405 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8406 /* to optimze, we temporarily turn off analysis mode while we undo
8407 * all the moves. Otherwise we get analysis output after each undo.
8409 if (first.analysisSupport) {
8410 SendToProgram("exit\nforce\n", &first);
8411 first.analyzing = FALSE;
8415 if (gameMode == IcsExamining && !pausing) {
8416 SendToICS(ics_prefix);
8417 SendToICS("backward 999999\n");
8419 BackwardInner(backwardMostMove);
8422 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8423 /* we have fed all the moves, so reactivate analysis mode */
8424 SendToProgram("analyze\n", &first);
8425 first.analyzing = TRUE;
8426 /*first.maybeThinking = TRUE;*/
8427 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8434 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8435 if (to >= forwardMostMove) to = forwardMostMove;
8436 if (to <= backwardMostMove) to = backwardMostMove;
8437 if (to < currentMove) {
8447 if (gameMode != IcsExamining) {
8448 DisplayError(_("You are not examining a game"), 0);
8452 DisplayError(_("You can't revert while pausing"), 0);
8455 SendToICS(ics_prefix);
8456 SendToICS("revert\n");
8463 case MachinePlaysWhite:
8464 case MachinePlaysBlack:
8465 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8466 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8469 if (forwardMostMove < 2) return;
8470 currentMove = forwardMostMove = forwardMostMove - 2;
8471 whiteTimeRemaining = timeRemaining[0][currentMove];
8472 blackTimeRemaining = timeRemaining[1][currentMove];
8473 DisplayBothClocks();
8474 DisplayMove(currentMove - 1);
8475 ClearHighlights();/*!! could figure this out*/
8476 DrawPosition(FALSE, boards[currentMove]);
8477 SendToProgram("remove\n", &first);
8478 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8481 case BeginningOfGame:
8485 case IcsPlayingWhite:
8486 case IcsPlayingBlack:
8487 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8488 SendToICS(ics_prefix);
8489 SendToICS("takeback 2\n");
8491 SendToICS(ics_prefix);
8492 SendToICS("takeback 1\n");
8501 ChessProgramState *cps;
8504 case MachinePlaysWhite:
8505 if (!WhiteOnMove(forwardMostMove)) {
8506 DisplayError(_("It is your turn"), 0);
8511 case MachinePlaysBlack:
8512 if (WhiteOnMove(forwardMostMove)) {
8513 DisplayError(_("It is your turn"), 0);
8518 case TwoMachinesPlay:
8519 if (WhiteOnMove(forwardMostMove) ==
8520 (first.twoMachinesColor[0] == 'w')) {
8526 case BeginningOfGame:
8530 SendToProgram("?\n", cps);
8537 if (gameMode != EditGame) return;
8544 if (forwardMostMove > currentMove) {
8545 if (gameInfo.resultDetails != NULL) {
8546 free(gameInfo.resultDetails);
8547 gameInfo.resultDetails = NULL;
8548 gameInfo.result = GameUnfinished;
8550 forwardMostMove = currentMove;
8551 HistorySet(parseList, backwardMostMove, forwardMostMove,
8559 if (appData.noChessProgram) return;
8561 case MachinePlaysWhite:
8562 if (WhiteOnMove(forwardMostMove)) {
8563 DisplayError(_("Wait until your turn"), 0);
8567 case BeginningOfGame:
8568 case MachinePlaysBlack:
8569 if (!WhiteOnMove(forwardMostMove)) {
8570 DisplayError(_("Wait until your turn"), 0);
8575 DisplayError(_("No hint available"), 0);
8578 SendToProgram("hint\n", &first);
8579 hintRequested = TRUE;
8585 if (appData.noChessProgram) return;
8587 case MachinePlaysWhite:
8588 if (WhiteOnMove(forwardMostMove)) {
8589 DisplayError(_("Wait until your turn"), 0);
8593 case BeginningOfGame:
8594 case MachinePlaysBlack:
8595 if (!WhiteOnMove(forwardMostMove)) {
8596 DisplayError(_("Wait until your turn"), 0);
8603 case TwoMachinesPlay:
8608 SendToProgram("bk\n", &first);
8609 bookOutput[0] = NULLCHAR;
8610 bookRequested = TRUE;
8616 char *tags = PGNTags(&gameInfo);
8617 TagsPopUp(tags, CmailMsg());
8621 /* end button procedures */
8624 PrintPosition(fp, move)
8630 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8631 for (j = 0; j < BOARD_SIZE; j++) {
8632 char c = PieceToChar(boards[move][i][j]);
8633 fputc(c == 'x' ? '.' : c, fp);
8634 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8637 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8638 fprintf(fp, "white to play\n");
8640 fprintf(fp, "black to play\n");
8647 if (gameInfo.white != NULL) {
8648 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8654 /* Find last component of program's own name, using some heuristics */
8656 TidyProgramName(prog, host, buf)
8657 char *prog, *host, buf[MSG_SIZ];
8660 int local = (strcmp(host, "localhost") == 0);
8661 while (!local && (p = strchr(prog, ';')) != NULL) {
8663 while (*p == ' ') p++;
8666 if (*prog == '"' || *prog == '\'') {
8667 q = strchr(prog + 1, *prog);
8669 q = strchr(prog, ' ');
8671 if (q == NULL) q = prog + strlen(prog);
8673 while (p >= prog && *p != '/' && *p != '\\') p--;
8675 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8676 memcpy(buf, p, q - p);
8677 buf[q - p] = NULLCHAR;
8685 TimeControlTagValue()
8688 if (!appData.clockMode) {
8690 } else if (movesPerSession > 0) {
8691 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8692 } else if (timeIncrement == 0) {
8693 sprintf(buf, "%ld", timeControl/1000);
8695 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8697 return StrSave(buf);
8703 /* This routine is used only for certain modes */
8704 VariantClass v = gameInfo.variant;
8705 ClearGameInfo(&gameInfo);
8706 gameInfo.variant = v;
8709 case MachinePlaysWhite:
8710 gameInfo.event = StrSave("Computer chess game");
8711 gameInfo.site = StrSave(HostName());
8712 gameInfo.date = PGNDate();
8713 gameInfo.round = StrSave("-");
8714 gameInfo.white = StrSave(first.tidy);
8715 gameInfo.black = StrSave(UserName());
8716 gameInfo.timeControl = TimeControlTagValue();
8719 case MachinePlaysBlack:
8720 gameInfo.event = StrSave("Computer chess game");
8721 gameInfo.site = StrSave(HostName());
8722 gameInfo.date = PGNDate();
8723 gameInfo.round = StrSave("-");
8724 gameInfo.white = StrSave(UserName());
8725 gameInfo.black = StrSave(first.tidy);
8726 gameInfo.timeControl = TimeControlTagValue();
8729 case TwoMachinesPlay:
8730 gameInfo.event = StrSave("Computer chess game");
8731 gameInfo.site = StrSave(HostName());
8732 gameInfo.date = PGNDate();
8733 if (matchGame > 0) {
8735 sprintf(buf, "%d", matchGame);
8736 gameInfo.round = StrSave(buf);
8738 gameInfo.round = StrSave("-");
8740 if (first.twoMachinesColor[0] == 'w') {
8741 gameInfo.white = StrSave(first.tidy);
8742 gameInfo.black = StrSave(second.tidy);
8744 gameInfo.white = StrSave(second.tidy);
8745 gameInfo.black = StrSave(first.tidy);
8747 gameInfo.timeControl = TimeControlTagValue();
8751 gameInfo.event = StrSave("Edited game");
8752 gameInfo.site = StrSave(HostName());
8753 gameInfo.date = PGNDate();
8754 gameInfo.round = StrSave("-");
8755 gameInfo.white = StrSave("-");
8756 gameInfo.black = StrSave("-");
8760 gameInfo.event = StrSave("Edited position");
8761 gameInfo.site = StrSave(HostName());
8762 gameInfo.date = PGNDate();
8763 gameInfo.round = StrSave("-");
8764 gameInfo.white = StrSave("-");
8765 gameInfo.black = StrSave("-");
8768 case IcsPlayingWhite:
8769 case IcsPlayingBlack:
8774 case PlayFromGameFile:
8775 gameInfo.event = StrSave("Game from non-PGN file");
8776 gameInfo.site = StrSave(HostName());
8777 gameInfo.date = PGNDate();
8778 gameInfo.round = StrSave("-");
8779 gameInfo.white = StrSave("?");
8780 gameInfo.black = StrSave("?");
8789 ReplaceComment(index, text)
8795 while (*text == '\n') text++;
8797 while (len > 0 && text[len - 1] == '\n') len--;
8799 if (commentList[index] != NULL)
8800 free(commentList[index]);
8803 commentList[index] = NULL;
8806 commentList[index] = (char *) malloc(len + 2);
8807 strncpy(commentList[index], text, len);
8808 commentList[index][len] = '\n';
8809 commentList[index][len + 1] = NULLCHAR;
8822 if (ch == '\r') continue;
8824 } while (ch != '\0');
8828 AppendComment(index, text)
8836 while (*text == '\n') text++;
8838 while (len > 0 && text[len - 1] == '\n') len--;
8840 if (len == 0) return;
8842 if (commentList[index] != NULL) {
8843 old = commentList[index];
8844 oldlen = strlen(old);
8845 commentList[index] = (char *) malloc(oldlen + len + 2);
8846 strcpy(commentList[index], old);
8848 strncpy(&commentList[index][oldlen], text, len);
8849 commentList[index][oldlen + len] = '\n';
8850 commentList[index][oldlen + len + 1] = NULLCHAR;
8852 commentList[index] = (char *) malloc(len + 2);
8853 strncpy(commentList[index], text, len);
8854 commentList[index][len] = '\n';
8855 commentList[index][len + 1] = NULLCHAR;
8860 SendToProgram(message, cps)
8862 ChessProgramState *cps;
8864 int count, outCount, error;
8867 if (cps->pr == NULL) return;
8870 if (appData.debugMode) {
8873 fprintf(debugFP, "%ld >%-6s: %s",
8874 SubtractTimeMarks(&now, &programStartTime),
8875 cps->which, message);
8878 count = strlen(message);
8879 outCount = OutputToProcess(cps->pr, message, count, &error);
8880 if (outCount < count && !exiting) {
8881 sprintf(buf, _("Error writing to %s chess program"), cps->which);
8882 DisplayFatalError(buf, error, 1);
8887 ReceiveFromProgram(isr, closure, message, count, error)
8896 ChessProgramState *cps = (ChessProgramState *)closure;
8898 if (isr != cps->isr) return; /* Killed intentionally */
8902 _("Error: %s chess program (%s) exited unexpectedly"),
8903 cps->which, cps->program);
8904 RemoveInputSource(cps->isr);
8905 DisplayFatalError(buf, 0, 1);
8908 _("Error reading from %s chess program (%s)"),
8909 cps->which, cps->program);
8910 RemoveInputSource(cps->isr);
8911 DisplayFatalError(buf, error, 1);
8913 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8917 if ((end_str = strchr(message, '\r')) != NULL)
8918 *end_str = NULLCHAR;
8919 if ((end_str = strchr(message, '\n')) != NULL)
8920 *end_str = NULLCHAR;
8922 if (appData.debugMode) {
8925 fprintf(debugFP, "%ld <%-6s: %s\n",
8926 SubtractTimeMarks(&now, &programStartTime),
8927 cps->which, message);
8929 /* if icsEngineAnalyze is active we block all
8930 whisper and kibitz output, because nobody want
8933 if (appData.icsEngineAnalyze) {
8934 if (strstr(message, "whisper") != NULL ||
8935 strstr(message, "kibitz") != NULL ||
8936 strstr(message, "tellics") != NULL) return;
8937 HandleMachineMove(message, cps);
8939 HandleMachineMove(message, cps);
8945 SendTimeControl(cps, mps, tc, inc, sd, st)
8946 ChessProgramState *cps;
8947 int mps, inc, sd, st;
8951 int seconds = (tc / 1000) % 60;
8954 /* Set exact time per move, normally using st command */
8955 if (cps->stKludge) {
8956 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8959 sprintf(buf, "level 1 %d\n", st/60);
8961 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8964 sprintf(buf, "st %d\n", st);
8967 /* Set conventional or incremental time control, using level command */
8969 /* Note old gnuchess bug -- minutes:seconds used to not work.
8970 Fixed in later versions, but still avoid :seconds
8971 when seconds is 0. */
8972 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8974 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8978 SendToProgram(buf, cps);
8980 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8981 /* Orthogonally, limit search to given depth */
8983 if (cps->sdKludge) {
8984 sprintf(buf, "depth\n%d\n", sd);
8986 sprintf(buf, "sd %d\n", sd);
8988 SendToProgram(buf, cps);
8993 SendTimeRemaining(cps, machineWhite)
8994 ChessProgramState *cps;
8995 int /*boolean*/ machineWhite;
8997 char message[MSG_SIZ];
9000 /* Note: this routine must be called when the clocks are stopped
9001 or when they have *just* been set or switched; otherwise
9002 it will be off by the time since the current tick started.
9005 time = whiteTimeRemaining / 10;
9006 otime = blackTimeRemaining / 10;
9008 time = blackTimeRemaining / 10;
9009 otime = whiteTimeRemaining / 10;
9011 if (time <= 0) time = 1;
9012 if (otime <= 0) otime = 1;
9014 sprintf(message, "time %ld\notim %ld\n", time, otime);
9015 SendToProgram(message, cps);
9019 BoolFeature(p, name, loc, cps)
9023 ChessProgramState *cps;
9026 int len = strlen(name);
9028 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9030 sscanf(*p, "%d", &val);
9032 while (**p && **p != ' ') (*p)++;
9033 sprintf(buf, "accepted %s\n", name);
9034 SendToProgram(buf, cps);
9041 IntFeature(p, name, loc, cps)
9045 ChessProgramState *cps;
9048 int len = strlen(name);
9049 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9051 sscanf(*p, "%d", loc);
9052 while (**p && **p != ' ') (*p)++;
9053 sprintf(buf, "accepted %s\n", name);
9054 SendToProgram(buf, cps);
9061 StringFeature(p, name, loc, cps)
9065 ChessProgramState *cps;
9068 int len = strlen(name);
9069 if (strncmp((*p), name, len) == 0
9070 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9072 sscanf(*p, "%[^\"]", loc);
9073 while (**p && **p != '\"') (*p)++;
9074 if (**p == '\"') (*p)++;
9075 sprintf(buf, "accepted %s\n", name);
9076 SendToProgram(buf, cps);
9083 FeatureDone(cps, val)
9084 ChessProgramState* cps;
9087 DelayedEventCallback cb = GetDelayedEvent();
9088 if ((cb == InitBackEnd3 && cps == &first) ||
9089 (cb == TwoMachinesEventIfReady && cps == &second)) {
9090 CancelDelayedEvent();
9091 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9093 cps->initDone = val;
9096 /* Parse feature command from engine */
9098 ParseFeatures(args, cps)
9100 ChessProgramState *cps;
9108 while (*p == ' ') p++;
9109 if (*p == NULLCHAR) return;
9111 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9112 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9113 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9114 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9115 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9116 if (BoolFeature(&p, "reuse", &val, cps)) {
9117 /* Engine can disable reuse, but can't enable it if user said no */
9118 if (!val) cps->reuse = FALSE;
9121 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9122 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9123 if (gameMode == TwoMachinesPlay) {
9124 DisplayTwoMachinesTitle();
9130 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9131 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9132 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9133 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9134 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9135 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9136 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9137 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9138 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9139 if (IntFeature(&p, "done", &val, cps)) {
9140 FeatureDone(cps, val);
9144 /* unknown feature: complain and skip */
9146 while (*q && *q != '=') q++;
9147 sprintf(buf, "rejected %.*s\n", q-p, p);
9148 SendToProgram(buf, cps);
9154 while (*p && *p != '\"') p++;
9155 if (*p == '\"') p++;
9157 while (*p && *p != ' ') p++;
9165 PeriodicUpdatesEvent(newState)
9168 if (newState == appData.periodicUpdates)
9171 appData.periodicUpdates=newState;
9173 /* Display type changes, so update it now */
9176 /* Get the ball rolling again... */
9178 AnalysisPeriodicEvent(1);
9179 StartAnalysisClock();
9184 PonderNextMoveEvent(newState)
9187 if (newState == appData.ponderNextMove) return;
9188 if (gameMode == EditPosition) EditPositionDone();
9190 SendToProgram("hard\n", &first);
9191 if (gameMode == TwoMachinesPlay) {
9192 SendToProgram("hard\n", &second);
9195 SendToProgram("easy\n", &first);
9196 thinkOutput[0] = NULLCHAR;
9197 if (gameMode == TwoMachinesPlay) {
9198 SendToProgram("easy\n", &second);
9201 appData.ponderNextMove = newState;
9205 ShowThinkingEvent(newState)
9208 if (newState == appData.showThinking) return;
9209 if (gameMode == EditPosition) EditPositionDone();
9211 SendToProgram("post\n", &first);
9212 if (gameMode == TwoMachinesPlay) {
9213 SendToProgram("post\n", &second);
9216 SendToProgram("nopost\n", &first);
9217 thinkOutput[0] = NULLCHAR;
9218 if (gameMode == TwoMachinesPlay) {
9219 SendToProgram("nopost\n", &second);
9222 appData.showThinking = newState;
9226 AskQuestionEvent(title, question, replyPrefix, which)
9227 char *title; char *question; char *replyPrefix; char *which;
9229 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9230 if (pr == NoProc) return;
9231 AskQuestion(title, question, replyPrefix, pr);
9235 DisplayMove(moveNumber)
9238 char message[MSG_SIZ];
9240 char cpThinkOutput[MSG_SIZ];
9242 if (moveNumber == forwardMostMove - 1 ||
9243 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9245 strcpy(cpThinkOutput, thinkOutput);
9246 if (strchr(cpThinkOutput, '\n'))
9247 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9249 *cpThinkOutput = NULLCHAR;
9252 if (moveNumber == forwardMostMove - 1 &&
9253 gameInfo.resultDetails != NULL) {
9254 if (gameInfo.resultDetails[0] == NULLCHAR) {
9255 sprintf(res, " %s", PGNResult(gameInfo.result));
9257 sprintf(res, " {%s} %s",
9258 gameInfo.resultDetails, PGNResult(gameInfo.result));
9264 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9265 DisplayMessage(res, cpThinkOutput);
9267 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9268 WhiteOnMove(moveNumber) ? " " : ".. ",
9269 parseList[moveNumber], res);
9270 DisplayMessage(message, cpThinkOutput);
9275 DisplayAnalysisText(text)
9280 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
9281 || appData.icsEngineAnalyze) {
9282 sprintf(buf, "Analysis (%s)", first.tidy);
9283 AnalysisPopUp(buf, text);
9291 while (*str && isspace(*str)) ++str;
9292 while (*str && !isspace(*str)) ++str;
9293 if (!*str) return 1;
9294 while (*str && isspace(*str)) ++str;
9295 if (!*str) return 1;
9304 static char *xtra[] = { "", " (--)", " (++)" };
9307 if (programStats.time == 0) {
9308 programStats.time = 1;
9311 if (programStats.got_only_move) {
9312 strcpy(buf, programStats.movelist);
9314 nps = (u64ToDouble(programStats.nodes) /
9315 ((double)programStats.time /100.0));
9317 cs = programStats.time % 100;
9318 s = programStats.time / 100;
9324 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9325 if (programStats.move_name[0] != NULLCHAR) {
9326 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9328 programStats.nr_moves-programStats.moves_left,
9329 programStats.nr_moves, programStats.move_name,
9330 ((float)programStats.score)/100.0, programStats.movelist,
9331 only_one_move(programStats.movelist)?
9332 xtra[programStats.got_fail] : "",
9333 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9335 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9337 programStats.nr_moves-programStats.moves_left,
9338 programStats.nr_moves, ((float)programStats.score)/100.0,
9339 programStats.movelist,
9340 only_one_move(programStats.movelist)?
9341 xtra[programStats.got_fail] : "",
9342 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9345 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9347 ((float)programStats.score)/100.0,
9348 programStats.movelist,
9349 only_one_move(programStats.movelist)?
9350 xtra[programStats.got_fail] : "",
9351 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9354 DisplayAnalysisText(buf);
9358 DisplayComment(moveNumber, text)
9362 char title[MSG_SIZ];
9364 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9365 strcpy(title, "Comment");
9367 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9368 WhiteOnMove(moveNumber) ? " " : ".. ",
9369 parseList[moveNumber]);
9372 CommentPopUp(title, text);
9375 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9376 * might be busy thinking or pondering. It can be omitted if your
9377 * gnuchess is configured to stop thinking immediately on any user
9378 * input. However, that gnuchess feature depends on the FIONREAD
9379 * ioctl, which does not work properly on some flavors of Unix.
9383 ChessProgramState *cps;
9386 if (!cps->useSigint) return;
9387 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9389 case MachinePlaysWhite:
9390 case MachinePlaysBlack:
9391 case TwoMachinesPlay:
9392 case IcsPlayingWhite:
9393 case IcsPlayingBlack:
9396 /* Skip if we know it isn't thinking */
9397 if (!cps->maybeThinking) return;
9398 if (appData.debugMode)
9399 fprintf(debugFP, "Interrupting %s\n", cps->which);
9400 InterruptChildProcess(cps->pr);
9401 cps->maybeThinking = FALSE;
9406 #endif /*ATTENTION*/
9412 if (whiteTimeRemaining <= 0) {
9415 if (appData.icsActive) {
9416 if (appData.autoCallFlag &&
9417 gameMode == IcsPlayingBlack && !blackFlag) {
9418 SendToICS(ics_prefix);
9419 SendToICS("flag\n");
9423 DisplayTitle(_("Both flags fell"));
9425 DisplayTitle(_("White's flag fell"));
9426 if (appData.autoCallFlag) {
9427 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9434 if (blackTimeRemaining <= 0) {
9437 if (appData.icsActive) {
9438 if (appData.autoCallFlag &&
9439 gameMode == IcsPlayingWhite && !whiteFlag) {
9440 SendToICS(ics_prefix);
9441 SendToICS("flag\n");
9445 DisplayTitle(_("Both flags fell"));
9447 DisplayTitle(_("Black's flag fell"));
9448 if (appData.autoCallFlag) {
9449 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9462 if (!appData.clockMode || appData.icsActive ||
9463 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9465 if (timeIncrement >= 0) {
9466 if (WhiteOnMove(forwardMostMove)) {
9467 blackTimeRemaining += timeIncrement;
9469 whiteTimeRemaining += timeIncrement;
9473 * add time to clocks when time control is achieved
9475 if (movesPerSession) {
9476 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9478 /* White made time control */
9479 whiteTimeRemaining += timeControl;
9482 /* Black made time control */
9483 blackTimeRemaining += timeControl;
9494 int wom = gameMode == EditPosition ?
9495 !blackPlaysFirst : WhiteOnMove(currentMove);
9496 DisplayWhiteClock(whiteTimeRemaining, wom);
9497 DisplayBlackClock(blackTimeRemaining, !wom);
9501 /* Timekeeping seems to be a portability nightmare. I think everyone
9502 has ftime(), but I'm really not sure, so I'm including some ifdefs
9503 to use other calls if you don't. Clocks will be less accurate if
9504 you have neither ftime nor gettimeofday.
9507 /* Get the current time as a TimeMark */
9512 #if HAVE_GETTIMEOFDAY
9514 struct timeval timeVal;
9515 struct timezone timeZone;
9517 gettimeofday(&timeVal, &timeZone);
9518 tm->sec = (long) timeVal.tv_sec;
9519 tm->ms = (int) (timeVal.tv_usec / 1000L);
9521 #else /*!HAVE_GETTIMEOFDAY*/
9524 #include <sys/timeb.h>
9528 tm->sec = (long) timeB.time;
9529 tm->ms = (int) timeB.millitm;
9531 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9532 tm->sec = (long) time(NULL);
9538 /* Return the difference in milliseconds between two
9539 time marks. We assume the difference will fit in a long!
9542 SubtractTimeMarks(tm2, tm1)
9543 TimeMark *tm2, *tm1;
9545 return 1000L*(tm2->sec - tm1->sec) +
9546 (long) (tm2->ms - tm1->ms);
9551 * Code to manage the game clocks.
9553 * In tournament play, black starts the clock and then white makes a move.
9554 * We give the human user a slight advantage if he is playing white---the
9555 * clocks don't run until he makes his first move, so it takes zero time.
9556 * Also, we don't account for network lag, so we could get out of sync
9557 * with GNU Chess's clock -- but then, referees are always right.
9560 static TimeMark tickStartTM;
9561 static long intendedTickLength;
9564 NextTickLength(timeRemaining)
9567 long nominalTickLength, nextTickLength;
9569 if (timeRemaining > 0L && timeRemaining <= 10000L)
9570 nominalTickLength = 100L;
9572 nominalTickLength = 1000L;
9573 nextTickLength = timeRemaining % nominalTickLength;
9574 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9576 return nextTickLength;
9579 /* Stop clocks and reset to a fresh time control */
9583 (void) StopClockTimer();
9584 if (appData.icsActive) {
9585 whiteTimeRemaining = blackTimeRemaining = 0;
9587 whiteTimeRemaining = blackTimeRemaining = timeControl;
9589 if (whiteFlag || blackFlag) {
9591 whiteFlag = blackFlag = FALSE;
9593 DisplayBothClocks();
9596 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9598 /* Decrement running clock by amount of time that has passed */
9603 long lastTickLength, fudge;
9606 if (!appData.clockMode) return;
9607 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9611 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9613 /* Fudge if we woke up a little too soon */
9614 fudge = intendedTickLength - lastTickLength;
9615 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9617 if (WhiteOnMove(forwardMostMove)) {
9618 timeRemaining = whiteTimeRemaining -= lastTickLength;
9619 DisplayWhiteClock(whiteTimeRemaining - fudge,
9620 WhiteOnMove(currentMove));
9622 timeRemaining = blackTimeRemaining -= lastTickLength;
9623 DisplayBlackClock(blackTimeRemaining - fudge,
9624 !WhiteOnMove(currentMove));
9627 if (CheckFlags()) return;
9630 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9631 StartClockTimer(intendedTickLength);
9633 /* if the time remaining has fallen below the alarm threshold, sound the
9634 * alarm. if the alarm has sounded and (due to a takeback or time control
9635 * with increment) the time remaining has increased to a level above the
9636 * threshold, reset the alarm so it can sound again.
9639 if (appData.icsActive && appData.icsAlarm) {
9641 /* make sure we are dealing with the user's clock */
9642 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9643 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9646 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9647 alarmSounded = FALSE;
9648 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9650 alarmSounded = TRUE;
9656 /* A player has just moved, so stop the previously running
9657 clock and (if in clock mode) start the other one.
9658 We redisplay both clocks in case we're in ICS mode, because
9659 ICS gives us an update to both clocks after every move.
9660 Note that this routine is called *after* forwardMostMove
9661 is updated, so the last fractional tick must be subtracted
9662 from the color that is *not* on move now.
9667 long lastTickLength;
9669 int flagged = FALSE;
9673 if (StopClockTimer() && appData.clockMode) {
9674 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9675 if (WhiteOnMove(forwardMostMove)) {
9676 blackTimeRemaining -= lastTickLength;
9678 whiteTimeRemaining -= lastTickLength;
9680 flagged = CheckFlags();
9684 if (flagged || !appData.clockMode) return;
9687 case MachinePlaysBlack:
9688 case MachinePlaysWhite:
9689 case BeginningOfGame:
9690 if (pausing) return;
9694 case PlayFromGameFile:
9703 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9704 whiteTimeRemaining : blackTimeRemaining);
9705 StartClockTimer(intendedTickLength);
9709 /* Stop both clocks */
9713 long lastTickLength;
9716 if (!StopClockTimer()) return;
9717 if (!appData.clockMode) return;
9721 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9722 if (WhiteOnMove(forwardMostMove)) {
9723 whiteTimeRemaining -= lastTickLength;
9724 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9726 blackTimeRemaining -= lastTickLength;
9727 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9732 /* Start clock of player on move. Time may have been reset, so
9733 if clock is already running, stop and restart it. */
9737 (void) StopClockTimer(); /* in case it was running already */
9738 DisplayBothClocks();
9739 if (CheckFlags()) return;
9741 if (!appData.clockMode) return;
9742 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9744 GetTimeMark(&tickStartTM);
9745 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9746 whiteTimeRemaining : blackTimeRemaining);
9747 StartClockTimer(intendedTickLength);
9754 long second, minute, hour, day;
9756 static char buf[32];
9758 if (ms > 0 && ms <= 9900) {
9759 /* convert milliseconds to tenths, rounding up */
9760 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9762 sprintf(buf, " %03.1f ", tenths/10.0);
9766 /* convert milliseconds to seconds, rounding up */
9767 /* use floating point to avoid strangeness of integer division
9768 with negative dividends on many machines */
9769 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9776 day = second / (60 * 60 * 24);
9777 second = second % (60 * 60 * 24);
9778 hour = second / (60 * 60);
9779 second = second % (60 * 60);
9780 minute = second / 60;
9781 second = second % 60;
9784 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9785 sign, day, hour, minute, second);
9787 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9789 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9796 * This is necessary because some C libraries aren't ANSI C compliant yet.
9799 StrStr(string, match)
9800 char *string, *match;
9804 length = strlen(match);
9806 for (i = strlen(string) - length; i >= 0; i--, string++)
9807 if (!strncmp(match, string, length))
9814 StrCaseStr(string, match)
9815 char *string, *match;
9819 length = strlen(match);
9821 for (i = strlen(string) - length; i >= 0; i--, string++) {
9822 for (j = 0; j < length; j++) {
9823 if (ToLower(match[j]) != ToLower(string[j]))
9826 if (j == length) return string;
9840 c1 = ToLower(*s1++);
9841 c2 = ToLower(*s2++);
9842 if (c1 > c2) return 1;
9843 if (c1 < c2) return -1;
9844 if (c1 == NULLCHAR) return 0;
9853 return isupper(c) ? tolower(c) : c;
9861 return islower(c) ? toupper(c) : c;
9863 #endif /* !_amigados */
9871 if ((ret = (char *) malloc(strlen(s) + 1))) {
9878 StrSavePtr(s, savePtr)
9884 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9885 strcpy(*savePtr, s);
9897 clock = time((time_t *)NULL);
9898 tm = localtime(&clock);
9899 sprintf(buf, "%04d.%02d.%02d",
9900 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9901 return StrSave(buf);
9909 int i, j, fromX, fromY, toX, toY;
9915 whiteToPlay = (gameMode == EditPosition) ?
9916 !blackPlaysFirst : (move % 2 == 0);
9919 /* Piece placement data */
9920 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9922 for (j = 0; j < BOARD_SIZE; j++) {
9923 if (boards[move][i][j] == EmptySquare) {
9926 if (emptycount > 0) {
9927 *p++ = '0' + emptycount;
9930 *p++ = PieceToChar(boards[move][i][j]);
9933 if (emptycount > 0) {
9934 *p++ = '0' + emptycount;
9942 *p++ = whiteToPlay ? 'w' : 'b';
9945 /* !!We don't keep track of castling availability, so fake it */
9947 if (boards[move][0][4] == WhiteKing) {
9948 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9949 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9951 if (boards[move][7][4] == BlackKing) {
9952 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9953 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9955 if (q == p) *p++ = '-';
9958 /* En passant target square */
9959 if (move > backwardMostMove) {
9960 fromX = moveList[move - 1][0] - 'a';
9961 fromY = moveList[move - 1][1] - '1';
9962 toX = moveList[move - 1][2] - 'a';
9963 toY = moveList[move - 1][3] - '1';
9964 if (fromY == (whiteToPlay ? 6 : 1) &&
9965 toY == (whiteToPlay ? 4 : 3) &&
9966 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9968 /* 2-square pawn move just happened */
9970 *p++ = whiteToPlay ? '6' : '3';
9978 /* !!We don't keep track of halfmove clock for 50-move rule */
9982 /* Fullmove number */
9983 sprintf(p, "%d", (move / 2) + 1);
9985 return StrSave(buf);
9989 ParseFEN(board, blackPlaysFirst, fen)
9991 int *blackPlaysFirst;
10000 /* Piece placement data */
10001 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10004 if (*p == '/' || *p == ' ') {
10005 if (*p == '/') p++;
10006 emptycount = BOARD_SIZE - j;
10007 while (emptycount--) board[i][j++] = EmptySquare;
10009 } else if (isdigit(*p)) {
10010 emptycount = *p++ - '0';
10011 if (j + emptycount > BOARD_SIZE) return FALSE;
10012 while (emptycount--) board[i][j++] = EmptySquare;
10013 } else if (isalpha(*p)) {
10014 if (j >= BOARD_SIZE) return FALSE;
10015 board[i][j++] = CharToPiece(*p++);
10021 while (*p == '/' || *p == ' ') p++;
10026 *blackPlaysFirst = FALSE;
10029 *blackPlaysFirst = TRUE;
10034 /* !!We ignore the rest of the FEN notation */
10039 EditPositionPasteFEN(char *fen)
10042 Board initial_position;
10044 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10045 DisplayError(_("Bad FEN position in clipboard"), 0);
10048 int savedBlackPlaysFirst = blackPlaysFirst;
10049 EditPositionEvent();
10050 blackPlaysFirst = savedBlackPlaysFirst;
10051 CopyBoard(boards[0], initial_position);
10052 EditPositionDone();
10053 DisplayBothClocks();
10054 DrawPosition(FALSE, boards[currentMove]);