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;
4401 /* icsEngineAnalyze */
4403 if (!appData.icsEngineAnalyze) ignore = TRUE;
4405 case TwoMachinesPlay:
4406 if ((cps->twoMachinesColor[0] == 'w') !=
4407 WhiteOnMove(forwardMostMove)) {
4418 if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
4419 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4421 if (plyext != ' ' && plyext != '\t') {
4424 programStats.depth = plylev;
4425 programStats.nodes = nodes;
4426 programStats.time = time;
4427 programStats.score = curscore;
4428 programStats.got_only_move = 0;
4430 /* Buffer overflow protection */
4431 if (buf1[0] != NULLCHAR) {
4432 if (strlen(buf1) >= sizeof(programStats.movelist)
4433 && appData.debugMode) {
4435 "PV is too long; using the first %d bytes.\n",
4436 sizeof(programStats.movelist) - 1);
4438 strncpy(programStats.movelist, buf1,
4439 sizeof(programStats.movelist));
4440 programStats.movelist[sizeof(programStats.movelist) - 1]
4443 sprintf(programStats.movelist, " no PV\n");
4446 if (programStats.seen_stat) {
4447 programStats.ok_to_send = 1;
4450 if (strchr(programStats.movelist, '(') != NULL) {
4451 programStats.line_is_book = 1;
4452 programStats.nr_moves = 0;
4453 programStats.moves_left = 0;
4455 programStats.line_is_book = 0;
4458 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4460 (gameMode == TwoMachinesPlay ?
4461 ToUpper(cps->twoMachinesColor[0]) : ' '),
4462 ((double) curscore) / 100.0,
4463 prefixHint ? lastHint : "",
4464 prefixHint ? " " : "", buf1);
4466 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
4467 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4468 DisplayMove(currentMove - 1);
4473 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4474 /* crafty (9.25+) says "(only move) <move>"
4475 * if there is only 1 legal move
4477 sscanf(p, "(only move) %s", buf1);
4478 sprintf(thinkOutput, "%s (only move)", buf1);
4479 sprintf(programStats.movelist, "%s (only move)", buf1);
4480 programStats.depth = 1;
4481 programStats.nr_moves = 1;
4482 programStats.moves_left = 1;
4483 programStats.nodes = 1;
4484 programStats.time = 1;
4485 programStats.got_only_move = 1;
4487 /* Not really, but we also use this member to
4488 mean "line isn't going to change" (Crafty
4489 isn't searching, so stats won't change) */
4490 programStats.line_is_book = 1;
4492 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4493 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4494 DisplayMove(currentMove - 1);
4498 } else if (sscanf(message,"stat01: %d" u64Display "%d %d %d %s",
4499 &time, &nodes, &plylev, &mvleft,
4500 &mvtot, mvname) >= 5) {
4501 /* The stat01: line is from Crafty (9.29+) in response
4502 to the "." command */
4503 programStats.seen_stat = 1;
4504 cps->maybeThinking = TRUE;
4506 if (programStats.got_only_move || !appData.periodicUpdates)
4509 programStats.depth = plylev;
4510 programStats.time = time;
4511 programStats.nodes = nodes;
4512 programStats.moves_left = mvleft;
4513 programStats.nr_moves = mvtot;
4514 strcpy(programStats.move_name, mvname);
4515 programStats.ok_to_send = 1;
4519 } else if (strncmp(message,"++",2) == 0) {
4520 /* Crafty 9.29+ outputs this */
4521 programStats.got_fail = 2;
4524 } else if (strncmp(message,"--",2) == 0) {
4525 /* Crafty 9.29+ outputs this */
4526 programStats.got_fail = 1;
4529 } else if (thinkOutput[0] != NULLCHAR &&
4530 strncmp(message, " ", 4) == 0) {
4532 while (*p && *p == ' ') p++;
4533 strcat(thinkOutput, " ");
4534 strcat(thinkOutput, p);
4535 strcat(programStats.movelist, " ");
4536 strcat(programStats.movelist, p);
4537 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4538 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4539 DisplayMove(currentMove - 1);
4549 /* Parse a game score from the character string "game", and
4550 record it as the history of the current game. The game
4551 score is NOT assumed to start from the standard position.
4552 The display is not updated in any way.
4555 ParseGameHistory(game)
4559 int fromX, fromY, toX, toY, boardIndex;
4564 if (appData.debugMode)
4565 fprintf(debugFP, "Parsing game history: %s\n", game);
4567 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4568 gameInfo.site = StrSave(appData.icsHost);
4569 gameInfo.date = PGNDate();
4570 gameInfo.round = StrSave("-");
4572 /* Parse out names of players */
4573 while (*game == ' ') game++;
4575 while (*game != ' ') *p++ = *game++;
4577 gameInfo.white = StrSave(buf);
4578 while (*game == ' ') game++;
4580 while (*game != ' ' && *game != '\n') *p++ = *game++;
4582 gameInfo.black = StrSave(buf);
4585 boardIndex = blackPlaysFirst ? 1 : 0;
4588 yyboardindex = boardIndex;
4589 moveType = (ChessMove) yylex();
4591 case WhitePromotionQueen:
4592 case BlackPromotionQueen:
4593 case WhitePromotionRook:
4594 case BlackPromotionRook:
4595 case WhitePromotionBishop:
4596 case BlackPromotionBishop:
4597 case WhitePromotionKnight:
4598 case BlackPromotionKnight:
4599 case WhitePromotionKing:
4600 case BlackPromotionKing:
4602 case WhiteCapturesEnPassant:
4603 case BlackCapturesEnPassant:
4604 case WhiteKingSideCastle:
4605 case WhiteQueenSideCastle:
4606 case BlackKingSideCastle:
4607 case BlackQueenSideCastle:
4608 case WhiteKingSideCastleWild:
4609 case WhiteQueenSideCastleWild:
4610 case BlackKingSideCastleWild:
4611 case BlackQueenSideCastleWild:
4612 case IllegalMove: /* maybe suicide chess, etc. */
4613 fromX = currentMoveString[0] - 'a';
4614 fromY = currentMoveString[1] - '1';
4615 toX = currentMoveString[2] - 'a';
4616 toY = currentMoveString[3] - '1';
4617 promoChar = currentMoveString[4];
4621 fromX = moveType == WhiteDrop ?
4622 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4623 (int) CharToPiece(ToLower(currentMoveString[0]));
4625 toX = currentMoveString[2] - 'a';
4626 toY = currentMoveString[3] - '1';
4627 promoChar = NULLCHAR;
4631 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4632 DisplayError(buf, 0);
4634 case ImpossibleMove:
4636 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4637 DisplayError(buf, 0);
4639 case (ChessMove) 0: /* end of file */
4640 if (boardIndex < backwardMostMove) {
4641 /* Oops, gap. How did that happen? */
4642 DisplayError(_("Gap in move list"), 0);
4645 backwardMostMove = blackPlaysFirst ? 1 : 0;
4646 if (boardIndex > forwardMostMove) {
4647 forwardMostMove = boardIndex;
4651 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4652 strcat(parseList[boardIndex-1], " ");
4653 strcat(parseList[boardIndex-1], yy_text);
4665 case GameUnfinished:
4666 if (gameMode == IcsExamining) {
4667 if (boardIndex < backwardMostMove) {
4668 /* Oops, gap. How did that happen? */
4671 backwardMostMove = blackPlaysFirst ? 1 : 0;
4674 gameInfo.result = moveType;
4675 p = strchr(yy_text, '{');
4676 if (p == NULL) p = strchr(yy_text, '(');
4679 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4681 q = strchr(p, *p == '{' ? '}' : ')');
4682 if (q != NULL) *q = NULLCHAR;
4685 gameInfo.resultDetails = StrSave(p);
4688 if (boardIndex >= forwardMostMove &&
4689 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4690 backwardMostMove = blackPlaysFirst ? 1 : 0;
4693 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4694 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4695 parseList[boardIndex]);
4696 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4697 /* currentMoveString is set as a side-effect of yylex */
4698 strcpy(moveList[boardIndex], currentMoveString);
4699 strcat(moveList[boardIndex], "\n");
4701 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4702 switch (MateTest(boards[boardIndex],
4703 PosFlags(boardIndex), EP_UNKNOWN)) {
4709 strcat(parseList[boardIndex - 1], "+");
4712 strcat(parseList[boardIndex - 1], "#");
4719 /* Apply a move to the given board */
4721 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4722 int fromX, fromY, toX, toY;
4726 ChessSquare captured = board[toY][toX];
4727 if (fromY == DROP_RANK) {
4729 board[toY][toX] = (ChessSquare) fromX;
4730 } else if (fromX == toX && fromY == toY) {
4732 } else if (fromY == 0 && fromX == 4
4733 && board[fromY][fromX] == WhiteKing
4734 && toY == 0 && toX == 6) {
4735 board[fromY][fromX] = EmptySquare;
4736 board[toY][toX] = WhiteKing;
4737 board[fromY][7] = EmptySquare;
4738 board[toY][5] = WhiteRook;
4739 } else if (fromY == 0 && fromX == 4
4740 && board[fromY][fromX] == WhiteKing
4741 && toY == 0 && toX == 2) {
4742 board[fromY][fromX] = EmptySquare;
4743 board[toY][toX] = WhiteKing;
4744 board[fromY][0] = EmptySquare;
4745 board[toY][3] = WhiteRook;
4746 } else if (fromY == 0 && fromX == 3
4747 && board[fromY][fromX] == WhiteKing
4748 && toY == 0 && toX == 5) {
4749 board[fromY][fromX] = EmptySquare;
4750 board[toY][toX] = WhiteKing;
4751 board[fromY][7] = EmptySquare;
4752 board[toY][4] = WhiteRook;
4753 } else if (fromY == 0 && fromX == 3
4754 && board[fromY][fromX] == WhiteKing
4755 && toY == 0 && toX == 1) {
4756 board[fromY][fromX] = EmptySquare;
4757 board[toY][toX] = WhiteKing;
4758 board[fromY][0] = EmptySquare;
4759 board[toY][2] = WhiteRook;
4760 } else if (board[fromY][fromX] == WhitePawn
4762 /* white pawn promotion */
4763 board[7][toX] = CharToPiece(ToUpper(promoChar));
4764 if (board[7][toX] == EmptySquare) {
4765 board[7][toX] = WhiteQueen;
4767 board[fromY][fromX] = EmptySquare;
4768 } else if ((fromY == 4)
4770 && (board[fromY][fromX] == WhitePawn)
4771 && (board[toY][toX] == EmptySquare)) {
4772 board[fromY][fromX] = EmptySquare;
4773 board[toY][toX] = WhitePawn;
4774 captured = board[toY - 1][toX];
4775 board[toY - 1][toX] = EmptySquare;
4776 } else if (fromY == 7 && fromX == 4
4777 && board[fromY][fromX] == BlackKing
4778 && toY == 7 && toX == 6) {
4779 board[fromY][fromX] = EmptySquare;
4780 board[toY][toX] = BlackKing;
4781 board[fromY][7] = EmptySquare;
4782 board[toY][5] = BlackRook;
4783 } else if (fromY == 7 && fromX == 4
4784 && board[fromY][fromX] == BlackKing
4785 && toY == 7 && toX == 2) {
4786 board[fromY][fromX] = EmptySquare;
4787 board[toY][toX] = BlackKing;
4788 board[fromY][0] = EmptySquare;
4789 board[toY][3] = BlackRook;
4790 } else if (fromY == 7 && fromX == 3
4791 && board[fromY][fromX] == BlackKing
4792 && toY == 7 && toX == 5) {
4793 board[fromY][fromX] = EmptySquare;
4794 board[toY][toX] = BlackKing;
4795 board[fromY][7] = EmptySquare;
4796 board[toY][4] = BlackRook;
4797 } else if (fromY == 7 && fromX == 3
4798 && board[fromY][fromX] == BlackKing
4799 && toY == 7 && toX == 1) {
4800 board[fromY][fromX] = EmptySquare;
4801 board[toY][toX] = BlackKing;
4802 board[fromY][0] = EmptySquare;
4803 board[toY][2] = BlackRook;
4804 } else if (board[fromY][fromX] == BlackPawn
4806 /* black pawn promotion */
4807 board[0][toX] = CharToPiece(ToLower(promoChar));
4808 if (board[0][toX] == EmptySquare) {
4809 board[0][toX] = BlackQueen;
4811 board[fromY][fromX] = EmptySquare;
4812 } else if ((fromY == 3)
4814 && (board[fromY][fromX] == BlackPawn)
4815 && (board[toY][toX] == EmptySquare)) {
4816 board[fromY][fromX] = EmptySquare;
4817 board[toY][toX] = BlackPawn;
4818 captured = board[toY + 1][toX];
4819 board[toY + 1][toX] = EmptySquare;
4821 board[toY][toX] = board[fromY][fromX];
4822 board[fromY][fromX] = EmptySquare;
4824 if (gameInfo.variant == VariantCrazyhouse) {
4826 /* !!A lot more code needs to be written to support holdings */
4827 if (fromY == DROP_RANK) {
4828 /* Delete from holdings */
4829 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4831 if (captured != EmptySquare) {
4832 /* Add to holdings */
4833 if (captured < BlackPawn) {
4834 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4836 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4840 } else if (gameInfo.variant == VariantAtomic) {
4841 if (captured != EmptySquare) {
4843 for (y = toY-1; y <= toY+1; y++) {
4844 for (x = toX-1; x <= toX+1; x++) {
4845 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4846 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4847 board[y][x] = EmptySquare;
4851 board[toY][toX] = EmptySquare;
4856 /* Updates forwardMostMove */
4858 MakeMove(fromX, fromY, toX, toY, promoChar)
4859 int fromX, fromY, toX, toY;
4863 if (forwardMostMove >= MAX_MOVES) {
4864 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4869 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4870 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4871 if (commentList[forwardMostMove] != NULL) {
4872 free(commentList[forwardMostMove]);
4873 commentList[forwardMostMove] = NULL;
4875 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4876 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4877 gameInfo.result = GameUnfinished;
4878 if (gameInfo.resultDetails != NULL) {
4879 free(gameInfo.resultDetails);
4880 gameInfo.resultDetails = NULL;
4882 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4883 moveList[forwardMostMove - 1]);
4884 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4885 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4886 fromY, fromX, toY, toX, promoChar,
4887 parseList[forwardMostMove - 1]);
4888 switch (MateTest(boards[forwardMostMove],
4889 PosFlags(forwardMostMove), EP_UNKNOWN)){
4895 strcat(parseList[forwardMostMove - 1], "+");
4898 strcat(parseList[forwardMostMove - 1], "#");
4903 /* Updates currentMove if not pausing */
4905 ShowMove(fromX, fromY, toX, toY)
4907 int instant = (gameMode == PlayFromGameFile) ?
4908 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4909 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4911 if (forwardMostMove == currentMove + 1) {
4912 AnimateMove(boards[forwardMostMove - 1],
4913 fromX, fromY, toX, toY);
4915 if (appData.highlightLastMove) {
4916 SetHighlights(fromX, fromY, toX, toY);
4919 currentMove = forwardMostMove;
4922 if (instant) return;
4923 DisplayMove(currentMove - 1);
4924 DrawPosition(FALSE, boards[currentMove]);
4925 DisplayBothClocks();
4926 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4931 InitChessProgram(cps)
4932 ChessProgramState *cps;
4935 if (appData.noChessProgram) return;
4936 hintRequested = FALSE;
4937 bookRequested = FALSE;
4938 SendToProgram(cps->initString, cps);
4939 if (gameInfo.variant != VariantNormal &&
4940 gameInfo.variant != VariantLoadable) {
4941 char *v = VariantName(gameInfo.variant);
4942 if (StrStr(cps->variants, v) == NULL) {
4943 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4944 DisplayFatalError(buf, 0, 1);
4947 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4948 SendToProgram(buf, cps);
4951 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4952 SendToProgram(buf, cps);
4954 cps->maybeThinking = FALSE;
4955 cps->offeredDraw = 0;
4956 if (!appData.icsActive) {
4957 SendTimeControl(cps, movesPerSession, timeControl,
4958 timeIncrement, appData.searchDepth,
4961 if (appData.showThinking) {
4962 SendToProgram("post\n", cps);
4964 SendToProgram("hard\n", cps);
4965 if (!appData.ponderNextMove) {
4966 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4967 it without being sure what state we are in first. "hard"
4968 is not a toggle, so that one is OK.
4970 SendToProgram("easy\n", cps);
4973 sprintf(buf, "ping %d\n", ++cps->lastPing);
4974 SendToProgram(buf, cps);
4976 cps->initDone = TRUE;
4981 StartChessProgram(cps)
4982 ChessProgramState *cps;
4987 if (appData.noChessProgram) return;
4988 cps->initDone = FALSE;
4990 if (strcmp(cps->host, "localhost") == 0) {
4991 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4992 } else if (*appData.remoteShell == NULLCHAR) {
4993 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4995 if (*appData.remoteUser == NULLCHAR) {
4996 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4999 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5000 cps->host, appData.remoteUser, cps->program);
5002 err = StartChildProcess(buf, "", &cps->pr);
5006 sprintf(buf, "Startup failure on '%s'", cps->program);
5007 DisplayFatalError(buf, err, 1);
5013 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5014 if (cps->protocolVersion > 1) {
5015 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5016 SendToProgram(buf, cps);
5018 SendToProgram("xboard\n", cps);
5024 TwoMachinesEventIfReady P((void))
5026 if (first.lastPing != first.lastPong) {
5027 DisplayMessage("", "Waiting for first chess program");
5028 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5031 if (second.lastPing != second.lastPong) {
5032 DisplayMessage("", "Waiting for second chess program");
5033 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5041 NextMatchGame P((void))
5044 if (*appData.loadGameFile != NULLCHAR) {
5045 LoadGameFromFile(appData.loadGameFile,
5046 appData.loadGameIndex,
5047 appData.loadGameFile, FALSE);
5048 } else if (*appData.loadPositionFile != NULLCHAR) {
5049 LoadPositionFromFile(appData.loadPositionFile,
5050 appData.loadPositionIndex,
5051 appData.loadPositionFile);
5053 TwoMachinesEventIfReady();
5057 GameEnds(result, resultDetails, whosays)
5059 char *resultDetails;
5062 GameMode nextGameMode;
5065 if (appData.debugMode) {
5066 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5067 result, resultDetails ? resultDetails : "(null)", whosays);
5070 if (appData.icsActive && whosays == GE_ENGINE) {
5071 /* If we are playing on ICS, the server decides when the
5072 game is over, but the engine can offer to draw, claim
5076 if (appData.zippyPlay && first.initDone) {
5077 if (result == GameIsDrawn) {
5078 /* In case draw still needs to be claimed */
5079 SendToICS(ics_prefix);
5080 SendToICS("draw\n");
5081 } else if (StrCaseStr(resultDetails, "resign")) {
5082 SendToICS(ics_prefix);
5083 SendToICS("resign\n");
5090 /* If we're loading the game from a file, stop */
5091 if (whosays == GE_FILE) {
5092 (void) StopLoadGameTimer();
5096 /* Cancel draw offers */
5097 first.offeredDraw = second.offeredDraw = 0;
5099 /* If this is an ICS game, only ICS can really say it's done;
5100 if not, anyone can. */
5101 isIcsGame = (gameMode == IcsPlayingWhite ||
5102 gameMode == IcsPlayingBlack ||
5103 gameMode == IcsObserving ||
5104 gameMode == IcsExamining);
5106 if (!isIcsGame || whosays == GE_ICS) {
5107 /* OK -- not an ICS game, or ICS said it was done */
5109 if (!isIcsGame && !appData.noChessProgram)
5110 SetUserThinkingEnables();
5112 if (resultDetails != NULL) {
5113 gameInfo.result = result;
5114 gameInfo.resultDetails = StrSave(resultDetails);
5116 /* Tell program how game ended in case it is learning */
5117 if (gameMode == MachinePlaysWhite ||
5118 gameMode == MachinePlaysBlack ||
5119 gameMode == TwoMachinesPlay ||
5120 gameMode == IcsPlayingWhite ||
5121 gameMode == IcsPlayingBlack ||
5122 gameMode == BeginningOfGame) {
5124 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5126 if (first.pr != NoProc) {
5127 SendToProgram(buf, &first);
5129 if (second.pr != NoProc &&
5130 gameMode == TwoMachinesPlay) {
5131 SendToProgram(buf, &second);
5135 /* display last move only if game was not loaded from file */
5136 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5137 DisplayMove(currentMove - 1);
5139 if (forwardMostMove != 0) {
5140 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5141 if (*appData.saveGameFile != NULLCHAR) {
5142 SaveGameToFile(appData.saveGameFile, TRUE);
5143 } else if (appData.autoSaveGames) {
5146 if (*appData.savePositionFile != NULLCHAR) {
5147 SavePositionToFile(appData.savePositionFile);
5153 if (appData.icsActive) {
5154 if (appData.quietPlay &&
5155 (gameMode == IcsPlayingWhite ||
5156 gameMode == IcsPlayingBlack)) {
5157 SendToICS(ics_prefix);
5158 SendToICS("set shout 1\n");
5160 nextGameMode = IcsIdle;
5161 ics_user_moved = FALSE;
5162 /* clean up premove. It's ugly when the game has ended and the
5163 * premove highlights are still on the board.
5167 ClearPremoveHighlights();
5168 DrawPosition(FALSE, boards[currentMove]);
5170 if (whosays == GE_ICS) {
5173 if (gameMode == IcsPlayingWhite)
5175 else if(gameMode == IcsPlayingBlack)
5179 if (gameMode == IcsPlayingBlack)
5181 else if(gameMode == IcsPlayingWhite)
5188 PlayIcsUnfinishedSound();
5191 } else if (gameMode == EditGame ||
5192 gameMode == PlayFromGameFile ||
5193 gameMode == AnalyzeMode ||
5194 gameMode == AnalyzeFile) {
5195 nextGameMode = gameMode;
5197 nextGameMode = EndOfGame;
5202 nextGameMode = gameMode;
5205 if (appData.noChessProgram) {
5206 gameMode = nextGameMode;
5212 /* Put first chess program into idle state */
5213 if (first.pr != NoProc &&
5214 (gameMode == MachinePlaysWhite ||
5215 gameMode == MachinePlaysBlack ||
5216 gameMode == TwoMachinesPlay ||
5217 gameMode == IcsPlayingWhite ||
5218 gameMode == IcsPlayingBlack ||
5219 gameMode == BeginningOfGame)) {
5220 SendToProgram("force\n", &first);
5221 if (first.usePing) {
5223 sprintf(buf, "ping %d\n", ++first.lastPing);
5224 SendToProgram(buf, &first);
5227 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5228 /* Kill off first chess program */
5229 if (first.isr != NULL)
5230 RemoveInputSource(first.isr);
5233 if (first.pr != NoProc) {
5235 SendToProgram("quit\n", &first);
5236 DestroyChildProcess(first.pr, first.useSigterm);
5241 /* Put second chess program into idle state */
5242 if (second.pr != NoProc &&
5243 gameMode == TwoMachinesPlay) {
5244 SendToProgram("force\n", &second);
5245 if (second.usePing) {
5247 sprintf(buf, "ping %d\n", ++second.lastPing);
5248 SendToProgram(buf, &second);
5251 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5252 /* Kill off second chess program */
5253 if (second.isr != NULL)
5254 RemoveInputSource(second.isr);
5257 if (second.pr != NoProc) {
5258 SendToProgram("quit\n", &second);
5259 DestroyChildProcess(second.pr, second.useSigterm);
5264 if (matchMode && gameMode == TwoMachinesPlay) {
5267 if (first.twoMachinesColor[0] == 'w') {
5274 if (first.twoMachinesColor[0] == 'b') {
5283 if (matchGame < appData.matchGames) {
5285 tmp = first.twoMachinesColor;
5286 first.twoMachinesColor = second.twoMachinesColor;
5287 second.twoMachinesColor = tmp;
5288 gameMode = nextGameMode;
5290 ScheduleDelayedEvent(NextMatchGame, 10000);
5294 gameMode = nextGameMode;
5295 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5296 first.tidy, second.tidy,
5297 first.matchWins, second.matchWins,
5298 appData.matchGames - (first.matchWins + second.matchWins));
5299 DisplayFatalError(buf, 0, 0);
5302 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5303 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5305 gameMode = nextGameMode;
5309 /* Assumes program was just initialized (initString sent).
5310 Leaves program in force mode. */
5312 FeedMovesToProgram(cps, upto)
5313 ChessProgramState *cps;
5318 if (appData.debugMode)
5319 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5320 startedFromSetupPosition ? "position and " : "",
5321 backwardMostMove, upto, cps->which);
5322 SendToProgram("force\n", cps);
5323 if (startedFromSetupPosition) {
5324 SendBoard(cps, backwardMostMove);
5326 for (i = backwardMostMove; i < upto; i++) {
5327 SendMoveToProgram(i, cps);
5333 ResurrectChessProgram()
5335 /* The chess program may have exited.
5336 If so, restart it and feed it all the moves made so far. */
5338 if (appData.noChessProgram || first.pr != NoProc) return;
5340 StartChessProgram(&first);
5341 InitChessProgram(&first);
5342 FeedMovesToProgram(&first, currentMove);
5344 if (!first.sendTime) {
5345 /* can't tell gnuchess what its clock should read,
5346 so we bow to its notion. */
5348 timeRemaining[0][currentMove] = whiteTimeRemaining;
5349 timeRemaining[1][currentMove] = blackTimeRemaining;
5352 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
5353 appData.icsEngineAnalyze) && first.analysisSupport) {
5354 SendToProgram("analyze\n", &first);
5355 first.analyzing = TRUE;
5368 if (appData.debugMode) {
5369 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5370 redraw, init, gameMode);
5373 pausing = pauseExamInvalid = FALSE;
5374 startedFromSetupPosition = blackPlaysFirst = FALSE;
5376 whiteFlag = blackFlag = FALSE;
5377 userOfferedDraw = FALSE;
5378 hintRequested = bookRequested = FALSE;
5379 first.maybeThinking = FALSE;
5380 second.maybeThinking = FALSE;
5381 thinkOutput[0] = NULLCHAR;
5382 lastHint[0] = NULLCHAR;
5383 ClearGameInfo(&gameInfo);
5384 gameInfo.variant = StringToVariant(appData.variant);
5385 ics_user_moved = ics_clock_paused = FALSE;
5386 ics_getting_history = H_FALSE;
5388 white_holding[0] = black_holding[0] = NULLCHAR;
5389 ClearProgramStats();
5393 flipView = appData.flipView;
5394 ClearPremoveHighlights();
5396 alarmSounded = FALSE;
5398 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5400 gameMode = BeginningOfGame;
5402 InitPosition(redraw);
5403 for (i = 0; i < MAX_MOVES; i++) {
5404 if (commentList[i] != NULL) {
5405 free(commentList[i]);
5406 commentList[i] = NULL;
5410 timeRemaining[0][0] = whiteTimeRemaining;
5411 timeRemaining[1][0] = blackTimeRemaining;
5412 if (first.pr == NULL) {
5413 StartChessProgram(&first);
5415 if (init) InitChessProgram(&first);
5417 DisplayMessage("", "");
5418 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5425 if (!AutoPlayOneMove())
5427 if (matchMode || appData.timeDelay == 0)
5429 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5431 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5440 int fromX, fromY, toX, toY;
5442 if (appData.debugMode) {
5443 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5446 if (gameMode != PlayFromGameFile)
5449 if (currentMove >= forwardMostMove) {
5450 gameMode = EditGame;
5455 toX = moveList[currentMove][2] - 'a';
5456 toY = moveList[currentMove][3] - '1';
5458 if (moveList[currentMove][1] == '@') {
5459 if (appData.highlightLastMove) {
5460 SetHighlights(-1, -1, toX, toY);
5463 fromX = moveList[currentMove][0] - 'a';
5464 fromY = moveList[currentMove][1] - '1';
5465 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5467 if (appData.highlightLastMove) {
5468 SetHighlights(fromX, fromY, toX, toY);
5471 DisplayMove(currentMove);
5472 SendMoveToProgram(currentMove++, &first);
5473 DisplayBothClocks();
5474 DrawPosition(FALSE, boards[currentMove]);
5475 if (commentList[currentMove] != NULL) {
5476 DisplayComment(currentMove - 1, commentList[currentMove]);
5483 LoadGameOneMove(readAhead)
5484 ChessMove readAhead;
5486 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5487 char promoChar = NULLCHAR;
5492 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5493 gameMode != AnalyzeMode && gameMode != Training) {
5498 yyboardindex = forwardMostMove;
5499 if (readAhead != (ChessMove)0) {
5500 moveType = readAhead;
5502 if (gameFileFP == NULL)
5504 moveType = (ChessMove) yylex();
5510 if (appData.debugMode)
5511 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5513 if (*p == '{' || *p == '[' || *p == '(') {
5514 p[strlen(p) - 1] = NULLCHAR;
5518 /* append the comment but don't display it */
5519 while (*p == '\n') p++;
5520 AppendComment(currentMove, p);
5523 case WhiteCapturesEnPassant:
5524 case BlackCapturesEnPassant:
5525 case WhitePromotionQueen:
5526 case BlackPromotionQueen:
5527 case WhitePromotionRook:
5528 case BlackPromotionRook:
5529 case WhitePromotionBishop:
5530 case BlackPromotionBishop:
5531 case WhitePromotionKnight:
5532 case BlackPromotionKnight:
5533 case WhitePromotionKing:
5534 case BlackPromotionKing:
5536 case WhiteKingSideCastle:
5537 case WhiteQueenSideCastle:
5538 case BlackKingSideCastle:
5539 case BlackQueenSideCastle:
5540 case WhiteKingSideCastleWild:
5541 case WhiteQueenSideCastleWild:
5542 case BlackKingSideCastleWild:
5543 case BlackQueenSideCastleWild:
5544 if (appData.debugMode)
5545 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5546 fromX = currentMoveString[0] - 'a';
5547 fromY = currentMoveString[1] - '1';
5548 toX = currentMoveString[2] - 'a';
5549 toY = currentMoveString[3] - '1';
5550 promoChar = currentMoveString[4];
5555 if (appData.debugMode)
5556 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5557 fromX = moveType == WhiteDrop ?
5558 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5559 (int) CharToPiece(ToLower(currentMoveString[0]));
5561 toX = currentMoveString[2] - 'a';
5562 toY = currentMoveString[3] - '1';
5568 case GameUnfinished:
5569 if (appData.debugMode)
5570 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5571 p = strchr(yy_text, '{');
5572 if (p == NULL) p = strchr(yy_text, '(');
5575 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5577 q = strchr(p, *p == '{' ? '}' : ')');
5578 if (q != NULL) *q = NULLCHAR;
5581 GameEnds(moveType, p, GE_FILE);
5583 if (cmailMsgLoaded) {
5585 flipView = WhiteOnMove(currentMove);
5586 if (moveType == GameUnfinished) flipView = !flipView;
5587 if (appData.debugMode)
5588 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5592 case (ChessMove) 0: /* end of file */
5593 if (appData.debugMode)
5594 fprintf(debugFP, "Parser hit end of file\n");
5595 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5601 if (WhiteOnMove(currentMove)) {
5602 GameEnds(BlackWins, "Black mates", GE_FILE);
5604 GameEnds(WhiteWins, "White mates", GE_FILE);
5608 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5615 if (lastLoadGameStart == GNUChessGame) {
5616 /* GNUChessGames have numbers, but they aren't move numbers */
5617 if (appData.debugMode)
5618 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5619 yy_text, (int) moveType);
5620 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5622 /* else fall thru */
5627 /* Reached start of next game in file */
5628 if (appData.debugMode)
5629 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5630 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5636 if (WhiteOnMove(currentMove)) {
5637 GameEnds(BlackWins, "Black mates", GE_FILE);
5639 GameEnds(WhiteWins, "White mates", GE_FILE);
5643 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5649 case PositionDiagram: /* should not happen; ignore */
5650 case ElapsedTime: /* ignore */
5651 case NAG: /* ignore */
5652 if (appData.debugMode)
5653 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5654 yy_text, (int) moveType);
5655 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5658 if (appData.testLegality) {
5659 if (appData.debugMode)
5660 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5661 sprintf(move, _("Illegal move: %d.%s%s"),
5662 (forwardMostMove / 2) + 1,
5663 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5664 DisplayError(move, 0);
5667 if (appData.debugMode)
5668 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5669 yy_text, currentMoveString);
5670 fromX = currentMoveString[0] - 'a';
5671 fromY = currentMoveString[1] - '1';
5672 toX = currentMoveString[2] - 'a';
5673 toY = currentMoveString[3] - '1';
5674 promoChar = currentMoveString[4];
5679 if (appData.debugMode)
5680 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5681 sprintf(move, _("Ambiguous move: %d.%s%s"),
5682 (forwardMostMove / 2) + 1,
5683 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5684 DisplayError(move, 0);
5689 case ImpossibleMove:
5690 if (appData.debugMode)
5691 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5692 sprintf(move, _("Illegal move: %d.%s%s"),
5693 (forwardMostMove / 2) + 1,
5694 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5695 DisplayError(move, 0);
5701 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5702 DrawPosition(FALSE, boards[currentMove]);
5703 DisplayBothClocks();
5704 if (!appData.matchMode && commentList[currentMove] != NULL)
5705 DisplayComment(currentMove - 1, commentList[currentMove]);
5707 (void) StopLoadGameTimer();
5709 cmailOldMove = forwardMostMove;
5712 /* currentMoveString is set as a side-effect of yylex */
5713 strcat(currentMoveString, "\n");
5714 strcpy(moveList[forwardMostMove], currentMoveString);
5716 thinkOutput[0] = NULLCHAR;
5717 MakeMove(fromX, fromY, toX, toY, promoChar);
5718 currentMove = forwardMostMove;
5723 /* Load the nth game from the given file */
5725 LoadGameFromFile(filename, n, title, useList)
5729 /*Boolean*/ int useList;
5734 if (strcmp(filename, "-") == 0) {
5738 f = fopen(filename, "rb");
5740 sprintf(buf, _("Can't open \"%s\""), filename);
5741 DisplayError(buf, errno);
5745 if (fseek(f, 0, 0) == -1) {
5746 /* f is not seekable; probably a pipe */
5749 if (useList && n == 0) {
5750 int error = GameListBuild(f);
5752 DisplayError(_("Cannot build game list"), error);
5753 } else if (!ListEmpty(&gameList) &&
5754 ((ListGame *) gameList.tailPred)->number > 1) {
5755 GameListPopUp(f, title);
5762 return LoadGame(f, n, title, FALSE);
5767 MakeRegisteredMove()
5769 int fromX, fromY, toX, toY;
5771 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5772 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5775 if (appData.debugMode)
5776 fprintf(debugFP, "Restoring %s for game %d\n",
5777 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5779 thinkOutput[0] = NULLCHAR;
5780 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5781 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5782 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5783 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5784 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5785 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5786 MakeMove(fromX, fromY, toX, toY, promoChar);
5787 ShowMove(fromX, fromY, toX, toY);
5789 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5796 if (WhiteOnMove(currentMove)) {
5797 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5799 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5804 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5811 if (WhiteOnMove(currentMove)) {
5812 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5814 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5819 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5830 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5832 CmailLoadGame(f, gameNumber, title, useList)
5840 if (gameNumber > nCmailGames) {
5841 DisplayError(_("No more games in this message"), 0);
5844 if (f == lastLoadGameFP) {
5845 int offset = gameNumber - lastLoadGameNumber;
5847 cmailMsg[0] = NULLCHAR;
5848 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5849 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5850 nCmailMovesRegistered--;
5852 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5853 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5854 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5857 if (! RegisterMove()) return FALSE;
5861 retVal = LoadGame(f, gameNumber, title, useList);
5863 /* Make move registered during previous look at this game, if any */
5864 MakeRegisteredMove();
5866 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5867 commentList[currentMove]
5868 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5869 DisplayComment(currentMove - 1, commentList[currentMove]);
5875 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5880 int gameNumber = lastLoadGameNumber + offset;
5881 if (lastLoadGameFP == NULL) {
5882 DisplayError(_("No game has been loaded yet"), 0);
5885 if (gameNumber <= 0) {
5886 DisplayError(_("Can't back up any further"), 0);
5889 if (cmailMsgLoaded) {
5890 return CmailLoadGame(lastLoadGameFP, gameNumber,
5891 lastLoadGameTitle, lastLoadGameUseList);
5893 return LoadGame(lastLoadGameFP, gameNumber,
5894 lastLoadGameTitle, lastLoadGameUseList);
5900 /* Load the nth game from open file f */
5902 LoadGame(f, gameNumber, title, useList)
5910 int gn = gameNumber;
5911 ListGame *lg = NULL;
5914 GameMode oldGameMode;
5916 if (appData.debugMode)
5917 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5919 if (gameMode == Training )
5920 SetTrainingModeOff();
5922 oldGameMode = gameMode;
5923 if (gameMode != BeginningOfGame) {
5928 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5929 fclose(lastLoadGameFP);
5933 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5936 fseek(f, lg->offset, 0);
5937 GameListHighlight(gameNumber);
5941 DisplayError(_("Game number out of range"), 0);
5946 if (fseek(f, 0, 0) == -1) {
5947 if (f == lastLoadGameFP ?
5948 gameNumber == lastLoadGameNumber + 1 :
5952 DisplayError(_("Can't seek on game file"), 0);
5958 lastLoadGameNumber = gameNumber;
5959 strcpy(lastLoadGameTitle, title);
5960 lastLoadGameUseList = useList;
5965 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5966 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5967 lg->gameInfo.black);
5969 } else if (*title != NULLCHAR) {
5970 if (gameNumber > 1) {
5971 sprintf(buf, "%s %d", title, gameNumber);
5974 DisplayTitle(title);
5978 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5979 gameMode = PlayFromGameFile;
5983 currentMove = forwardMostMove = backwardMostMove = 0;
5984 CopyBoard(boards[0], initialPosition);
5988 * Skip the first gn-1 games in the file.
5989 * Also skip over anything that precedes an identifiable
5990 * start of game marker, to avoid being confused by
5991 * garbage at the start of the file. Currently
5992 * recognized start of game markers are the move number "1",
5993 * the pattern "gnuchess .* game", the pattern
5994 * "^[#;%] [^ ]* game file", and a PGN tag block.
5995 * A game that starts with one of the latter two patterns
5996 * will also have a move number 1, possibly
5997 * following a position diagram.
5998 * 5-4-02: Let's try being more lenient and allowing a game to
5999 * start with an unnumbered move. Does that break anything?
6001 cm = lastLoadGameStart = (ChessMove) 0;
6003 yyboardindex = forwardMostMove;
6004 cm = (ChessMove) yylex();
6007 if (cmailMsgLoaded) {
6008 nCmailGames = CMAIL_MAX_GAMES - gn;
6011 DisplayError(_("Game not found in file"), 0);
6018 lastLoadGameStart = cm;
6022 switch (lastLoadGameStart) {
6029 gn--; /* count this game */
6030 lastLoadGameStart = cm;
6039 switch (lastLoadGameStart) {
6044 gn--; /* count this game */
6045 lastLoadGameStart = cm;
6048 lastLoadGameStart = cm; /* game counted already */
6056 yyboardindex = forwardMostMove;
6057 cm = (ChessMove) yylex();
6058 } while (cm == PGNTag || cm == Comment);
6065 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6066 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6067 != CMAIL_OLD_RESULT) {
6069 cmailResult[ CMAIL_MAX_GAMES
6070 - gn - 1] = CMAIL_OLD_RESULT;
6076 /* Only a NormalMove can be at the start of a game
6077 * without a position diagram. */
6078 if (lastLoadGameStart == (ChessMove) 0) {
6080 lastLoadGameStart = MoveNumberOne;
6089 if (appData.debugMode)
6090 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6092 if (cm == XBoardGame) {
6093 /* Skip any header junk before position diagram and/or move 1 */
6095 yyboardindex = forwardMostMove;
6096 cm = (ChessMove) yylex();
6098 if (cm == (ChessMove) 0 ||
6099 cm == GNUChessGame || cm == XBoardGame) {
6100 /* Empty game; pretend end-of-file and handle later */
6105 if (cm == MoveNumberOne || cm == PositionDiagram ||
6106 cm == PGNTag || cm == Comment)
6109 } else if (cm == GNUChessGame) {
6110 if (gameInfo.event != NULL) {
6111 free(gameInfo.event);
6113 gameInfo.event = StrSave(yy_text);
6116 startedFromSetupPosition = FALSE;
6117 while (cm == PGNTag) {
6118 if (appData.debugMode)
6119 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6120 err = ParsePGNTag(yy_text, &gameInfo);
6121 if (!err) numPGNTags++;
6123 if (gameInfo.fen != NULL) {
6124 Board initial_position;
6125 startedFromSetupPosition = TRUE;
6126 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6128 DisplayError(_("Bad FEN position in file"), 0);
6131 CopyBoard(boards[0], initial_position);
6132 if (blackPlaysFirst) {
6133 currentMove = forwardMostMove = backwardMostMove = 1;
6134 CopyBoard(boards[1], initial_position);
6135 strcpy(moveList[0], "");
6136 strcpy(parseList[0], "");
6137 timeRemaining[0][1] = whiteTimeRemaining;
6138 timeRemaining[1][1] = blackTimeRemaining;
6139 if (commentList[0] != NULL) {
6140 commentList[1] = commentList[0];
6141 commentList[0] = NULL;
6144 currentMove = forwardMostMove = backwardMostMove = 0;
6146 yyboardindex = forwardMostMove;
6148 gameInfo.fen = NULL;
6151 yyboardindex = forwardMostMove;
6152 cm = (ChessMove) yylex();
6154 /* Handle comments interspersed among the tags */
6155 while (cm == Comment) {
6157 if (appData.debugMode)
6158 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6160 if (*p == '{' || *p == '[' || *p == '(') {
6161 p[strlen(p) - 1] = NULLCHAR;
6164 while (*p == '\n') p++;
6165 AppendComment(currentMove, p);
6166 yyboardindex = forwardMostMove;
6167 cm = (ChessMove) yylex();
6171 /* don't rely on existence of Event tag since if game was
6172 * pasted from clipboard the Event tag may not exist
6174 if (numPGNTags > 0){
6176 if (gameInfo.variant == VariantNormal) {
6177 gameInfo.variant = StringToVariant(gameInfo.event);
6180 tags = PGNTags(&gameInfo);
6181 TagsPopUp(tags, CmailMsg());
6185 /* Make something up, but don't display it now */
6190 if (cm == PositionDiagram) {
6193 Board initial_position;
6195 if (appData.debugMode)
6196 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6198 if (!startedFromSetupPosition) {
6200 for (i = BOARD_SIZE - 1; i >= 0; i--)
6201 for (j = 0; j < BOARD_SIZE; p++)
6211 initial_position[i][j++] = CharToPiece(*p);
6214 while (*p == ' ' || *p == '\t' ||
6215 *p == '\n' || *p == '\r') p++;
6217 if (strncmp(p, "black", strlen("black"))==0)
6218 blackPlaysFirst = TRUE;
6220 blackPlaysFirst = FALSE;
6221 startedFromSetupPosition = TRUE;
6223 CopyBoard(boards[0], initial_position);
6224 if (blackPlaysFirst) {
6225 currentMove = forwardMostMove = backwardMostMove = 1;
6226 CopyBoard(boards[1], initial_position);
6227 strcpy(moveList[0], "");
6228 strcpy(parseList[0], "");
6229 timeRemaining[0][1] = whiteTimeRemaining;
6230 timeRemaining[1][1] = blackTimeRemaining;
6231 if (commentList[0] != NULL) {
6232 commentList[1] = commentList[0];
6233 commentList[0] = NULL;
6236 currentMove = forwardMostMove = backwardMostMove = 0;
6239 yyboardindex = forwardMostMove;
6240 cm = (ChessMove) yylex();
6243 if (first.pr == NoProc) {
6244 StartChessProgram(&first);
6246 InitChessProgram(&first);
6247 SendToProgram("force\n", &first);
6248 if (startedFromSetupPosition) {
6249 SendBoard(&first, forwardMostMove);
6250 DisplayBothClocks();
6253 while (cm == Comment) {
6255 if (appData.debugMode)
6256 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6258 if (*p == '{' || *p == '[' || *p == '(') {
6259 p[strlen(p) - 1] = NULLCHAR;
6262 while (*p == '\n') p++;
6263 AppendComment(currentMove, p);
6264 yyboardindex = forwardMostMove;
6265 cm = (ChessMove) yylex();
6268 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6269 cm == WhiteWins || cm == BlackWins ||
6270 cm == GameIsDrawn || cm == GameUnfinished) {
6271 DisplayMessage("", _("No moves in game"));
6272 if (cmailMsgLoaded) {
6273 if (appData.debugMode)
6274 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6278 DrawPosition(FALSE, boards[currentMove]);
6279 DisplayBothClocks();
6280 gameMode = EditGame;
6287 if (commentList[currentMove] != NULL) {
6288 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6289 DisplayComment(currentMove - 1, commentList[currentMove]);
6292 if (!matchMode && appData.timeDelay != 0)
6293 DrawPosition(FALSE, boards[currentMove]);
6295 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6296 programStats.ok_to_send = 1;
6299 /* if the first token after the PGN tags is a move
6300 * and not move number 1, retrieve it from the parser
6302 if (cm != MoveNumberOne)
6303 LoadGameOneMove(cm);
6305 /* load the remaining moves from the file */
6306 while (LoadGameOneMove((ChessMove)0)) {
6307 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6308 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6311 /* rewind to the start of the game */
6312 currentMove = backwardMostMove;
6314 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6316 if (oldGameMode == AnalyzeFile ||
6317 oldGameMode == AnalyzeMode) {
6321 if (matchMode || appData.timeDelay == 0) {
6323 gameMode = EditGame;
6325 } else if (appData.timeDelay > 0) {
6329 if (appData.debugMode)
6330 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6334 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6336 ReloadPosition(offset)
6339 int positionNumber = lastLoadPositionNumber + offset;
6340 if (lastLoadPositionFP == NULL) {
6341 DisplayError(_("No position has been loaded yet"), 0);
6344 if (positionNumber <= 0) {
6345 DisplayError(_("Can't back up any further"), 0);
6348 return LoadPosition(lastLoadPositionFP, positionNumber,
6349 lastLoadPositionTitle);
6352 /* Load the nth position from the given file */
6354 LoadPositionFromFile(filename, n, title)
6362 if (strcmp(filename, "-") == 0) {
6363 return LoadPosition(stdin, n, "stdin");
6365 f = fopen(filename, "rb");
6367 sprintf(buf, _("Can't open \"%s\""), filename);
6368 DisplayError(buf, errno);
6371 return LoadPosition(f, n, title);
6376 /* Load the nth position from the given open file, and close it */
6378 LoadPosition(f, positionNumber, title)
6383 char *p, line[MSG_SIZ];
6384 Board initial_position;
6385 int i, j, fenMode, pn;
6387 if (gameMode == Training )
6388 SetTrainingModeOff();
6390 if (gameMode != BeginningOfGame) {
6393 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6394 fclose(lastLoadPositionFP);
6396 if (positionNumber == 0) positionNumber = 1;
6397 lastLoadPositionFP = f;
6398 lastLoadPositionNumber = positionNumber;
6399 strcpy(lastLoadPositionTitle, title);
6400 if (first.pr == NoProc) {
6401 StartChessProgram(&first);
6402 InitChessProgram(&first);
6404 pn = positionNumber;
6405 if (positionNumber < 0) {
6406 /* Negative position number means to seek to that byte offset */
6407 if (fseek(f, -positionNumber, 0) == -1) {
6408 DisplayError(_("Can't seek on position file"), 0);
6413 if (fseek(f, 0, 0) == -1) {
6414 if (f == lastLoadPositionFP ?
6415 positionNumber == lastLoadPositionNumber + 1 :
6416 positionNumber == 1) {
6419 DisplayError(_("Can't seek on position file"), 0);
6424 /* See if this file is FEN or old-style xboard */
6425 if (fgets(line, MSG_SIZ, f) == NULL) {
6426 DisplayError(_("Position not found in file"), 0);
6434 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6435 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6436 case '1': case '2': case '3': case '4': case '5': case '6':
6443 if (fenMode || line[0] == '#') pn--;
6445 /* skip postions before number pn */
6446 if (fgets(line, MSG_SIZ, f) == NULL) {
6448 DisplayError(_("Position not found in file"), 0);
6451 if (fenMode || line[0] == '#') pn--;
6456 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6457 DisplayError(_("Bad FEN position in file"), 0);
6461 (void) fgets(line, MSG_SIZ, f);
6462 (void) fgets(line, MSG_SIZ, f);
6464 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6465 (void) fgets(line, MSG_SIZ, f);
6466 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6469 initial_position[i][j++] = CharToPiece(*p);
6473 blackPlaysFirst = FALSE;
6475 (void) fgets(line, MSG_SIZ, f);
6476 if (strncmp(line, "black", strlen("black"))==0)
6477 blackPlaysFirst = TRUE;
6480 startedFromSetupPosition = TRUE;
6482 SendToProgram("force\n", &first);
6483 CopyBoard(boards[0], initial_position);
6484 if (blackPlaysFirst) {
6485 currentMove = forwardMostMove = backwardMostMove = 1;
6486 strcpy(moveList[0], "");
6487 strcpy(parseList[0], "");
6488 CopyBoard(boards[1], initial_position);
6489 DisplayMessage("", _("Black to play"));
6491 currentMove = forwardMostMove = backwardMostMove = 0;
6492 DisplayMessage("", _("White to play"));
6494 SendBoard(&first, forwardMostMove);
6496 if (positionNumber > 1) {
6497 sprintf(line, "%s %d", title, positionNumber);
6500 DisplayTitle(title);
6502 gameMode = EditGame;
6505 timeRemaining[0][1] = whiteTimeRemaining;
6506 timeRemaining[1][1] = blackTimeRemaining;
6507 DrawPosition(FALSE, boards[currentMove]);
6514 CopyPlayerNameIntoFileName(dest, src)
6517 while (*src != NULLCHAR && *src != ',') {
6522 *(*dest)++ = *src++;
6527 char *DefaultFileName(ext)
6530 static char def[MSG_SIZ];
6533 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6535 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6537 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6546 /* Save the current game to the given file */
6548 SaveGameToFile(filename, append)
6555 if (strcmp(filename, "-") == 0) {
6556 return SaveGame(stdout, 0, NULL);
6558 f = fopen(filename, append ? "a" : "w");
6560 sprintf(buf, _("Can't open \"%s\""), filename);
6561 DisplayError(buf, errno);
6564 return SaveGame(f, 0, NULL);
6573 static char buf[MSG_SIZ];
6576 p = strchr(str, ' ');
6577 if (p == NULL) return str;
6578 strncpy(buf, str, p - str);
6579 buf[p - str] = NULLCHAR;
6583 #define PGN_MAX_LINE 75
6585 /* Save game in PGN style and close the file */
6590 int i, offset, linelen, newblock;
6594 int movelen, numlen, blank;
6596 tm = time((time_t *) NULL);
6598 PrintPGNTags(f, &gameInfo);
6600 if (backwardMostMove > 0 || startedFromSetupPosition) {
6601 char *fen = PositionToFEN(backwardMostMove);
6602 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6603 fprintf(f, "\n{--------------\n");
6604 PrintPosition(f, backwardMostMove);
6605 fprintf(f, "--------------}\n");
6611 i = backwardMostMove;
6612 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6616 while (i < forwardMostMove) {
6617 /* Print comments preceding this move */
6618 if (commentList[i] != NULL) {
6619 if (linelen > 0) fprintf(f, "\n");
6620 fprintf(f, "{\n%s}\n", commentList[i]);
6625 /* Format move number */
6627 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6630 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6632 numtext[0] = NULLCHAR;
6635 numlen = strlen(numtext);
6638 /* Print move number */
6639 blank = linelen > 0 && numlen > 0;
6640 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6649 fprintf(f, numtext);
6653 movetext = SavePart(parseList[i]);
6654 movelen = strlen(movetext);
6657 blank = linelen > 0 && movelen > 0;
6658 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6667 fprintf(f, movetext);
6673 /* Start a new line */
6674 if (linelen > 0) fprintf(f, "\n");
6676 /* Print comments after last move */
6677 if (commentList[i] != NULL) {
6678 fprintf(f, "{\n%s}\n", commentList[i]);
6682 if (gameInfo.resultDetails != NULL &&
6683 gameInfo.resultDetails[0] != NULLCHAR) {
6684 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6685 PGNResult(gameInfo.result));
6687 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6694 /* Save game in old style and close the file */
6702 tm = time((time_t *) NULL);
6704 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6707 if (backwardMostMove > 0 || startedFromSetupPosition) {
6708 fprintf(f, "\n[--------------\n");
6709 PrintPosition(f, backwardMostMove);
6710 fprintf(f, "--------------]\n");
6715 i = backwardMostMove;
6716 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6718 while (i < forwardMostMove) {
6719 if (commentList[i] != NULL) {
6720 fprintf(f, "[%s]\n", commentList[i]);
6724 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6727 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6729 if (commentList[i] != NULL) {
6733 if (i >= forwardMostMove) {
6737 fprintf(f, "%s\n", parseList[i]);
6742 if (commentList[i] != NULL) {
6743 fprintf(f, "[%s]\n", commentList[i]);
6746 /* This isn't really the old style, but it's close enough */
6747 if (gameInfo.resultDetails != NULL &&
6748 gameInfo.resultDetails[0] != NULLCHAR) {
6749 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6750 gameInfo.resultDetails);
6752 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6759 /* Save the current game to open file f and close the file */
6761 SaveGame(f, dummy, dummy2)
6766 if (gameMode == EditPosition) EditPositionDone();
6767 if (appData.oldSaveStyle)
6768 return SaveGameOldStyle(f);
6770 return SaveGamePGN(f);
6773 /* Save the current position to the given file */
6775 SavePositionToFile(filename)
6781 if (strcmp(filename, "-") == 0) {
6782 return SavePosition(stdout, 0, NULL);
6784 f = fopen(filename, "a");
6786 sprintf(buf, _("Can't open \"%s\""), filename);
6787 DisplayError(buf, errno);
6790 SavePosition(f, 0, NULL);
6796 /* Save the current position to the given open file and close the file */
6798 SavePosition(f, dummy, dummy2)
6806 if (appData.oldSaveStyle) {
6807 tm = time((time_t *) NULL);
6809 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6811 fprintf(f, "[--------------\n");
6812 PrintPosition(f, currentMove);
6813 fprintf(f, "--------------]\n");
6815 fen = PositionToFEN(currentMove);
6816 fprintf(f, "%s\n", fen);
6824 ReloadCmailMsgEvent(unregister)
6828 static char *inFilename = NULL;
6829 static char *outFilename;
6831 struct stat inbuf, outbuf;
6834 /* Any registered moves are unregistered if unregister is set, */
6835 /* i.e. invoked by the signal handler */
6837 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6838 cmailMoveRegistered[i] = FALSE;
6839 if (cmailCommentList[i] != NULL) {
6840 free(cmailCommentList[i]);
6841 cmailCommentList[i] = NULL;
6844 nCmailMovesRegistered = 0;
6847 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6848 cmailResult[i] = CMAIL_NOT_RESULT;
6852 if (inFilename == NULL) {
6853 /* Because the filenames are static they only get malloced once */
6854 /* and they never get freed */
6855 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6856 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6858 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6859 sprintf(outFilename, "%s.out", appData.cmailGameName);
6862 status = stat(outFilename, &outbuf);
6864 cmailMailedMove = FALSE;
6866 status = stat(inFilename, &inbuf);
6867 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6870 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6871 counts the games, notes how each one terminated, etc.
6873 It would be nice to remove this kludge and instead gather all
6874 the information while building the game list. (And to keep it
6875 in the game list nodes instead of having a bunch of fixed-size
6876 parallel arrays.) Note this will require getting each game's
6877 termination from the PGN tags, as the game list builder does
6878 not process the game moves. --mann
6880 cmailMsgLoaded = TRUE;
6881 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6883 /* Load first game in the file or popup game menu */
6884 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6894 char string[MSG_SIZ];
6896 if ( cmailMailedMove
6897 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6898 return TRUE; /* Allow free viewing */
6901 /* Unregister move to ensure that we don't leave RegisterMove */
6902 /* with the move registered when the conditions for registering no */
6904 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6905 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6906 nCmailMovesRegistered --;
6908 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6910 free(cmailCommentList[lastLoadGameNumber - 1]);
6911 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6915 if (cmailOldMove == -1) {
6916 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6920 if (currentMove > cmailOldMove + 1) {
6921 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6925 if (currentMove < cmailOldMove) {
6926 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6930 if (forwardMostMove > currentMove) {
6931 /* Silently truncate extra moves */
6935 if ( (currentMove == cmailOldMove + 1)
6936 || ( (currentMove == cmailOldMove)
6937 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6938 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6939 if (gameInfo.result != GameUnfinished) {
6940 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6943 if (commentList[currentMove] != NULL) {
6944 cmailCommentList[lastLoadGameNumber - 1]
6945 = StrSave(commentList[currentMove]);
6947 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6949 if (appData.debugMode)
6950 fprintf(debugFP, "Saving %s for game %d\n",
6951 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6954 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6956 f = fopen(string, "w");
6957 if (appData.oldSaveStyle) {
6958 SaveGameOldStyle(f); /* also closes the file */
6960 sprintf(string, "%s.pos.out", appData.cmailGameName);
6961 f = fopen(string, "w");
6962 SavePosition(f, 0, NULL); /* also closes the file */
6964 fprintf(f, "{--------------\n");
6965 PrintPosition(f, currentMove);
6966 fprintf(f, "--------------}\n\n");
6968 SaveGame(f, 0, NULL); /* also closes the file*/
6971 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6972 nCmailMovesRegistered ++;
6973 } else if (nCmailGames == 1) {
6974 DisplayError(_("You have not made a move yet"), 0);
6985 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6986 FILE *commandOutput;
6987 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6988 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6994 if (! cmailMsgLoaded) {
6995 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
6999 if (nCmailGames == nCmailResults) {
7000 DisplayError(_("No unfinished games"), 0);
7004 #if CMAIL_PROHIBIT_REMAIL
7005 if (cmailMailedMove) {
7006 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);
7007 DisplayError(msg, 0);
7012 if (! (cmailMailedMove || RegisterMove())) return;
7014 if ( cmailMailedMove
7015 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7016 sprintf(string, partCommandString,
7017 appData.debugMode ? " -v" : "", appData.cmailGameName);
7018 commandOutput = popen(string, "r");
7020 if (commandOutput == NULL) {
7021 DisplayError(_("Failed to invoke cmail"), 0);
7023 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7024 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7027 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7028 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7029 nBytes = MSG_SIZ - 1;
7031 (void) memcpy(msg, buffer, nBytes);
7033 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7035 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7036 cmailMailedMove = TRUE; /* Prevent >1 moves */
7039 for (i = 0; i < nCmailGames; i ++) {
7040 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7045 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7047 sprintf(buffer, "%s/%s.%s.archive",
7049 appData.cmailGameName,
7051 LoadGameFromFile(buffer, 1, buffer, FALSE);
7052 cmailMsgLoaded = FALSE;
7056 DisplayInformation(msg);
7057 pclose(commandOutput);
7060 if ((*cmailMsg) != '\0') {
7061 DisplayInformation(cmailMsg);
7075 int prependComma = 0;
7077 char string[MSG_SIZ]; /* Space for game-list */
7080 if (!cmailMsgLoaded) return "";
7082 if (cmailMailedMove) {
7083 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7085 /* Create a list of games left */
7086 sprintf(string, "[");
7087 for (i = 0; i < nCmailGames; i ++) {
7088 if (! ( cmailMoveRegistered[i]
7089 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7091 sprintf(number, ",%d", i + 1);
7093 sprintf(number, "%d", i + 1);
7097 strcat(string, number);
7100 strcat(string, "]");
7102 if (nCmailMovesRegistered + nCmailResults == 0) {
7103 switch (nCmailGames) {
7106 _("Still need to make move for game\n"));
7111 _("Still need to make moves for both games\n"));
7116 _("Still need to make moves for all %d games\n"),
7121 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7124 _("Still need to make a move for game %s\n"),
7129 if (nCmailResults == nCmailGames) {
7130 sprintf(cmailMsg, _("No unfinished games\n"));
7132 sprintf(cmailMsg, _("Ready to send mail\n"));
7138 _("Still need to make moves for games %s\n"),
7150 if (gameMode == Training)
7151 SetTrainingModeOff();
7154 cmailMsgLoaded = FALSE;
7155 if (appData.icsActive) {
7156 SendToICS(ics_prefix);
7157 SendToICS("refresh\n");
7161 static int exiting = 0;
7169 /* Give up on clean exit */
7173 /* Keep trying for clean exit */
7177 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7179 if (telnetISR != NULL) {
7180 RemoveInputSource(telnetISR);
7182 if (icsPR != NoProc) {
7183 DestroyChildProcess(icsPR, TRUE);
7185 /* Save game if resource set and not already saved by GameEnds() */
7186 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7187 if (*appData.saveGameFile != NULLCHAR) {
7188 SaveGameToFile(appData.saveGameFile, TRUE);
7189 } else if (appData.autoSaveGames) {
7192 if (*appData.savePositionFile != NULLCHAR) {
7193 SavePositionToFile(appData.savePositionFile);
7196 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7198 /* Kill off chess programs */
7199 if (first.pr != NoProc) {
7201 SendToProgram("quit\n", &first);
7202 DestroyChildProcess(first.pr, first.useSigterm);
7204 if (second.pr != NoProc) {
7205 SendToProgram("quit\n", &second);
7206 DestroyChildProcess(second.pr, second.useSigterm);
7208 if (first.isr != NULL) {
7209 RemoveInputSource(first.isr);
7211 if (second.isr != NULL) {
7212 RemoveInputSource(second.isr);
7222 if (appData.debugMode)
7223 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7227 if (gameMode == MachinePlaysWhite ||
7228 gameMode == MachinePlaysBlack) {
7231 DisplayBothClocks();
7233 if (gameMode == PlayFromGameFile) {
7234 if (appData.timeDelay >= 0)
7236 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7238 SendToICS(ics_prefix);
7239 SendToICS("refresh\n");
7240 } else if (currentMove < forwardMostMove) {
7241 ForwardInner(forwardMostMove);
7243 pauseExamInvalid = FALSE;
7249 pauseExamForwardMostMove = forwardMostMove;
7250 pauseExamInvalid = FALSE;
7253 case IcsPlayingWhite:
7254 case IcsPlayingBlack:
7258 case PlayFromGameFile:
7259 (void) StopLoadGameTimer();
7263 case BeginningOfGame:
7264 if (appData.icsActive) return;
7265 /* else fall through */
7266 case MachinePlaysWhite:
7267 case MachinePlaysBlack:
7268 case TwoMachinesPlay:
7269 if (forwardMostMove == 0)
7270 return; /* don't pause if no one has moved */
7271 if ((gameMode == MachinePlaysWhite &&
7272 !WhiteOnMove(forwardMostMove)) ||
7273 (gameMode == MachinePlaysBlack &&
7274 WhiteOnMove(forwardMostMove))) {
7287 char title[MSG_SIZ];
7289 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7290 strcpy(title, _("Edit comment"));
7292 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7293 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7294 parseList[currentMove - 1]);
7297 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7304 char *tags = PGNTags(&gameInfo);
7305 EditTagsPopUp(tags);
7312 if (appData.noChessProgram || gameMode == AnalyzeMode)
7315 if (gameMode != AnalyzeFile) {
7316 if (!appData.icsEngineAnalyze) {
7318 if (gameMode != EditGame) return;
7320 ResurrectChessProgram();
7321 SendToProgram("analyze\n", &first);
7322 first.analyzing = TRUE;
7323 /*first.maybeThinking = TRUE;*/
7324 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7325 AnalysisPopUp(_("Analysis"),
7326 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7328 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
7333 StartAnalysisClock();
7334 GetTimeMark(&lastNodeCountTime);
7341 if (appData.noChessProgram || gameMode == AnalyzeFile)
7344 if (gameMode != AnalyzeMode) {
7346 if (gameMode != EditGame) return;
7347 ResurrectChessProgram();
7348 SendToProgram("analyze\n", &first);
7349 first.analyzing = TRUE;
7350 /*first.maybeThinking = TRUE;*/
7351 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7352 AnalysisPopUp(_("Analysis"),
7353 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7355 gameMode = AnalyzeFile;
7360 StartAnalysisClock();
7361 GetTimeMark(&lastNodeCountTime);
7370 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7374 if (gameMode == PlayFromGameFile ||
7375 gameMode == TwoMachinesPlay ||
7376 gameMode == Training ||
7377 gameMode == AnalyzeMode ||
7378 gameMode == EndOfGame)
7381 if (gameMode == EditPosition)
7384 if (!WhiteOnMove(currentMove)) {
7385 DisplayError(_("It is not White's turn"), 0);
7389 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7392 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7393 gameMode == AnalyzeFile)
7396 ResurrectChessProgram(); /* in case it isn't running */
7397 gameMode = MachinePlaysWhite;
7401 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7403 if (first.sendName) {
7404 sprintf(buf, "name %s\n", gameInfo.black);
7405 SendToProgram(buf, &first);
7407 if (first.sendTime) {
7408 if (first.useColors) {
7409 SendToProgram("black\n", &first); /*gnu kludge*/
7411 SendTimeRemaining(&first, TRUE);
7413 if (first.useColors) {
7414 SendToProgram("white\ngo\n", &first);
7416 SendToProgram("go\n", &first);
7418 SetMachineThinkingEnables();
7419 first.maybeThinking = TRUE;
7422 if (appData.autoFlipView && !flipView) {
7423 flipView = !flipView;
7424 DrawPosition(FALSE, NULL);
7433 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7437 if (gameMode == PlayFromGameFile ||
7438 gameMode == TwoMachinesPlay ||
7439 gameMode == Training ||
7440 gameMode == AnalyzeMode ||
7441 gameMode == EndOfGame)
7444 if (gameMode == EditPosition)
7447 if (WhiteOnMove(currentMove)) {
7448 DisplayError(_("It is not Black's turn"), 0);
7452 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7455 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7456 gameMode == AnalyzeFile)
7459 ResurrectChessProgram(); /* in case it isn't running */
7460 gameMode = MachinePlaysBlack;
7464 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7466 if (first.sendName) {
7467 sprintf(buf, "name %s\n", gameInfo.white);
7468 SendToProgram(buf, &first);
7470 if (first.sendTime) {
7471 if (first.useColors) {
7472 SendToProgram("white\n", &first); /*gnu kludge*/
7474 SendTimeRemaining(&first, FALSE);
7476 if (first.useColors) {
7477 SendToProgram("black\ngo\n", &first);
7479 SendToProgram("go\n", &first);
7481 SetMachineThinkingEnables();
7482 first.maybeThinking = TRUE;
7485 if (appData.autoFlipView && flipView) {
7486 flipView = !flipView;
7487 DrawPosition(FALSE, NULL);
7493 DisplayTwoMachinesTitle()
7496 if (appData.matchGames > 0) {
7497 if (first.twoMachinesColor[0] == 'w') {
7498 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7499 gameInfo.white, gameInfo.black,
7500 first.matchWins, second.matchWins,
7501 matchGame - 1 - (first.matchWins + second.matchWins));
7503 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7504 gameInfo.white, gameInfo.black,
7505 second.matchWins, first.matchWins,
7506 matchGame - 1 - (first.matchWins + second.matchWins));
7509 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7515 TwoMachinesEvent P((void))
7519 ChessProgramState *onmove;
7521 if (appData.noChessProgram) return;
7524 case TwoMachinesPlay:
7526 case MachinePlaysWhite:
7527 case MachinePlaysBlack:
7528 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7529 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7533 case BeginningOfGame:
7534 case PlayFromGameFile:
7537 if (gameMode != EditGame) return;
7551 forwardMostMove = currentMove;
7552 ResurrectChessProgram(); /* in case first program isn't running */
7554 if (second.pr == NULL) {
7555 StartChessProgram(&second);
7556 if (second.protocolVersion == 1) {
7557 TwoMachinesEventIfReady();
7559 /* kludge: allow timeout for initial "feature" command */
7561 DisplayMessage("", _("Starting second chess program"));
7562 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7566 DisplayMessage("", "");
7567 InitChessProgram(&second);
7568 SendToProgram("force\n", &second);
7569 if (startedFromSetupPosition) {
7570 SendBoard(&second, backwardMostMove);
7572 for (i = backwardMostMove; i < forwardMostMove; i++) {
7573 SendMoveToProgram(i, &second);
7576 gameMode = TwoMachinesPlay;
7580 DisplayTwoMachinesTitle();
7582 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7588 SendToProgram(first.computerString, &first);
7589 if (first.sendName) {
7590 sprintf(buf, "name %s\n", second.tidy);
7591 SendToProgram(buf, &first);
7593 SendToProgram(second.computerString, &second);
7594 if (second.sendName) {
7595 sprintf(buf, "name %s\n", first.tidy);
7596 SendToProgram(buf, &second);
7599 if (!first.sendTime || !second.sendTime) {
7601 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7602 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7604 if (onmove->sendTime) {
7605 if (onmove->useColors) {
7606 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7608 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7610 if (onmove->useColors) {
7611 SendToProgram(onmove->twoMachinesColor, onmove);
7613 SendToProgram("go\n", onmove);
7614 onmove->maybeThinking = TRUE;
7615 SetMachineThinkingEnables();
7623 if (gameMode == Training) {
7624 SetTrainingModeOff();
7625 gameMode = PlayFromGameFile;
7626 DisplayMessage("", _("Training mode off"));
7628 gameMode = Training;
7629 animateTraining = appData.animate;
7631 /* make sure we are not already at the end of the game */
7632 if (currentMove < forwardMostMove) {
7633 SetTrainingModeOn();
7634 DisplayMessage("", _("Training mode on"));
7636 gameMode = PlayFromGameFile;
7637 DisplayError(_("Already at end of game"), 0);
7646 if (!appData.icsActive) return;
7648 case IcsPlayingWhite:
7649 case IcsPlayingBlack:
7652 case BeginningOfGame:
7686 SetTrainingModeOff();
7688 case MachinePlaysWhite:
7689 case MachinePlaysBlack:
7690 case BeginningOfGame:
7691 SendToProgram("force\n", &first);
7692 SetUserThinkingEnables();
7694 case PlayFromGameFile:
7695 (void) StopLoadGameTimer();
7696 if (gameFileFP != NULL) {
7706 SendToProgram("force\n", &first);
7708 case TwoMachinesPlay:
7709 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7710 ResurrectChessProgram();
7711 SetUserThinkingEnables();
7714 ResurrectChessProgram();
7716 case IcsPlayingBlack:
7717 case IcsPlayingWhite:
7718 DisplayError(_("Warning: You are still playing a game"), 0);
7721 DisplayError(_("Warning: You are still observing a game"), 0);
7724 DisplayError(_("Warning: You are still examining a game"), 0);
7735 first.offeredDraw = second.offeredDraw = 0;
7737 if (gameMode == PlayFromGameFile) {
7738 whiteTimeRemaining = timeRemaining[0][currentMove];
7739 blackTimeRemaining = timeRemaining[1][currentMove];
7743 if (gameMode == MachinePlaysWhite ||
7744 gameMode == MachinePlaysBlack ||
7745 gameMode == TwoMachinesPlay ||
7746 gameMode == EndOfGame) {
7747 i = forwardMostMove;
7748 while (i > currentMove) {
7749 SendToProgram("undo\n", &first);
7752 whiteTimeRemaining = timeRemaining[0][currentMove];
7753 blackTimeRemaining = timeRemaining[1][currentMove];
7754 DisplayBothClocks();
7755 if (whiteFlag || blackFlag) {
7756 whiteFlag = blackFlag = 0;
7761 gameMode = EditGame;
7770 if (gameMode == EditPosition) {
7776 if (gameMode != EditGame) return;
7778 gameMode = EditPosition;
7781 if (currentMove > 0)
7782 CopyBoard(boards[0], boards[currentMove]);
7784 blackPlaysFirst = !WhiteOnMove(currentMove);
7786 currentMove = forwardMostMove = backwardMostMove = 0;
7787 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7794 /* icsEngineAnalyze - possible call from other functions */
7795 if (appData.icsEngineAnalyze) {
7796 appData.icsEngineAnalyze = FALSE;
7797 DisplayMessage("","Close ICS engine analyze...");
7799 if (first.analysisSupport && first.analyzing) {
7800 SendToProgram("exit\n", &first);
7801 first.analyzing = FALSE;
7804 thinkOutput[0] = NULLCHAR;
7810 startedFromSetupPosition = TRUE;
7811 InitChessProgram(&first);
7812 SendToProgram("force\n", &first);
7813 if (blackPlaysFirst) {
7814 strcpy(moveList[0], "");
7815 strcpy(parseList[0], "");
7816 currentMove = forwardMostMove = backwardMostMove = 1;
7817 CopyBoard(boards[1], boards[0]);
7819 currentMove = forwardMostMove = backwardMostMove = 0;
7821 SendBoard(&first, forwardMostMove);
7823 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7824 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7825 gameMode = EditGame;
7827 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7830 /* Pause for `ms' milliseconds */
7831 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7841 } while (SubtractTimeMarks(&m2, &m1) < ms);
7844 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7846 SendMultiLineToICS(buf)
7849 char temp[MSG_SIZ+1], *p;
7856 strncpy(temp, buf, len);
7861 if (*p == '\n' || *p == '\r')
7868 SendToPlayer(temp, strlen(temp));
7872 SetWhiteToPlayEvent()
7874 if (gameMode == EditPosition) {
7875 blackPlaysFirst = FALSE;
7876 DisplayBothClocks(); /* works because currentMove is 0 */
7877 } else if (gameMode == IcsExamining) {
7878 SendToICS(ics_prefix);
7879 SendToICS("tomove white\n");
7884 SetBlackToPlayEvent()
7886 if (gameMode == EditPosition) {
7887 blackPlaysFirst = TRUE;
7888 currentMove = 1; /* kludge */
7889 DisplayBothClocks();
7891 } else if (gameMode == IcsExamining) {
7892 SendToICS(ics_prefix);
7893 SendToICS("tomove black\n");
7898 EditPositionMenuEvent(selection, x, y)
7899 ChessSquare selection;
7904 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7906 switch (selection) {
7908 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7909 SendToICS(ics_prefix);
7910 SendToICS("bsetup clear\n");
7911 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7912 SendToICS(ics_prefix);
7913 SendToICS("clearboard\n");
7915 for (x = 0; x < BOARD_SIZE; x++) {
7916 for (y = 0; y < BOARD_SIZE; y++) {
7917 if (gameMode == IcsExamining) {
7918 if (boards[currentMove][y][x] != EmptySquare) {
7919 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7924 boards[0][y][x] = EmptySquare;
7929 if (gameMode == EditPosition) {
7930 DrawPosition(FALSE, boards[0]);
7935 SetWhiteToPlayEvent();
7939 SetBlackToPlayEvent();
7943 if (gameMode == IcsExamining) {
7944 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7947 boards[0][y][x] = EmptySquare;
7948 DrawPosition(FALSE, boards[0]);
7953 if (gameMode == IcsExamining) {
7954 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7955 PieceToChar(selection), 'a' + x, '1' + y);
7958 boards[0][y][x] = selection;
7959 DrawPosition(FALSE, boards[0]);
7967 DropMenuEvent(selection, x, y)
7968 ChessSquare selection;
7974 case IcsPlayingWhite:
7975 case MachinePlaysBlack:
7976 if (!WhiteOnMove(currentMove)) {
7977 DisplayMoveError(_("It is Black's turn"));
7980 moveType = WhiteDrop;
7982 case IcsPlayingBlack:
7983 case MachinePlaysWhite:
7984 if (WhiteOnMove(currentMove)) {
7985 DisplayMoveError(_("It is White's turn"));
7988 moveType = BlackDrop;
7991 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7997 if (moveType == BlackDrop && selection < BlackPawn) {
7998 selection = (ChessSquare) ((int) selection
7999 + (int) BlackPawn - (int) WhitePawn);
8001 if (boards[currentMove][y][x] != EmptySquare) {
8002 DisplayMoveError(_("That square is occupied"));
8006 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8012 /* Accept a pending offer of any kind from opponent */
8014 if (appData.icsActive) {
8015 SendToICS(ics_prefix);
8016 SendToICS("accept\n");
8017 } else if (cmailMsgLoaded) {
8018 if (currentMove == cmailOldMove &&
8019 commentList[cmailOldMove] != NULL &&
8020 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8021 "Black offers a draw" : "White offers a draw")) {
8023 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8024 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8026 DisplayError(_("There is no pending offer on this move"), 0);
8027 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8030 /* Not used for offers from chess program */
8037 /* Decline a pending offer of any kind from opponent */
8039 if (appData.icsActive) {
8040 SendToICS(ics_prefix);
8041 SendToICS("decline\n");
8042 } else if (cmailMsgLoaded) {
8043 if (currentMove == cmailOldMove &&
8044 commentList[cmailOldMove] != NULL &&
8045 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8046 "Black offers a draw" : "White offers a draw")) {
8048 AppendComment(cmailOldMove, "Draw declined");
8049 DisplayComment(cmailOldMove - 1, "Draw declined");
8052 DisplayError(_("There is no pending offer on this move"), 0);
8055 /* Not used for offers from chess program */
8062 /* Issue ICS rematch command */
8063 if (appData.icsActive) {
8064 SendToICS(ics_prefix);
8065 SendToICS("rematch\n");
8072 /* Call your opponent's flag (claim a win on time) */
8073 if (appData.icsActive) {
8074 SendToICS(ics_prefix);
8075 SendToICS("flag\n");
8080 case MachinePlaysWhite:
8083 GameEnds(GameIsDrawn, "Both players ran out of time",
8086 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8088 DisplayError(_("Your opponent is not out of time"), 0);
8091 case MachinePlaysBlack:
8094 GameEnds(GameIsDrawn, "Both players ran out of time",
8097 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8099 DisplayError(_("Your opponent is not out of time"), 0);
8109 /* Offer draw or accept pending draw offer from opponent */
8111 if (appData.icsActive) {
8112 /* Note: tournament rules require draw offers to be
8113 made after you make your move but before you punch
8114 your clock. Currently ICS doesn't let you do that;
8115 instead, you immediately punch your clock after making
8116 a move, but you can offer a draw at any time. */
8118 SendToICS(ics_prefix);
8119 SendToICS("draw\n");
8120 } else if (cmailMsgLoaded) {
8121 if (currentMove == cmailOldMove &&
8122 commentList[cmailOldMove] != NULL &&
8123 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8124 "Black offers a draw" : "White offers a draw")) {
8125 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8127 } else if (currentMove == cmailOldMove + 1) {
8128 char *offer = WhiteOnMove(cmailOldMove) ?
8129 "White offers a draw" : "Black offers a draw";
8130 AppendComment(currentMove, offer);
8131 DisplayComment(currentMove - 1, offer);
8132 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8134 DisplayError(_("You must make your move before offering a draw"), 0);
8135 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8137 } else if (first.offeredDraw) {
8138 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8140 if (first.sendDrawOffers) {
8141 SendToProgram("draw\n", &first);
8142 userOfferedDraw = TRUE;
8150 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8152 if (appData.icsActive) {
8153 SendToICS(ics_prefix);
8154 SendToICS("adjourn\n");
8156 /* Currently GNU Chess doesn't offer or accept Adjourns */
8164 /* Offer Abort or accept pending Abort offer from opponent */
8166 if (appData.icsActive) {
8167 SendToICS(ics_prefix);
8168 SendToICS("abort\n");
8170 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8177 /* Resign. You can do this even if it's not your turn. */
8179 if (appData.icsActive) {
8180 SendToICS(ics_prefix);
8181 SendToICS("resign\n");
8184 case MachinePlaysWhite:
8185 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8187 case MachinePlaysBlack:
8188 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8191 if (cmailMsgLoaded) {
8193 if (WhiteOnMove(cmailOldMove)) {
8194 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8196 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8198 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8209 StopObservingEvent()
8211 /* Stop observing current games */
8212 SendToICS(ics_prefix);
8213 SendToICS("unobserve\n");
8217 StopExaminingEvent()
8219 /* Stop observing current game */
8220 SendToICS(ics_prefix);
8221 SendToICS("unexamine\n");
8225 ForwardInner(target)
8230 if (appData.debugMode)
8231 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8232 target, currentMove, forwardMostMove);
8234 if (gameMode == EditPosition)
8237 if (gameMode == PlayFromGameFile && !pausing)
8240 if (gameMode == IcsExamining && pausing)
8241 limit = pauseExamForwardMostMove;
8243 limit = forwardMostMove;
8245 if (target > limit) target = limit;
8247 if (target > 0 && moveList[target - 1][0]) {
8248 int fromX, fromY, toX, toY;
8249 toX = moveList[target - 1][2] - 'a';
8250 toY = moveList[target - 1][3] - '1';
8251 if (moveList[target - 1][1] == '@') {
8252 if (appData.highlightLastMove) {
8253 SetHighlights(-1, -1, toX, toY);
8256 fromX = moveList[target - 1][0] - 'a';
8257 fromY = moveList[target - 1][1] - '1';
8258 if (target == currentMove + 1) {
8259 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8261 if (appData.highlightLastMove) {
8262 SetHighlights(fromX, fromY, toX, toY);
8266 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8267 gameMode == Training || gameMode == PlayFromGameFile ||
8268 gameMode == AnalyzeFile) {
8269 while (currentMove < target) {
8270 SendMoveToProgram(currentMove++, &first);
8273 currentMove = target;
8276 if (gameMode == EditGame || gameMode == EndOfGame) {
8277 whiteTimeRemaining = timeRemaining[0][currentMove];
8278 blackTimeRemaining = timeRemaining[1][currentMove];
8280 DisplayBothClocks();
8281 DisplayMove(currentMove - 1);
8282 DrawPosition(FALSE, boards[currentMove]);
8283 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8284 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8285 DisplayComment(currentMove - 1, commentList[currentMove]);
8293 if (gameMode == IcsExamining && !pausing) {
8294 SendToICS(ics_prefix);
8295 SendToICS("forward\n");
8297 ForwardInner(currentMove + 1);
8304 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8305 /* to optimze, we temporarily turn off analysis mode while we feed
8306 * the remaining moves to the engine. Otherwise we get analysis output
8309 if (first.analysisSupport) {
8310 SendToProgram("exit\nforce\n", &first);
8311 first.analyzing = FALSE;
8315 if (gameMode == IcsExamining && !pausing) {
8316 SendToICS(ics_prefix);
8317 SendToICS("forward 999999\n");
8319 ForwardInner(forwardMostMove);
8322 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8323 /* we have fed all the moves, so reactivate analysis mode */
8324 SendToProgram("analyze\n", &first);
8325 first.analyzing = TRUE;
8326 /*first.maybeThinking = TRUE;*/
8327 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8332 BackwardInner(target)
8335 if (appData.debugMode)
8336 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8337 target, currentMove, forwardMostMove);
8339 if (gameMode == EditPosition) return;
8340 if (currentMove <= backwardMostMove) {
8342 DrawPosition(FALSE, boards[currentMove]);
8345 if (gameMode == PlayFromGameFile && !pausing)
8348 if (moveList[target][0]) {
8349 int fromX, fromY, toX, toY;
8350 toX = moveList[target][2] - 'a';
8351 toY = moveList[target][3] - '1';
8352 if (moveList[target][1] == '@') {
8353 if (appData.highlightLastMove) {
8354 SetHighlights(-1, -1, toX, toY);
8357 fromX = moveList[target][0] - 'a';
8358 fromY = moveList[target][1] - '1';
8359 if (target == currentMove - 1) {
8360 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8362 if (appData.highlightLastMove) {
8363 SetHighlights(fromX, fromY, toX, toY);
8367 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8368 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8369 while (currentMove > target) {
8370 SendToProgram("undo\n", &first);
8374 currentMove = target;
8377 if (gameMode == EditGame || gameMode == EndOfGame) {
8378 whiteTimeRemaining = timeRemaining[0][currentMove];
8379 blackTimeRemaining = timeRemaining[1][currentMove];
8381 DisplayBothClocks();
8382 DisplayMove(currentMove - 1);
8383 DrawPosition(FALSE, boards[currentMove]);
8384 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8385 if (commentList[currentMove] != NULL) {
8386 DisplayComment(currentMove - 1, commentList[currentMove]);
8393 if (gameMode == IcsExamining && !pausing) {
8394 SendToICS(ics_prefix);
8395 SendToICS("backward\n");
8397 BackwardInner(currentMove - 1);
8404 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8405 /* to optimze, we temporarily turn off analysis mode while we undo
8406 * all the moves. Otherwise we get analysis output after each undo.
8408 if (first.analysisSupport) {
8409 SendToProgram("exit\nforce\n", &first);
8410 first.analyzing = FALSE;
8414 if (gameMode == IcsExamining && !pausing) {
8415 SendToICS(ics_prefix);
8416 SendToICS("backward 999999\n");
8418 BackwardInner(backwardMostMove);
8421 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8422 /* we have fed all the moves, so reactivate analysis mode */
8423 SendToProgram("analyze\n", &first);
8424 first.analyzing = TRUE;
8425 /*first.maybeThinking = TRUE;*/
8426 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8433 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8434 if (to >= forwardMostMove) to = forwardMostMove;
8435 if (to <= backwardMostMove) to = backwardMostMove;
8436 if (to < currentMove) {
8446 if (gameMode != IcsExamining) {
8447 DisplayError(_("You are not examining a game"), 0);
8451 DisplayError(_("You can't revert while pausing"), 0);
8454 SendToICS(ics_prefix);
8455 SendToICS("revert\n");
8462 case MachinePlaysWhite:
8463 case MachinePlaysBlack:
8464 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8465 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8468 if (forwardMostMove < 2) return;
8469 currentMove = forwardMostMove = forwardMostMove - 2;
8470 whiteTimeRemaining = timeRemaining[0][currentMove];
8471 blackTimeRemaining = timeRemaining[1][currentMove];
8472 DisplayBothClocks();
8473 DisplayMove(currentMove - 1);
8474 ClearHighlights();/*!! could figure this out*/
8475 DrawPosition(FALSE, boards[currentMove]);
8476 SendToProgram("remove\n", &first);
8477 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8480 case BeginningOfGame:
8484 case IcsPlayingWhite:
8485 case IcsPlayingBlack:
8486 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8487 SendToICS(ics_prefix);
8488 SendToICS("takeback 2\n");
8490 SendToICS(ics_prefix);
8491 SendToICS("takeback 1\n");
8500 ChessProgramState *cps;
8503 case MachinePlaysWhite:
8504 if (!WhiteOnMove(forwardMostMove)) {
8505 DisplayError(_("It is your turn"), 0);
8510 case MachinePlaysBlack:
8511 if (WhiteOnMove(forwardMostMove)) {
8512 DisplayError(_("It is your turn"), 0);
8517 case TwoMachinesPlay:
8518 if (WhiteOnMove(forwardMostMove) ==
8519 (first.twoMachinesColor[0] == 'w')) {
8525 case BeginningOfGame:
8529 SendToProgram("?\n", cps);
8536 if (gameMode != EditGame) return;
8543 if (forwardMostMove > currentMove) {
8544 if (gameInfo.resultDetails != NULL) {
8545 free(gameInfo.resultDetails);
8546 gameInfo.resultDetails = NULL;
8547 gameInfo.result = GameUnfinished;
8549 forwardMostMove = currentMove;
8550 HistorySet(parseList, backwardMostMove, forwardMostMove,
8558 if (appData.noChessProgram) return;
8560 case MachinePlaysWhite:
8561 if (WhiteOnMove(forwardMostMove)) {
8562 DisplayError(_("Wait until your turn"), 0);
8566 case BeginningOfGame:
8567 case MachinePlaysBlack:
8568 if (!WhiteOnMove(forwardMostMove)) {
8569 DisplayError(_("Wait until your turn"), 0);
8574 DisplayError(_("No hint available"), 0);
8577 SendToProgram("hint\n", &first);
8578 hintRequested = TRUE;
8584 if (appData.noChessProgram) return;
8586 case MachinePlaysWhite:
8587 if (WhiteOnMove(forwardMostMove)) {
8588 DisplayError(_("Wait until your turn"), 0);
8592 case BeginningOfGame:
8593 case MachinePlaysBlack:
8594 if (!WhiteOnMove(forwardMostMove)) {
8595 DisplayError(_("Wait until your turn"), 0);
8602 case TwoMachinesPlay:
8607 SendToProgram("bk\n", &first);
8608 bookOutput[0] = NULLCHAR;
8609 bookRequested = TRUE;
8615 char *tags = PGNTags(&gameInfo);
8616 TagsPopUp(tags, CmailMsg());
8620 /* end button procedures */
8623 PrintPosition(fp, move)
8629 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8630 for (j = 0; j < BOARD_SIZE; j++) {
8631 char c = PieceToChar(boards[move][i][j]);
8632 fputc(c == 'x' ? '.' : c, fp);
8633 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8636 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8637 fprintf(fp, "white to play\n");
8639 fprintf(fp, "black to play\n");
8646 if (gameInfo.white != NULL) {
8647 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8653 /* Find last component of program's own name, using some heuristics */
8655 TidyProgramName(prog, host, buf)
8656 char *prog, *host, buf[MSG_SIZ];
8659 int local = (strcmp(host, "localhost") == 0);
8660 while (!local && (p = strchr(prog, ';')) != NULL) {
8662 while (*p == ' ') p++;
8665 if (*prog == '"' || *prog == '\'') {
8666 q = strchr(prog + 1, *prog);
8668 q = strchr(prog, ' ');
8670 if (q == NULL) q = prog + strlen(prog);
8672 while (p >= prog && *p != '/' && *p != '\\') p--;
8674 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8675 memcpy(buf, p, q - p);
8676 buf[q - p] = NULLCHAR;
8684 TimeControlTagValue()
8687 if (!appData.clockMode) {
8689 } else if (movesPerSession > 0) {
8690 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8691 } else if (timeIncrement == 0) {
8692 sprintf(buf, "%ld", timeControl/1000);
8694 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8696 return StrSave(buf);
8702 /* This routine is used only for certain modes */
8703 VariantClass v = gameInfo.variant;
8704 ClearGameInfo(&gameInfo);
8705 gameInfo.variant = v;
8708 case MachinePlaysWhite:
8709 gameInfo.event = StrSave("Computer chess game");
8710 gameInfo.site = StrSave(HostName());
8711 gameInfo.date = PGNDate();
8712 gameInfo.round = StrSave("-");
8713 gameInfo.white = StrSave(first.tidy);
8714 gameInfo.black = StrSave(UserName());
8715 gameInfo.timeControl = TimeControlTagValue();
8718 case MachinePlaysBlack:
8719 gameInfo.event = StrSave("Computer chess game");
8720 gameInfo.site = StrSave(HostName());
8721 gameInfo.date = PGNDate();
8722 gameInfo.round = StrSave("-");
8723 gameInfo.white = StrSave(UserName());
8724 gameInfo.black = StrSave(first.tidy);
8725 gameInfo.timeControl = TimeControlTagValue();
8728 case TwoMachinesPlay:
8729 gameInfo.event = StrSave("Computer chess game");
8730 gameInfo.site = StrSave(HostName());
8731 gameInfo.date = PGNDate();
8732 if (matchGame > 0) {
8734 sprintf(buf, "%d", matchGame);
8735 gameInfo.round = StrSave(buf);
8737 gameInfo.round = StrSave("-");
8739 if (first.twoMachinesColor[0] == 'w') {
8740 gameInfo.white = StrSave(first.tidy);
8741 gameInfo.black = StrSave(second.tidy);
8743 gameInfo.white = StrSave(second.tidy);
8744 gameInfo.black = StrSave(first.tidy);
8746 gameInfo.timeControl = TimeControlTagValue();
8750 gameInfo.event = StrSave("Edited game");
8751 gameInfo.site = StrSave(HostName());
8752 gameInfo.date = PGNDate();
8753 gameInfo.round = StrSave("-");
8754 gameInfo.white = StrSave("-");
8755 gameInfo.black = StrSave("-");
8759 gameInfo.event = StrSave("Edited position");
8760 gameInfo.site = StrSave(HostName());
8761 gameInfo.date = PGNDate();
8762 gameInfo.round = StrSave("-");
8763 gameInfo.white = StrSave("-");
8764 gameInfo.black = StrSave("-");
8767 case IcsPlayingWhite:
8768 case IcsPlayingBlack:
8773 case PlayFromGameFile:
8774 gameInfo.event = StrSave("Game from non-PGN file");
8775 gameInfo.site = StrSave(HostName());
8776 gameInfo.date = PGNDate();
8777 gameInfo.round = StrSave("-");
8778 gameInfo.white = StrSave("?");
8779 gameInfo.black = StrSave("?");
8788 ReplaceComment(index, text)
8794 while (*text == '\n') text++;
8796 while (len > 0 && text[len - 1] == '\n') len--;
8798 if (commentList[index] != NULL)
8799 free(commentList[index]);
8802 commentList[index] = NULL;
8805 commentList[index] = (char *) malloc(len + 2);
8806 strncpy(commentList[index], text, len);
8807 commentList[index][len] = '\n';
8808 commentList[index][len + 1] = NULLCHAR;
8821 if (ch == '\r') continue;
8823 } while (ch != '\0');
8827 AppendComment(index, text)
8835 while (*text == '\n') text++;
8837 while (len > 0 && text[len - 1] == '\n') len--;
8839 if (len == 0) return;
8841 if (commentList[index] != NULL) {
8842 old = commentList[index];
8843 oldlen = strlen(old);
8844 commentList[index] = (char *) malloc(oldlen + len + 2);
8845 strcpy(commentList[index], old);
8847 strncpy(&commentList[index][oldlen], text, len);
8848 commentList[index][oldlen + len] = '\n';
8849 commentList[index][oldlen + len + 1] = NULLCHAR;
8851 commentList[index] = (char *) malloc(len + 2);
8852 strncpy(commentList[index], text, len);
8853 commentList[index][len] = '\n';
8854 commentList[index][len + 1] = NULLCHAR;
8859 SendToProgram(message, cps)
8861 ChessProgramState *cps;
8863 int count, outCount, error;
8866 if (cps->pr == NULL) return;
8869 if (appData.debugMode) {
8872 fprintf(debugFP, "%ld >%-6s: %s",
8873 SubtractTimeMarks(&now, &programStartTime),
8874 cps->which, message);
8877 count = strlen(message);
8878 outCount = OutputToProcess(cps->pr, message, count, &error);
8879 if (outCount < count && !exiting) {
8880 sprintf(buf, _("Error writing to %s chess program"), cps->which);
8881 DisplayFatalError(buf, error, 1);
8886 ReceiveFromProgram(isr, closure, message, count, error)
8895 ChessProgramState *cps = (ChessProgramState *)closure;
8897 if (isr != cps->isr) return; /* Killed intentionally */
8901 _("Error: %s chess program (%s) exited unexpectedly"),
8902 cps->which, cps->program);
8903 RemoveInputSource(cps->isr);
8904 DisplayFatalError(buf, 0, 1);
8907 _("Error reading from %s chess program (%s)"),
8908 cps->which, cps->program);
8909 RemoveInputSource(cps->isr);
8910 DisplayFatalError(buf, error, 1);
8912 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8916 if ((end_str = strchr(message, '\r')) != NULL)
8917 *end_str = NULLCHAR;
8918 if ((end_str = strchr(message, '\n')) != NULL)
8919 *end_str = NULLCHAR;
8921 if (appData.debugMode) {
8924 fprintf(debugFP, "%ld <%-6s: %s\n",
8925 SubtractTimeMarks(&now, &programStartTime),
8926 cps->which, message);
8928 /* if icsEngineAnalyze is active we block all
8929 whisper and kibitz output, because nobody want
8932 if (appData.icsEngineAnalyze) {
8933 if (strstr(message, "whisper") != NULL ||
8934 strstr(message, "kibitz") != NULL ||
8935 strstr(message, "tellics") != NULL) return;
8936 HandleMachineMove(message, cps);
8938 HandleMachineMove(message, cps);
8944 SendTimeControl(cps, mps, tc, inc, sd, st)
8945 ChessProgramState *cps;
8946 int mps, inc, sd, st;
8950 int seconds = (tc / 1000) % 60;
8953 /* Set exact time per move, normally using st command */
8954 if (cps->stKludge) {
8955 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8958 sprintf(buf, "level 1 %d\n", st/60);
8960 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8963 sprintf(buf, "st %d\n", st);
8966 /* Set conventional or incremental time control, using level command */
8968 /* Note old gnuchess bug -- minutes:seconds used to not work.
8969 Fixed in later versions, but still avoid :seconds
8970 when seconds is 0. */
8971 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8973 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8977 SendToProgram(buf, cps);
8979 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8980 /* Orthogonally, limit search to given depth */
8982 if (cps->sdKludge) {
8983 sprintf(buf, "depth\n%d\n", sd);
8985 sprintf(buf, "sd %d\n", sd);
8987 SendToProgram(buf, cps);
8992 SendTimeRemaining(cps, machineWhite)
8993 ChessProgramState *cps;
8994 int /*boolean*/ machineWhite;
8996 char message[MSG_SIZ];
8999 /* Note: this routine must be called when the clocks are stopped
9000 or when they have *just* been set or switched; otherwise
9001 it will be off by the time since the current tick started.
9004 time = whiteTimeRemaining / 10;
9005 otime = blackTimeRemaining / 10;
9007 time = blackTimeRemaining / 10;
9008 otime = whiteTimeRemaining / 10;
9010 if (time <= 0) time = 1;
9011 if (otime <= 0) otime = 1;
9013 sprintf(message, "time %ld\notim %ld\n", time, otime);
9014 SendToProgram(message, cps);
9018 BoolFeature(p, name, loc, cps)
9022 ChessProgramState *cps;
9025 int len = strlen(name);
9027 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9029 sscanf(*p, "%d", &val);
9031 while (**p && **p != ' ') (*p)++;
9032 sprintf(buf, "accepted %s\n", name);
9033 SendToProgram(buf, cps);
9040 IntFeature(p, name, loc, cps)
9044 ChessProgramState *cps;
9047 int len = strlen(name);
9048 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9050 sscanf(*p, "%d", loc);
9051 while (**p && **p != ' ') (*p)++;
9052 sprintf(buf, "accepted %s\n", name);
9053 SendToProgram(buf, cps);
9060 StringFeature(p, name, loc, cps)
9064 ChessProgramState *cps;
9067 int len = strlen(name);
9068 if (strncmp((*p), name, len) == 0
9069 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9071 sscanf(*p, "%[^\"]", loc);
9072 while (**p && **p != '\"') (*p)++;
9073 if (**p == '\"') (*p)++;
9074 sprintf(buf, "accepted %s\n", name);
9075 SendToProgram(buf, cps);
9082 FeatureDone(cps, val)
9083 ChessProgramState* cps;
9086 DelayedEventCallback cb = GetDelayedEvent();
9087 if ((cb == InitBackEnd3 && cps == &first) ||
9088 (cb == TwoMachinesEventIfReady && cps == &second)) {
9089 CancelDelayedEvent();
9090 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9092 cps->initDone = val;
9095 /* Parse feature command from engine */
9097 ParseFeatures(args, cps)
9099 ChessProgramState *cps;
9107 while (*p == ' ') p++;
9108 if (*p == NULLCHAR) return;
9110 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9111 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9112 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9113 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9114 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9115 if (BoolFeature(&p, "reuse", &val, cps)) {
9116 /* Engine can disable reuse, but can't enable it if user said no */
9117 if (!val) cps->reuse = FALSE;
9120 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9121 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9122 if (gameMode == TwoMachinesPlay) {
9123 DisplayTwoMachinesTitle();
9129 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9130 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9131 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9132 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9133 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9134 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9135 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9136 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9137 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9138 if (IntFeature(&p, "done", &val, cps)) {
9139 FeatureDone(cps, val);
9143 /* unknown feature: complain and skip */
9145 while (*q && *q != '=') q++;
9146 sprintf(buf, "rejected %.*s\n", q-p, p);
9147 SendToProgram(buf, cps);
9153 while (*p && *p != '\"') p++;
9154 if (*p == '\"') p++;
9156 while (*p && *p != ' ') p++;
9164 PeriodicUpdatesEvent(newState)
9167 if (newState == appData.periodicUpdates)
9170 appData.periodicUpdates=newState;
9172 /* Display type changes, so update it now */
9175 /* Get the ball rolling again... */
9177 AnalysisPeriodicEvent(1);
9178 StartAnalysisClock();
9183 PonderNextMoveEvent(newState)
9186 if (newState == appData.ponderNextMove) return;
9187 if (gameMode == EditPosition) EditPositionDone();
9189 SendToProgram("hard\n", &first);
9190 if (gameMode == TwoMachinesPlay) {
9191 SendToProgram("hard\n", &second);
9194 SendToProgram("easy\n", &first);
9195 thinkOutput[0] = NULLCHAR;
9196 if (gameMode == TwoMachinesPlay) {
9197 SendToProgram("easy\n", &second);
9200 appData.ponderNextMove = newState;
9204 ShowThinkingEvent(newState)
9207 if (newState == appData.showThinking) return;
9208 if (gameMode == EditPosition) EditPositionDone();
9210 SendToProgram("post\n", &first);
9211 if (gameMode == TwoMachinesPlay) {
9212 SendToProgram("post\n", &second);
9215 SendToProgram("nopost\n", &first);
9216 thinkOutput[0] = NULLCHAR;
9217 if (gameMode == TwoMachinesPlay) {
9218 SendToProgram("nopost\n", &second);
9221 appData.showThinking = newState;
9225 AskQuestionEvent(title, question, replyPrefix, which)
9226 char *title; char *question; char *replyPrefix; char *which;
9228 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9229 if (pr == NoProc) return;
9230 AskQuestion(title, question, replyPrefix, pr);
9234 DisplayMove(moveNumber)
9237 char message[MSG_SIZ];
9239 char cpThinkOutput[MSG_SIZ];
9241 if (moveNumber == forwardMostMove - 1 ||
9242 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9244 strcpy(cpThinkOutput, thinkOutput);
9245 if (strchr(cpThinkOutput, '\n'))
9246 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9248 *cpThinkOutput = NULLCHAR;
9251 if (moveNumber == forwardMostMove - 1 &&
9252 gameInfo.resultDetails != NULL) {
9253 if (gameInfo.resultDetails[0] == NULLCHAR) {
9254 sprintf(res, " %s", PGNResult(gameInfo.result));
9256 sprintf(res, " {%s} %s",
9257 gameInfo.resultDetails, PGNResult(gameInfo.result));
9263 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9264 DisplayMessage(res, cpThinkOutput);
9266 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9267 WhiteOnMove(moveNumber) ? " " : ".. ",
9268 parseList[moveNumber], res);
9269 DisplayMessage(message, cpThinkOutput);
9274 DisplayAnalysisText(text)
9279 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
9280 || appData.icsEngineAnalyze) {
9281 sprintf(buf, "Analysis (%s)", first.tidy);
9282 AnalysisPopUp(buf, text);
9290 while (*str && isspace(*str)) ++str;
9291 while (*str && !isspace(*str)) ++str;
9292 if (!*str) return 1;
9293 while (*str && isspace(*str)) ++str;
9294 if (!*str) return 1;
9303 static char *xtra[] = { "", " (--)", " (++)" };
9306 if (programStats.time == 0) {
9307 programStats.time = 1;
9310 if (programStats.got_only_move) {
9311 strcpy(buf, programStats.movelist);
9313 nps = (u64ToDouble(programStats.nodes) /
9314 ((double)programStats.time /100.0));
9316 cs = programStats.time % 100;
9317 s = programStats.time / 100;
9323 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9324 if (programStats.move_name[0] != NULLCHAR) {
9325 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9327 programStats.nr_moves-programStats.moves_left,
9328 programStats.nr_moves, programStats.move_name,
9329 ((float)programStats.score)/100.0, programStats.movelist,
9330 only_one_move(programStats.movelist)?
9331 xtra[programStats.got_fail] : "",
9332 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9334 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9336 programStats.nr_moves-programStats.moves_left,
9337 programStats.nr_moves, ((float)programStats.score)/100.0,
9338 programStats.movelist,
9339 only_one_move(programStats.movelist)?
9340 xtra[programStats.got_fail] : "",
9341 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9344 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9346 ((float)programStats.score)/100.0,
9347 programStats.movelist,
9348 only_one_move(programStats.movelist)?
9349 xtra[programStats.got_fail] : "",
9350 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9353 DisplayAnalysisText(buf);
9357 DisplayComment(moveNumber, text)
9361 char title[MSG_SIZ];
9363 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9364 strcpy(title, "Comment");
9366 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9367 WhiteOnMove(moveNumber) ? " " : ".. ",
9368 parseList[moveNumber]);
9371 CommentPopUp(title, text);
9374 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9375 * might be busy thinking or pondering. It can be omitted if your
9376 * gnuchess is configured to stop thinking immediately on any user
9377 * input. However, that gnuchess feature depends on the FIONREAD
9378 * ioctl, which does not work properly on some flavors of Unix.
9382 ChessProgramState *cps;
9385 if (!cps->useSigint) return;
9386 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9388 case MachinePlaysWhite:
9389 case MachinePlaysBlack:
9390 case TwoMachinesPlay:
9391 case IcsPlayingWhite:
9392 case IcsPlayingBlack:
9395 /* Skip if we know it isn't thinking */
9396 if (!cps->maybeThinking) return;
9397 if (appData.debugMode)
9398 fprintf(debugFP, "Interrupting %s\n", cps->which);
9399 InterruptChildProcess(cps->pr);
9400 cps->maybeThinking = FALSE;
9405 #endif /*ATTENTION*/
9411 if (whiteTimeRemaining <= 0) {
9414 if (appData.icsActive) {
9415 if (appData.autoCallFlag &&
9416 gameMode == IcsPlayingBlack && !blackFlag) {
9417 SendToICS(ics_prefix);
9418 SendToICS("flag\n");
9422 DisplayTitle(_("Both flags fell"));
9424 DisplayTitle(_("White's flag fell"));
9425 if (appData.autoCallFlag) {
9426 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9433 if (blackTimeRemaining <= 0) {
9436 if (appData.icsActive) {
9437 if (appData.autoCallFlag &&
9438 gameMode == IcsPlayingWhite && !whiteFlag) {
9439 SendToICS(ics_prefix);
9440 SendToICS("flag\n");
9444 DisplayTitle(_("Both flags fell"));
9446 DisplayTitle(_("Black's flag fell"));
9447 if (appData.autoCallFlag) {
9448 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9461 if (!appData.clockMode || appData.icsActive ||
9462 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9464 if (timeIncrement >= 0) {
9465 if (WhiteOnMove(forwardMostMove)) {
9466 blackTimeRemaining += timeIncrement;
9468 whiteTimeRemaining += timeIncrement;
9472 * add time to clocks when time control is achieved
9474 if (movesPerSession) {
9475 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9477 /* White made time control */
9478 whiteTimeRemaining += timeControl;
9481 /* Black made time control */
9482 blackTimeRemaining += timeControl;
9493 int wom = gameMode == EditPosition ?
9494 !blackPlaysFirst : WhiteOnMove(currentMove);
9495 DisplayWhiteClock(whiteTimeRemaining, wom);
9496 DisplayBlackClock(blackTimeRemaining, !wom);
9500 /* Timekeeping seems to be a portability nightmare. I think everyone
9501 has ftime(), but I'm really not sure, so I'm including some ifdefs
9502 to use other calls if you don't. Clocks will be less accurate if
9503 you have neither ftime nor gettimeofday.
9506 /* Get the current time as a TimeMark */
9511 #if HAVE_GETTIMEOFDAY
9513 struct timeval timeVal;
9514 struct timezone timeZone;
9516 gettimeofday(&timeVal, &timeZone);
9517 tm->sec = (long) timeVal.tv_sec;
9518 tm->ms = (int) (timeVal.tv_usec / 1000L);
9520 #else /*!HAVE_GETTIMEOFDAY*/
9523 #include <sys/timeb.h>
9527 tm->sec = (long) timeB.time;
9528 tm->ms = (int) timeB.millitm;
9530 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9531 tm->sec = (long) time(NULL);
9537 /* Return the difference in milliseconds between two
9538 time marks. We assume the difference will fit in a long!
9541 SubtractTimeMarks(tm2, tm1)
9542 TimeMark *tm2, *tm1;
9544 return 1000L*(tm2->sec - tm1->sec) +
9545 (long) (tm2->ms - tm1->ms);
9550 * Code to manage the game clocks.
9552 * In tournament play, black starts the clock and then white makes a move.
9553 * We give the human user a slight advantage if he is playing white---the
9554 * clocks don't run until he makes his first move, so it takes zero time.
9555 * Also, we don't account for network lag, so we could get out of sync
9556 * with GNU Chess's clock -- but then, referees are always right.
9559 static TimeMark tickStartTM;
9560 static long intendedTickLength;
9563 NextTickLength(timeRemaining)
9566 long nominalTickLength, nextTickLength;
9568 if (timeRemaining > 0L && timeRemaining <= 10000L)
9569 nominalTickLength = 100L;
9571 nominalTickLength = 1000L;
9572 nextTickLength = timeRemaining % nominalTickLength;
9573 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9575 return nextTickLength;
9578 /* Stop clocks and reset to a fresh time control */
9582 (void) StopClockTimer();
9583 if (appData.icsActive) {
9584 whiteTimeRemaining = blackTimeRemaining = 0;
9586 whiteTimeRemaining = blackTimeRemaining = timeControl;
9588 if (whiteFlag || blackFlag) {
9590 whiteFlag = blackFlag = FALSE;
9592 DisplayBothClocks();
9595 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9597 /* Decrement running clock by amount of time that has passed */
9602 long lastTickLength, fudge;
9605 if (!appData.clockMode) return;
9606 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9610 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9612 /* Fudge if we woke up a little too soon */
9613 fudge = intendedTickLength - lastTickLength;
9614 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9616 if (WhiteOnMove(forwardMostMove)) {
9617 timeRemaining = whiteTimeRemaining -= lastTickLength;
9618 DisplayWhiteClock(whiteTimeRemaining - fudge,
9619 WhiteOnMove(currentMove));
9621 timeRemaining = blackTimeRemaining -= lastTickLength;
9622 DisplayBlackClock(blackTimeRemaining - fudge,
9623 !WhiteOnMove(currentMove));
9626 if (CheckFlags()) return;
9629 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9630 StartClockTimer(intendedTickLength);
9632 /* if the time remaining has fallen below the alarm threshold, sound the
9633 * alarm. if the alarm has sounded and (due to a takeback or time control
9634 * with increment) the time remaining has increased to a level above the
9635 * threshold, reset the alarm so it can sound again.
9638 if (appData.icsActive && appData.icsAlarm) {
9640 /* make sure we are dealing with the user's clock */
9641 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9642 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9645 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9646 alarmSounded = FALSE;
9647 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9649 alarmSounded = TRUE;
9655 /* A player has just moved, so stop the previously running
9656 clock and (if in clock mode) start the other one.
9657 We redisplay both clocks in case we're in ICS mode, because
9658 ICS gives us an update to both clocks after every move.
9659 Note that this routine is called *after* forwardMostMove
9660 is updated, so the last fractional tick must be subtracted
9661 from the color that is *not* on move now.
9666 long lastTickLength;
9668 int flagged = FALSE;
9672 if (StopClockTimer() && appData.clockMode) {
9673 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9674 if (WhiteOnMove(forwardMostMove)) {
9675 blackTimeRemaining -= lastTickLength;
9677 whiteTimeRemaining -= lastTickLength;
9679 flagged = CheckFlags();
9683 if (flagged || !appData.clockMode) return;
9686 case MachinePlaysBlack:
9687 case MachinePlaysWhite:
9688 case BeginningOfGame:
9689 if (pausing) return;
9693 case PlayFromGameFile:
9702 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9703 whiteTimeRemaining : blackTimeRemaining);
9704 StartClockTimer(intendedTickLength);
9708 /* Stop both clocks */
9712 long lastTickLength;
9715 if (!StopClockTimer()) return;
9716 if (!appData.clockMode) return;
9720 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9721 if (WhiteOnMove(forwardMostMove)) {
9722 whiteTimeRemaining -= lastTickLength;
9723 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9725 blackTimeRemaining -= lastTickLength;
9726 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9731 /* Start clock of player on move. Time may have been reset, so
9732 if clock is already running, stop and restart it. */
9736 (void) StopClockTimer(); /* in case it was running already */
9737 DisplayBothClocks();
9738 if (CheckFlags()) return;
9740 if (!appData.clockMode) return;
9741 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9743 GetTimeMark(&tickStartTM);
9744 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9745 whiteTimeRemaining : blackTimeRemaining);
9746 StartClockTimer(intendedTickLength);
9753 long second, minute, hour, day;
9755 static char buf[32];
9757 if (ms > 0 && ms <= 9900) {
9758 /* convert milliseconds to tenths, rounding up */
9759 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9761 sprintf(buf, " %03.1f ", tenths/10.0);
9765 /* convert milliseconds to seconds, rounding up */
9766 /* use floating point to avoid strangeness of integer division
9767 with negative dividends on many machines */
9768 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9775 day = second / (60 * 60 * 24);
9776 second = second % (60 * 60 * 24);
9777 hour = second / (60 * 60);
9778 second = second % (60 * 60);
9779 minute = second / 60;
9780 second = second % 60;
9783 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9784 sign, day, hour, minute, second);
9786 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9788 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9795 * This is necessary because some C libraries aren't ANSI C compliant yet.
9798 StrStr(string, match)
9799 char *string, *match;
9803 length = strlen(match);
9805 for (i = strlen(string) - length; i >= 0; i--, string++)
9806 if (!strncmp(match, string, length))
9813 StrCaseStr(string, match)
9814 char *string, *match;
9818 length = strlen(match);
9820 for (i = strlen(string) - length; i >= 0; i--, string++) {
9821 for (j = 0; j < length; j++) {
9822 if (ToLower(match[j]) != ToLower(string[j]))
9825 if (j == length) return string;
9839 c1 = ToLower(*s1++);
9840 c2 = ToLower(*s2++);
9841 if (c1 > c2) return 1;
9842 if (c1 < c2) return -1;
9843 if (c1 == NULLCHAR) return 0;
9852 return isupper(c) ? tolower(c) : c;
9860 return islower(c) ? toupper(c) : c;
9862 #endif /* !_amigados */
9870 if ((ret = (char *) malloc(strlen(s) + 1))) {
9877 StrSavePtr(s, savePtr)
9883 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9884 strcpy(*savePtr, s);
9896 clock = time((time_t *)NULL);
9897 tm = localtime(&clock);
9898 sprintf(buf, "%04d.%02d.%02d",
9899 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9900 return StrSave(buf);
9908 int i, j, fromX, fromY, toX, toY;
9914 whiteToPlay = (gameMode == EditPosition) ?
9915 !blackPlaysFirst : (move % 2 == 0);
9918 /* Piece placement data */
9919 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9921 for (j = 0; j < BOARD_SIZE; j++) {
9922 if (boards[move][i][j] == EmptySquare) {
9925 if (emptycount > 0) {
9926 *p++ = '0' + emptycount;
9929 *p++ = PieceToChar(boards[move][i][j]);
9932 if (emptycount > 0) {
9933 *p++ = '0' + emptycount;
9941 *p++ = whiteToPlay ? 'w' : 'b';
9944 /* !!We don't keep track of castling availability, so fake it */
9946 if (boards[move][0][4] == WhiteKing) {
9947 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9948 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9950 if (boards[move][7][4] == BlackKing) {
9951 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9952 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9954 if (q == p) *p++ = '-';
9957 /* En passant target square */
9958 if (move > backwardMostMove) {
9959 fromX = moveList[move - 1][0] - 'a';
9960 fromY = moveList[move - 1][1] - '1';
9961 toX = moveList[move - 1][2] - 'a';
9962 toY = moveList[move - 1][3] - '1';
9963 if (fromY == (whiteToPlay ? 6 : 1) &&
9964 toY == (whiteToPlay ? 4 : 3) &&
9965 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9967 /* 2-square pawn move just happened */
9969 *p++ = whiteToPlay ? '6' : '3';
9977 /* !!We don't keep track of halfmove clock for 50-move rule */
9981 /* Fullmove number */
9982 sprintf(p, "%d", (move / 2) + 1);
9984 return StrSave(buf);
9988 ParseFEN(board, blackPlaysFirst, fen)
9990 int *blackPlaysFirst;
9999 /* Piece placement data */
10000 for (i = BOARD_SIZE - 1; i >= 0; i--) {
10003 if (*p == '/' || *p == ' ') {
10004 if (*p == '/') p++;
10005 emptycount = BOARD_SIZE - j;
10006 while (emptycount--) board[i][j++] = EmptySquare;
10008 } else if (isdigit(*p)) {
10009 emptycount = *p++ - '0';
10010 if (j + emptycount > BOARD_SIZE) return FALSE;
10011 while (emptycount--) board[i][j++] = EmptySquare;
10012 } else if (isalpha(*p)) {
10013 if (j >= BOARD_SIZE) return FALSE;
10014 board[i][j++] = CharToPiece(*p++);
10020 while (*p == '/' || *p == ' ') p++;
10025 *blackPlaysFirst = FALSE;
10028 *blackPlaysFirst = TRUE;
10033 /* !!We ignore the rest of the FEN notation */
10038 EditPositionPasteFEN(char *fen)
10041 Board initial_position;
10043 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10044 DisplayError(_("Bad FEN position in clipboard"), 0);
10047 int savedBlackPlaysFirst = blackPlaysFirst;
10048 EditPositionEvent();
10049 blackPlaysFirst = savedBlackPlaysFirst;
10050 CopyBoard(boards[0], initial_position);
10051 EditPositionDone();
10052 DisplayBothClocks();
10053 DrawPosition(FALSE, boards[currentMove]);