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)
2451 ics_user_moved = FALSE;
2456 if (looking_at(buf, &i, "no longer examining game *")) {
2457 if (gameMode == IcsExamining &&
2458 atoi(star_match[0]) == ics_gamenum)
2462 ics_user_moved = FALSE;
2467 /* Advance leftover_start past any newlines we find,
2468 so only partial lines can get reparsed */
2469 if (looking_at(buf, &i, "\n")) {
2470 prevColor = curColor;
2471 if (curColor != ColorNormal) {
2472 if (oldi > next_out) {
2473 SendToPlayer(&buf[next_out], oldi - next_out);
2476 Colorize(ColorNormal, FALSE);
2477 curColor = ColorNormal;
2479 if (started == STARTED_BOARD) {
2480 started = STARTED_NONE;
2481 parse[parse_pos] = NULLCHAR;
2482 ParseBoard12(parse);
2485 /* Send premove here */
2486 if (appData.premove) {
2488 if (currentMove == 0 &&
2489 gameMode == IcsPlayingWhite &&
2490 appData.premoveWhite) {
2491 sprintf(str, "%s%s\n", ics_prefix,
2492 appData.premoveWhiteText);
2493 if (appData.debugMode)
2494 fprintf(debugFP, "Sending premove:\n");
2496 } else if (currentMove == 1 &&
2497 gameMode == IcsPlayingBlack &&
2498 appData.premoveBlack) {
2499 sprintf(str, "%s%s\n", ics_prefix,
2500 appData.premoveBlackText);
2501 if (appData.debugMode)
2502 fprintf(debugFP, "Sending premove:\n");
2504 } else if (gotPremove) {
2506 ClearPremoveHighlights();
2507 if (appData.debugMode)
2508 fprintf(debugFP, "Sending premove:\n");
2509 UserMoveEvent(premoveFromX, premoveFromY,
2510 premoveToX, premoveToY,
2515 /* Usually suppress following prompt */
2516 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2517 if (looking_at(buf, &i, "*% ")) {
2518 savingComment = FALSE;
2522 } else if (started == STARTED_HOLDINGS) {
2524 char new_piece[MSG_SIZ];
2525 started = STARTED_NONE;
2526 parse[parse_pos] = NULLCHAR;
2527 if (appData.debugMode)
2528 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2529 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2530 gamenum == ics_gamenum) {
2531 if (gameInfo.variant == VariantNormal) {
2532 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2533 /* Get a move list just to see the header, which
2534 will tell us whether this is really bug or zh */
2535 if (ics_getting_history == H_FALSE) {
2536 ics_getting_history = H_REQUESTED;
2537 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2541 new_piece[0] = NULLCHAR;
2542 sscanf(parse, "game %d white [%s black [%s <- %s",
2543 &gamenum, white_holding, black_holding,
2545 white_holding[strlen(white_holding)-1] = NULLCHAR;
2546 black_holding[strlen(black_holding)-1] = NULLCHAR;
2548 if (appData.zippyPlay && first.initDone) {
2549 ZippyHoldings(white_holding, black_holding,
2553 if (tinyLayout || smallLayout) {
2554 char wh[16], bh[16];
2555 PackHolding(wh, white_holding);
2556 PackHolding(bh, black_holding);
2557 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2558 gameInfo.white, gameInfo.black);
2560 sprintf(str, "%s [%s] vs. %s [%s]",
2561 gameInfo.white, white_holding,
2562 gameInfo.black, black_holding);
2564 DrawPosition(FALSE, NULL);
2567 /* Suppress following prompt */
2568 if (looking_at(buf, &i, "*% ")) {
2569 savingComment = FALSE;
2576 i++; /* skip unparsed character and loop back */
2579 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2580 started != STARTED_HOLDINGS && i > next_out) {
2581 SendToPlayer(&buf[next_out], i - next_out);
2585 leftover_len = buf_len - leftover_start;
2586 /* if buffer ends with something we couldn't parse,
2587 reparse it after appending the next read */
2589 } else if (count == 0) {
2590 RemoveInputSource(isr);
2591 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2593 DisplayFatalError(_("Error reading from ICS"), error, 1);
2598 /* Board style 12 looks like this:
2600 <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
2602 * The "<12> " is stripped before it gets to this routine. The two
2603 * trailing 0's (flip state and clock ticking) are later addition, and
2604 * some chess servers may not have them, or may have only the first.
2605 * Additional trailing fields may be added in the future.
2608 #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"
2610 #define RELATION_OBSERVING_PLAYED 0
2611 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2612 #define RELATION_PLAYING_MYMOVE 1
2613 #define RELATION_PLAYING_NOTMYMOVE -1
2614 #define RELATION_EXAMINING 2
2615 #define RELATION_ISOLATED_BOARD -3
2616 #define RELATION_STARTING_POSITION -4 /* FICS only */
2619 ParseBoard12(string)
2622 GameMode newGameMode;
2623 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2624 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2625 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2626 char to_play, board_chars[72];
2627 char move_str[500], str[500], elapsed_time[500];
2628 char black[32], white[32];
2630 int prevMove = currentMove;
2633 int fromX, fromY, toX, toY;
2636 fromX = fromY = toX = toY = -1;
2640 if (appData.debugMode)
2641 fprintf(debugFP, _("Parsing board: %s\n"), string);
2643 move_str[0] = NULLCHAR;
2644 elapsed_time[0] = NULLCHAR;
2645 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2646 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2647 &gamenum, white, black, &relation, &basetime, &increment,
2648 &white_stren, &black_stren, &white_time, &black_time,
2649 &moveNum, str, elapsed_time, move_str, &ics_flip,
2653 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2654 DisplayError(str, 0);
2658 /* Convert the move number to internal form */
2659 moveNum = (moveNum - 1) * 2;
2660 if (to_play == 'B') moveNum++;
2661 if (moveNum >= MAX_MOVES) {
2662 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2668 case RELATION_OBSERVING_PLAYED:
2669 case RELATION_OBSERVING_STATIC:
2670 if (gamenum == -1) {
2671 /* Old ICC buglet */
2672 relation = RELATION_OBSERVING_STATIC;
2674 newGameMode = IcsObserving;
2676 case RELATION_PLAYING_MYMOVE:
2677 case RELATION_PLAYING_NOTMYMOVE:
2679 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2680 IcsPlayingWhite : IcsPlayingBlack;
2682 case RELATION_EXAMINING:
2683 newGameMode = IcsExamining;
2685 case RELATION_ISOLATED_BOARD:
2687 /* Just display this board. If user was doing something else,
2688 we will forget about it until the next board comes. */
2689 newGameMode = IcsIdle;
2691 case RELATION_STARTING_POSITION:
2692 newGameMode = gameMode;
2696 /* Modify behavior for initial board display on move listing
2699 switch (ics_getting_history) {
2703 case H_GOT_REQ_HEADER:
2704 case H_GOT_UNREQ_HEADER:
2705 /* This is the initial position of the current game */
2706 gamenum = ics_gamenum;
2707 moveNum = 0; /* old ICS bug workaround */
2708 if (to_play == 'B') {
2709 startedFromSetupPosition = TRUE;
2710 blackPlaysFirst = TRUE;
2712 if (forwardMostMove == 0) forwardMostMove = 1;
2713 if (backwardMostMove == 0) backwardMostMove = 1;
2714 if (currentMove == 0) currentMove = 1;
2716 newGameMode = gameMode;
2717 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2719 case H_GOT_UNWANTED_HEADER:
2720 /* This is an initial board that we don't want */
2722 case H_GETTING_MOVES:
2723 /* Should not happen */
2724 DisplayError(_("Error gathering move list: extra board"), 0);
2725 ics_getting_history = H_FALSE;
2729 /* Take action if this is the first board of a new game, or of a
2730 different game than is currently being displayed. */
2731 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2732 relation == RELATION_ISOLATED_BOARD) {
2734 /* Forget the old game and get the history (if any) of the new one */
2735 if (gameMode != BeginningOfGame) {
2739 if (appData.autoRaiseBoard) BoardToTop();
2741 if (gamenum == -1) {
2742 newGameMode = IcsIdle;
2743 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2744 appData.getMoveList) {
2745 /* Need to get game history */
2746 ics_getting_history = H_REQUESTED;
2747 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2751 /* Initially flip the board to have black on the bottom if playing
2752 black or if the ICS flip flag is set, but let the user change
2753 it with the Flip View button. */
2754 flipView = appData.autoFlipView ?
2755 (newGameMode == IcsPlayingBlack) || ics_flip :
2758 /* Done with values from previous mode; copy in new ones */
2759 gameMode = newGameMode;
2761 ics_gamenum = gamenum;
2762 if (gamenum == gs_gamenum) {
2763 int klen = strlen(gs_kind);
2764 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2765 sprintf(str, "ICS %s", gs_kind);
2766 gameInfo.event = StrSave(str);
2768 gameInfo.event = StrSave("ICS game");
2770 gameInfo.site = StrSave(appData.icsHost);
2771 gameInfo.date = PGNDate();
2772 gameInfo.round = StrSave("-");
2773 gameInfo.white = StrSave(white);
2774 gameInfo.black = StrSave(black);
2775 timeControl = basetime * 60 * 1000;
2776 timeIncrement = increment * 1000;
2777 movesPerSession = 0;
2778 gameInfo.timeControl = TimeControlTagValue();
2779 gameInfo.variant = StringToVariant(gameInfo.event);
2781 /* Do we have the ratings? */
2782 if (strcmp(player1Name, white) == 0 &&
2783 strcmp(player2Name, black) == 0) {
2784 if (appData.debugMode)
2785 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2786 player1Rating, player2Rating);
2787 gameInfo.whiteRating = player1Rating;
2788 gameInfo.blackRating = player2Rating;
2789 } else if (strcmp(player2Name, white) == 0 &&
2790 strcmp(player1Name, black) == 0) {
2791 if (appData.debugMode)
2792 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2793 player2Rating, player1Rating);
2794 gameInfo.whiteRating = player2Rating;
2795 gameInfo.blackRating = player1Rating;
2797 player1Name[0] = player2Name[0] = NULLCHAR;
2799 /* Silence shouts if requested */
2800 if (appData.quietPlay &&
2801 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2802 SendToICS(ics_prefix);
2803 SendToICS("set shout 0\n");
2807 /* Deal with midgame name changes */
2809 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2810 if (gameInfo.white) free(gameInfo.white);
2811 gameInfo.white = StrSave(white);
2813 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2814 if (gameInfo.black) free(gameInfo.black);
2815 gameInfo.black = StrSave(black);
2819 /* Throw away game result if anything actually changes in examine mode */
2820 if (gameMode == IcsExamining && !newGame) {
2821 gameInfo.result = GameUnfinished;
2822 if (gameInfo.resultDetails != NULL) {
2823 free(gameInfo.resultDetails);
2824 gameInfo.resultDetails = NULL;
2828 /* In pausing && IcsExamining mode, we ignore boards coming
2829 in if they are in a different variation than we are. */
2830 if (pauseExamInvalid) return;
2831 if (pausing && gameMode == IcsExamining) {
2832 if (moveNum <= pauseExamForwardMostMove) {
2833 pauseExamInvalid = TRUE;
2834 forwardMostMove = pauseExamForwardMostMove;
2839 /* Parse the board */
2840 for (k = 0; k < 8; k++)
2841 for (j = 0; j < 8; j++)
2842 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2843 CopyBoard(boards[moveNum], board);
2845 startedFromSetupPosition =
2846 !CompareBoards(board, initialPosition);
2849 if (ics_getting_history == H_GOT_REQ_HEADER ||
2850 ics_getting_history == H_GOT_UNREQ_HEADER) {
2851 /* This was an initial position from a move list, not
2852 the current position */
2856 /* Update currentMove and known move number limits */
2857 newMove = newGame || moveNum > forwardMostMove;
2859 forwardMostMove = backwardMostMove = currentMove = moveNum;
2860 if (gameMode == IcsExamining && moveNum == 0) {
2861 /* Workaround for ICS limitation: we are not told the wild
2862 type when starting to examine a game. But if we ask for
2863 the move list, the move list header will tell us */
2864 ics_getting_history = H_REQUESTED;
2865 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2868 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2869 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2870 forwardMostMove = moveNum;
2871 if (!pausing || currentMove > forwardMostMove)
2872 currentMove = forwardMostMove;
2874 /* New part of history that is not contiguous with old part */
2875 if (pausing && gameMode == IcsExamining) {
2876 pauseExamInvalid = TRUE;
2877 forwardMostMove = pauseExamForwardMostMove;
2880 forwardMostMove = backwardMostMove = currentMove = moveNum;
2881 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2882 ics_getting_history = H_REQUESTED;
2883 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2888 /* Update the clocks */
2889 if (strchr(elapsed_time, '.')) {
2891 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2892 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2894 /* Time is in seconds */
2895 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2896 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2901 if (appData.zippyPlay && newGame &&
2902 gameMode != IcsObserving && gameMode != IcsIdle &&
2903 gameMode != IcsExamining)
2904 ZippyFirstBoard(moveNum, basetime, increment);
2907 /* Put the move on the move list, first converting
2908 to canonical algebraic form. */
2910 if (moveNum <= backwardMostMove) {
2911 /* We don't know what the board looked like before
2913 strcpy(parseList[moveNum - 1], move_str);
2914 strcat(parseList[moveNum - 1], " ");
2915 strcat(parseList[moveNum - 1], elapsed_time);
2916 moveList[moveNum - 1][0] = NULLCHAR;
2917 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2918 &fromX, &fromY, &toX, &toY, &promoChar)) {
2919 (void) CoordsToAlgebraic(boards[moveNum - 1],
2920 PosFlags(moveNum - 1), EP_UNKNOWN,
2921 fromY, fromX, toY, toX, promoChar,
2922 parseList[moveNum-1]);
2923 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2929 strcat(parseList[moveNum - 1], "+");
2932 strcat(parseList[moveNum - 1], "#");
2935 strcat(parseList[moveNum - 1], " ");
2936 strcat(parseList[moveNum - 1], elapsed_time);
2937 /* currentMoveString is set as a side-effect of ParseOneMove */
2938 strcpy(moveList[moveNum - 1], currentMoveString);
2939 strcat(moveList[moveNum - 1], "\n");
2940 } else if (strcmp(move_str, "none") == 0) {
2941 /* Again, we don't know what the board looked like;
2942 this is really the start of the game. */
2943 parseList[moveNum - 1][0] = NULLCHAR;
2944 moveList[moveNum - 1][0] = NULLCHAR;
2945 backwardMostMove = moveNum;
2946 startedFromSetupPosition = TRUE;
2947 fromX = fromY = toX = toY = -1;
2949 /* Move from ICS was illegal!? Punt. */
2951 if (appData.testLegality && appData.debugMode) {
2952 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2953 DisplayError(str, 0);
2956 strcpy(parseList[moveNum - 1], move_str);
2957 strcat(parseList[moveNum - 1], " ");
2958 strcat(parseList[moveNum - 1], elapsed_time);
2959 moveList[moveNum - 1][0] = NULLCHAR;
2960 fromX = fromY = toX = toY = -1;
2964 /* Send move to chess program (BEFORE animating it). */
2965 if (appData.zippyPlay && !newGame && newMove &&
2966 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2968 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2969 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2970 if (moveList[moveNum - 1][0] == NULLCHAR) {
2971 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2973 DisplayError(str, 0);
2975 if (first.sendTime) {
2976 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2978 SendMoveToProgram(moveNum - 1, &first);
2981 if (first.useColors) {
2982 SendToProgram(gameMode == IcsPlayingWhite ?
2984 "black\ngo\n", &first);
2986 SendToProgram("go\n", &first);
2988 first.maybeThinking = TRUE;
2991 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
2992 if (moveList[moveNum - 1][0] == NULLCHAR) {
2993 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
2994 DisplayError(str, 0);
2996 SendMoveToProgram(moveNum - 1, &first);
3003 if (moveNum > 0 && !gotPremove) {
3004 /* If move comes from a remote source, animate it. If it
3005 isn't remote, it will have already been animated. */
3006 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3007 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3009 if (!pausing && appData.highlightLastMove) {
3010 SetHighlights(fromX, fromY, toX, toY);
3014 /* Start the clocks */
3015 whiteFlag = blackFlag = FALSE;
3016 appData.clockMode = !(basetime == 0 && increment == 0);
3018 ics_clock_paused = TRUE;
3020 } else if (ticking == 1) {
3021 ics_clock_paused = FALSE;
3023 if (gameMode == IcsIdle ||
3024 relation == RELATION_OBSERVING_STATIC ||
3025 relation == RELATION_EXAMINING ||
3027 DisplayBothClocks();
3031 /* Display opponents and material strengths */
3032 if (gameInfo.variant != VariantBughouse &&
3033 gameInfo.variant != VariantCrazyhouse) {
3034 if (tinyLayout || smallLayout) {
3035 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3036 gameInfo.white, white_stren, gameInfo.black, black_stren,
3037 basetime, increment);
3039 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3040 gameInfo.white, white_stren, gameInfo.black, black_stren,
3041 basetime, increment);
3047 /* Display the board */
3050 if (appData.premove)
3052 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3053 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3054 ClearPremoveHighlights();
3056 DrawPosition(FALSE, boards[currentMove]);
3057 DisplayMove(moveNum - 1);
3058 if (appData.ringBellAfterMoves && !ics_user_moved)
3062 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3069 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3070 ics_getting_history = H_REQUESTED;
3071 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3077 AnalysisPeriodicEvent(force)
3080 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3081 && !force) || !appData.periodicUpdates)
3084 /* Send . command to Crafty to collect stats */
3085 SendToProgram(".\n", &first);
3087 /* Don't send another until we get a response (this makes
3088 us stop sending to old Crafty's which don't understand
3089 the "." command (sending illegal cmds resets node count & time,
3090 which looks bad)) */
3091 programStats.ok_to_send = 0;
3095 SendMoveToProgram(moveNum, cps)
3097 ChessProgramState *cps;
3100 if (cps->useUsermove) {
3101 SendToProgram("usermove ", cps);
3105 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3106 int len = space - parseList[moveNum];
3107 memcpy(buf, parseList[moveNum], len);
3109 buf[len] = NULLCHAR;
3111 sprintf(buf, "%s\n", parseList[moveNum]);
3113 SendToProgram(buf, cps);
3115 SendToProgram(moveList[moveNum], cps);
3120 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3122 int fromX, fromY, toX, toY;
3124 char user_move[MSG_SIZ];
3128 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3129 (int)moveType, fromX, fromY, toX, toY);
3130 DisplayError(user_move + strlen("say "), 0);
3132 case WhiteKingSideCastle:
3133 case BlackKingSideCastle:
3134 case WhiteQueenSideCastleWild:
3135 case BlackQueenSideCastleWild:
3136 sprintf(user_move, "o-o\n");
3138 case WhiteQueenSideCastle:
3139 case BlackQueenSideCastle:
3140 case WhiteKingSideCastleWild:
3141 case BlackKingSideCastleWild:
3142 sprintf(user_move, "o-o-o\n");
3144 case WhitePromotionQueen:
3145 case BlackPromotionQueen:
3146 case WhitePromotionRook:
3147 case BlackPromotionRook:
3148 case WhitePromotionBishop:
3149 case BlackPromotionBishop:
3150 case WhitePromotionKnight:
3151 case BlackPromotionKnight:
3152 case WhitePromotionKing:
3153 case BlackPromotionKing:
3154 sprintf(user_move, "%c%c%c%c=%c\n",
3155 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3156 PieceToChar(PromoPiece(moveType)));
3160 sprintf(user_move, "%c@%c%c\n",
3161 ToUpper(PieceToChar((ChessSquare) fromX)),
3162 'a' + toX, '1' + toY);
3165 case WhiteCapturesEnPassant:
3166 case BlackCapturesEnPassant:
3167 case IllegalMove: /* could be a variant we don't quite understand */
3168 sprintf(user_move, "%c%c%c%c\n",
3169 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3172 SendToICS(user_move);
3176 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3181 if (rf == DROP_RANK) {
3182 sprintf(move, "%c@%c%c\n",
3183 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3185 if (promoChar == 'x' || promoChar == NULLCHAR) {
3186 sprintf(move, "%c%c%c%c\n",
3187 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3189 sprintf(move, "%c%c%c%c%c\n",
3190 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3196 ProcessICSInitScript(f)
3201 while (fgets(buf, MSG_SIZ, f)) {
3202 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3209 /* Parser for moves from gnuchess, ICS, or user typein box */
3211 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3214 ChessMove *moveType;
3215 int *fromX, *fromY, *toX, *toY;
3218 *moveType = yylexstr(moveNum, move);
3219 switch (*moveType) {
3220 case WhitePromotionQueen:
3221 case BlackPromotionQueen:
3222 case WhitePromotionRook:
3223 case BlackPromotionRook:
3224 case WhitePromotionBishop:
3225 case BlackPromotionBishop:
3226 case WhitePromotionKnight:
3227 case BlackPromotionKnight:
3228 case WhitePromotionKing:
3229 case BlackPromotionKing:
3231 case WhiteCapturesEnPassant:
3232 case BlackCapturesEnPassant:
3233 case WhiteKingSideCastle:
3234 case WhiteQueenSideCastle:
3235 case BlackKingSideCastle:
3236 case BlackQueenSideCastle:
3237 case WhiteKingSideCastleWild:
3238 case WhiteQueenSideCastleWild:
3239 case BlackKingSideCastleWild:
3240 case BlackQueenSideCastleWild:
3241 case IllegalMove: /* bug or odd chess variant */
3242 *fromX = currentMoveString[0] - 'a';
3243 *fromY = currentMoveString[1] - '1';
3244 *toX = currentMoveString[2] - 'a';
3245 *toY = currentMoveString[3] - '1';
3246 *promoChar = currentMoveString[4];
3247 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3248 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3249 *fromX = *fromY = *toX = *toY = 0;
3252 if (appData.testLegality) {
3253 return (*moveType != IllegalMove);
3255 return !(fromX == fromY && toX == toY);
3260 *fromX = *moveType == WhiteDrop ?
3261 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3262 (int) CharToPiece(ToLower(currentMoveString[0]));
3264 *toX = currentMoveString[2] - 'a';
3265 *toY = currentMoveString[3] - '1';
3266 *promoChar = NULLCHAR;
3270 case ImpossibleMove:
3271 case (ChessMove) 0: /* end of file */
3281 *fromX = *fromY = *toX = *toY = 0;
3282 *promoChar = NULLCHAR;
3289 InitPosition(redraw)
3292 currentMove = forwardMostMove = backwardMostMove = 0;
3293 switch (gameInfo.variant) {
3295 CopyBoard(boards[0], initialPosition);
3297 case VariantTwoKings:
3298 CopyBoard(boards[0], twoKingsPosition);
3299 startedFromSetupPosition = TRUE;
3301 case VariantWildCastle:
3302 CopyBoard(boards[0], initialPosition);
3303 /* !!?shuffle with kings guaranteed to be on d or e file */
3305 case VariantNoCastle:
3306 CopyBoard(boards[0], initialPosition);
3307 /* !!?unconstrained back-rank shuffle */
3309 case VariantFischeRandom:
3310 CopyBoard(boards[0], initialPosition);
3311 /* !!shuffle according to FR rules */
3315 DrawPosition(FALSE, boards[currentMove]);
3319 SendBoard(cps, moveNum)
3320 ChessProgramState *cps;
3323 char message[MSG_SIZ];
3325 if (cps->useSetboard) {
3326 char* fen = PositionToFEN(moveNum);
3327 sprintf(message, "setboard %s\n", fen);
3328 SendToProgram(message, cps);
3334 /* Kludge to set black to move, avoiding the troublesome and now
3335 * deprecated "black" command.
3337 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3339 SendToProgram("edit\n", cps);
3340 SendToProgram("#\n", cps);
3341 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3342 bp = &boards[moveNum][i][0];
3343 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3344 if ((int) *bp < (int) BlackPawn) {
3345 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3347 SendToProgram(message, cps);
3352 SendToProgram("c\n", cps);
3353 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3354 bp = &boards[moveNum][i][0];
3355 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3356 if (((int) *bp != (int) EmptySquare)
3357 && ((int) *bp >= (int) BlackPawn)) {
3358 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3360 SendToProgram(message, cps);
3365 SendToProgram(".\n", cps);
3370 IsPromotion(fromX, fromY, toX, toY)
3371 int fromX, fromY, toX, toY;
3373 return gameMode != EditPosition &&
3374 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3375 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3376 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3381 PieceForSquare (x, y)
3385 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3388 return boards[currentMove][y][x];
3392 OKToStartUserMove(x, y)
3395 ChessSquare from_piece;
3398 if (matchMode) return FALSE;
3399 if (gameMode == EditPosition) return TRUE;
3401 if (x >= 0 && y >= 0)
3402 from_piece = boards[currentMove][y][x];
3404 from_piece = EmptySquare;
3406 if (from_piece == EmptySquare) return FALSE;
3408 white_piece = (int)from_piece >= (int)WhitePawn &&
3409 (int)from_piece <= (int)WhiteKing;
3412 case PlayFromGameFile:
3414 case TwoMachinesPlay:
3422 case MachinePlaysWhite:
3423 case IcsPlayingBlack:
3424 if (appData.zippyPlay) return FALSE;
3426 DisplayMoveError(_("You are playing Black"));
3431 case MachinePlaysBlack:
3432 case IcsPlayingWhite:
3433 if (appData.zippyPlay) return FALSE;
3435 DisplayMoveError(_("You are playing White"));
3441 if (!white_piece && WhiteOnMove(currentMove)) {
3442 DisplayMoveError(_("It is White's turn"));
3445 if (white_piece && !WhiteOnMove(currentMove)) {
3446 DisplayMoveError(_("It is Black's turn"));
3449 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3450 /* Editing correspondence game history */
3451 /* Could disallow this or prompt for confirmation */
3454 if (currentMove < forwardMostMove) {
3455 /* Discarding moves */
3456 /* Could prompt for confirmation here,
3457 but I don't think that's such a good idea */
3458 forwardMostMove = currentMove;
3462 case BeginningOfGame:
3463 if (appData.icsActive) return FALSE;
3464 if (!appData.noChessProgram) {
3466 DisplayMoveError(_("You are playing White"));
3473 if (!white_piece && WhiteOnMove(currentMove)) {
3474 DisplayMoveError(_("It is White's turn"));
3477 if (white_piece && !WhiteOnMove(currentMove)) {
3478 DisplayMoveError(_("It is Black's turn"));
3487 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3488 && gameMode != AnalyzeFile && gameMode != Training) {
3489 DisplayMoveError(_("Displayed position is not current"));
3495 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3496 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3497 int lastLoadGameUseList = FALSE;
3498 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3499 ChessMove lastLoadGameStart = (ChessMove) 0;
3503 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3504 int fromX, fromY, toX, toY;
3509 if (fromX < 0 || fromY < 0) return;
3510 if ((fromX == toX) && (fromY == toY)) {
3514 /* Check if the user is playing in turn. This is complicated because we
3515 let the user "pick up" a piece before it is his turn. So the piece he
3516 tried to pick up may have been captured by the time he puts it down!
3517 Therefore we use the color the user is supposed to be playing in this
3518 test, not the color of the piece that is currently on the starting
3519 square---except in EditGame mode, where the user is playing both
3520 sides; fortunately there the capture race can't happen. (It can
3521 now happen in IcsExamining mode, but that's just too bad. The user
3522 will get a somewhat confusing message in that case.)
3526 case PlayFromGameFile:
3528 case TwoMachinesPlay:
3532 /* We switched into a game mode where moves are not accepted,
3533 perhaps while the mouse button was down. */
3536 case MachinePlaysWhite:
3537 /* User is moving for Black */
3538 if (WhiteOnMove(currentMove)) {
3539 DisplayMoveError(_("It is White's turn"));
3544 case MachinePlaysBlack:
3545 /* User is moving for White */
3546 if (!WhiteOnMove(currentMove)) {
3547 DisplayMoveError(_("It is Black's turn"));
3554 case BeginningOfGame:
3557 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3558 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3559 /* User is moving for Black */
3560 if (WhiteOnMove(currentMove)) {
3561 DisplayMoveError(_("It is White's turn"));
3565 /* User is moving for White */
3566 if (!WhiteOnMove(currentMove)) {
3567 DisplayMoveError(_("It is Black's turn"));
3573 case IcsPlayingBlack:
3574 /* User is moving for Black */
3575 if (WhiteOnMove(currentMove)) {
3576 if (!appData.premove) {
3577 DisplayMoveError(_("It is White's turn"));
3578 } else if (toX >= 0 && toY >= 0) {
3581 premoveFromX = fromX;
3582 premoveFromY = fromY;
3583 premovePromoChar = promoChar;
3585 if (appData.debugMode)
3586 fprintf(debugFP, "Got premove: fromX %d,"
3587 "fromY %d, toX %d, toY %d\n",
3588 fromX, fromY, toX, toY);
3594 case IcsPlayingWhite:
3595 /* User is moving for White */
3596 if (!WhiteOnMove(currentMove)) {
3597 if (!appData.premove) {
3598 DisplayMoveError(_("It is Black's turn"));
3599 } else if (toX >= 0 && toY >= 0) {
3602 premoveFromX = fromX;
3603 premoveFromY = fromY;
3604 premovePromoChar = promoChar;
3606 if (appData.debugMode)
3607 fprintf(debugFP, "Got premove: fromX %d,"
3608 "fromY %d, toX %d, toY %d\n",
3609 fromX, fromY, toX, toY);
3619 if (toX == -2 || toY == -2) {
3620 boards[0][fromY][fromX] = EmptySquare;
3621 DrawPosition(FALSE, boards[currentMove]);
3622 } else if (toX >= 0 && toY >= 0) {
3623 boards[0][toY][toX] = boards[0][fromY][fromX];
3624 boards[0][fromY][fromX] = EmptySquare;
3625 DrawPosition(FALSE, boards[currentMove]);
3630 if (toX < 0 || toY < 0) return;
3631 userOfferedDraw = FALSE;
3633 if (appData.testLegality) {
3634 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3635 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3636 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3637 DisplayMoveError(_("Illegal move"));
3641 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3644 if (gameMode == Training) {
3645 /* compare the move played on the board to the next move in the
3646 * game. If they match, display the move and the opponent's response.
3647 * If they don't match, display an error message.
3651 CopyBoard(testBoard, boards[currentMove]);
3652 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3654 if (CompareBoards(testBoard, boards[currentMove+1])) {
3655 ForwardInner(currentMove+1);
3657 /* Autoplay the opponent's response.
3658 * if appData.animate was TRUE when Training mode was entered,
3659 * the response will be animated.
3661 saveAnimate = appData.animate;
3662 appData.animate = animateTraining;
3663 ForwardInner(currentMove+1);
3664 appData.animate = saveAnimate;
3666 /* check for the end of the game */
3667 if (currentMove >= forwardMostMove) {
3668 gameMode = PlayFromGameFile;
3670 SetTrainingModeOff();
3671 DisplayInformation(_("End of game"));
3674 DisplayError(_("Incorrect move"), 0);
3679 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3682 /* Common tail of UserMoveEvent and DropMenuEvent */
3684 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3686 int fromX, fromY, toX, toY;
3687 /*char*/int promoChar;
3689 /* Ok, now we know that the move is good, so we can kill
3690 the previous line in Analysis Mode */
3691 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3692 forwardMostMove = currentMove;
3695 /* If we need the chess program but it's dead, restart it */
3696 ResurrectChessProgram();
3698 /* A user move restarts a paused game*/
3702 thinkOutput[0] = NULLCHAR;
3704 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3706 if (gameMode == BeginningOfGame) {
3707 if (appData.noChessProgram) {
3708 gameMode = EditGame;
3712 gameMode = MachinePlaysBlack;
3714 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3716 if (first.sendName) {
3717 sprintf(buf, "name %s\n", gameInfo.white);
3718 SendToProgram(buf, &first);
3724 /* Relay move to ICS or chess engine */
3725 if (appData.icsActive) {
3726 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3727 gameMode == IcsExamining) {
3728 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3732 if (first.sendTime && (gameMode == BeginningOfGame ||
3733 gameMode == MachinePlaysWhite ||
3734 gameMode == MachinePlaysBlack)) {
3735 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3737 SendMoveToProgram(forwardMostMove-1, &first);
3738 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3739 first.maybeThinking = TRUE;
3741 if (currentMove == cmailOldMove + 1) {
3742 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3746 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3750 switch (MateTest(boards[currentMove], PosFlags(currentMove),
3756 if (WhiteOnMove(currentMove)) {
3757 GameEnds(BlackWins, "Black mates", GE_PLAYER);
3759 GameEnds(WhiteWins, "White mates", GE_PLAYER);
3763 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3768 case MachinePlaysBlack:
3769 case MachinePlaysWhite:
3770 /* disable certain menu options while machine is thinking */
3771 SetMachineThinkingEnables();
3780 HandleMachineMove(message, cps)
3782 ChessProgramState *cps;
3784 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3785 char realname[MSG_SIZ];
3786 int fromX, fromY, toX, toY;
3793 * Kludge to ignore BEL characters
3795 while (*message == '\007') message++;
3798 * Look for book output
3800 if (cps == &first && bookRequested) {
3801 if (message[0] == '\t' || message[0] == ' ') {
3802 /* Part of the book output is here; append it */
3803 strcat(bookOutput, message);
3804 strcat(bookOutput, " \n");
3806 } else if (bookOutput[0] != NULLCHAR) {
3807 /* All of book output has arrived; display it */
3808 char *p = bookOutput;
3809 while (*p != NULLCHAR) {
3810 if (*p == '\t') *p = ' ';
3813 DisplayInformation(bookOutput);
3814 bookRequested = FALSE;
3815 /* Fall through to parse the current output */
3820 * Look for machine move.
3822 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3823 strcmp(buf2, "...") == 0) ||
3824 (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3825 strcmp(buf1, "move") == 0)) {
3827 /* This method is only useful on engines that support ping */
3828 if (cps->lastPing != cps->lastPong) {
3829 if (gameMode == BeginningOfGame) {
3830 /* Extra move from before last new; ignore */
3831 if (appData.debugMode) {
3832 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3835 if (appData.debugMode) {
3836 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3837 cps->which, gameMode);
3839 SendToProgram("undo\n", cps);
3845 case BeginningOfGame:
3846 /* Extra move from before last reset; ignore */
3847 if (appData.debugMode) {
3848 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3855 /* Extra move after we tried to stop. The mode test is
3856 not a reliable way of detecting this problem, but it's
3857 the best we can do on engines that don't support ping.
3859 if (appData.debugMode) {
3860 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3861 cps->which, gameMode);
3863 SendToProgram("undo\n", cps);
3866 case MachinePlaysWhite:
3867 case IcsPlayingWhite:
3868 machineWhite = TRUE;
3871 case MachinePlaysBlack:
3872 case IcsPlayingBlack:
3873 machineWhite = FALSE;
3876 case TwoMachinesPlay:
3877 machineWhite = (cps->twoMachinesColor[0] == 'w');
3880 if (WhiteOnMove(forwardMostMove) != machineWhite) {
3881 if (appData.debugMode) {
3883 "Ignoring move out of turn by %s, gameMode %d"
3884 ", forwardMost %d\n",
3885 cps->which, gameMode, forwardMostMove);
3890 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3891 &fromX, &fromY, &toX, &toY, &promoChar)) {
3892 /* Machine move could not be parsed; ignore it. */
3893 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3894 machineMove, cps->which);
3895 DisplayError(buf1, 0);
3896 if (gameMode == TwoMachinesPlay) {
3897 GameEnds(machineWhite ? BlackWins : WhiteWins,
3898 "Forfeit due to illegal move", GE_XBOARD);
3903 hintRequested = FALSE;
3904 lastHint[0] = NULLCHAR;
3905 bookRequested = FALSE;
3906 /* Program may be pondering now */
3907 cps->maybeThinking = TRUE;
3908 if (cps->sendTime == 2) cps->sendTime = 1;
3909 if (cps->offeredDraw) cps->offeredDraw--;
3912 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3914 SendMoveToICS(moveType, fromX, fromY, toX, toY);
3918 /* currentMoveString is set as a side-effect of ParseOneMove */
3919 strcpy(machineMove, currentMoveString);
3920 strcat(machineMove, "\n");
3921 strcpy(moveList[forwardMostMove], machineMove);
3923 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3925 if (gameMode == TwoMachinesPlay) {
3926 if (cps->other->sendTime) {
3927 SendTimeRemaining(cps->other,
3928 cps->other->twoMachinesColor[0] == 'w');
3930 SendMoveToProgram(forwardMostMove-1, cps->other);
3933 if (cps->other->useColors) {
3934 SendToProgram(cps->other->twoMachinesColor, cps->other);
3936 SendToProgram("go\n", cps->other);
3938 cps->other->maybeThinking = TRUE;
3941 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3942 if (!pausing && appData.ringBellAfterMoves) {
3946 * Reenable menu items that were disabled while
3947 * machine was thinking
3949 if (gameMode != TwoMachinesPlay)
3950 SetUserThinkingEnables();
3954 /* Set special modes for chess engines. Later something general
3955 * could be added here; for now there is just one kludge feature,
3956 * needed because Crafty 15.10 and earlier don't ignore SIGINT
3957 * when "xboard" is given as an interactive command.
3959 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3960 cps->useSigint = FALSE;
3961 cps->useSigterm = FALSE;
3965 * Look for communication commands
3967 if (!strncmp(message, "telluser ", 9)) {
3968 DisplayNote(message + 9);
3971 if (!strncmp(message, "tellusererror ", 14)) {
3972 DisplayError(message + 14, 0);
3975 if (!strncmp(message, "tellopponent ", 13)) {
3976 if (appData.icsActive) {
3978 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3982 DisplayNote(message + 13);
3986 if (!strncmp(message, "tellothers ", 11)) {
3987 if (appData.icsActive) {
3989 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
3995 if (!strncmp(message, "tellall ", 8)) {
3996 if (appData.icsActive) {
3998 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4002 DisplayNote(message + 8);
4006 if (strncmp(message, "warning", 7) == 0) {
4007 /* Undocumented feature, use tellusererror in new code */
4008 DisplayError(message, 0);
4011 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4012 strcpy(realname, cps->tidy);
4013 strcat(realname, " query");
4014 AskQuestion(realname, buf2, buf1, cps->pr);
4017 /* Commands from the engine directly to ICS. We don't allow these to be
4018 * sent until we are logged on. Crafty kibitzes have been known to
4019 * interfere with the login process.
4022 if (!strncmp(message, "tellics ", 8)) {
4023 SendToICS(message + 8);
4027 if (!strncmp(message, "tellicsnoalias ", 15)) {
4028 SendToICS(ics_prefix);
4029 SendToICS(message + 15);
4033 /* The following are for backward compatibility only */
4034 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4035 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4036 SendToICS(ics_prefix);
4042 if (strncmp(message, "feature ", 8) == 0) {
4043 ParseFeatures(message+8, cps);
4045 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4049 * If the move is illegal, cancel it and redraw the board.
4050 * Also deal with other error cases. Matching is rather loose
4051 * here to accommodate engines written before the spec.
4053 if (strncmp(message + 1, "llegal move", 11) == 0 ||
4054 strncmp(message, "Error", 5) == 0) {
4055 if (StrStr(message, "name") ||
4056 StrStr(message, "rating") || StrStr(message, "?") ||
4057 StrStr(message, "result") || StrStr(message, "board") ||
4058 StrStr(message, "bk") || StrStr(message, "computer") ||
4059 StrStr(message, "variant") || StrStr(message, "hint") ||
4060 StrStr(message, "random") || StrStr(message, "depth") ||
4061 StrStr(message, "accepted")) {
4064 if (StrStr(message, "protover")) {
4065 /* Program is responding to input, so it's apparently done
4066 initializing, and this error message indicates it is
4067 protocol version 1. So we don't need to wait any longer
4068 for it to initialize and send feature commands. */
4069 FeatureDone(cps, 1);
4070 cps->protocolVersion = 1;
4073 cps->maybeThinking = FALSE;
4075 if (StrStr(message, "draw")) {
4076 /* Program doesn't have "draw" command */
4077 cps->sendDrawOffers = 0;
4080 if (cps->sendTime != 1 &&
4081 (StrStr(message, "time") || StrStr(message, "otim"))) {
4082 /* Program apparently doesn't have "time" or "otim" command */
4086 if (StrStr(message, "analyze")) {
4087 cps->analysisSupport = FALSE;
4088 cps->analyzing = FALSE;
4090 sprintf(buf2, "%s does not support analysis", cps->tidy);
4091 DisplayError(buf2, 0);
4094 if (StrStr(message, "(no matching move)st")) {
4095 /* Special kludge for GNU Chess 4 only */
4096 cps->stKludge = TRUE;
4097 SendTimeControl(cps, movesPerSession, timeControl,
4098 timeIncrement, appData.searchDepth,
4102 if (StrStr(message, "(no matching move)sd")) {
4103 /* Special kludge for GNU Chess 4 only */
4104 cps->sdKludge = TRUE;
4105 SendTimeControl(cps, movesPerSession, timeControl,
4106 timeIncrement, appData.searchDepth,
4110 if (!StrStr(message, "llegal")) return;
4111 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4112 gameMode == IcsIdle) return;
4113 if (forwardMostMove <= backwardMostMove) return;
4115 /* Following removed: it caused a bug where a real illegal move
4116 message in analyze mored would be ignored. */
4117 if (cps == &first && programStats.ok_to_send == 0) {
4118 /* Bogus message from Crafty responding to "." This filtering
4119 can miss some of the bad messages, but fortunately the bug
4120 is fixed in current Crafty versions, so it doesn't matter. */
4124 if (pausing) PauseEvent();
4125 if (gameMode == PlayFromGameFile) {
4126 /* Stop reading this game file */
4127 gameMode = EditGame;
4130 currentMove = --forwardMostMove;
4131 DisplayMove(currentMove-1); /* before DisplayMoveError */
4133 DisplayBothClocks();
4134 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
4135 parseList[currentMove], cps->which);
4136 DisplayMoveError(buf1);
4137 DrawPosition(FALSE, boards[currentMove]);
4140 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4141 /* Program has a broken "time" command that
4142 outputs a string not ending in newline.
4148 * If chess program startup fails, exit with an error message.
4149 * Attempts to recover here are futile.
4151 if ((StrStr(message, "unknown host") != NULL)
4152 || (StrStr(message, "No remote directory") != NULL)
4153 || (StrStr(message, "not found") != NULL)
4154 || (StrStr(message, "No such file") != NULL)
4155 || (StrStr(message, "can't alloc") != NULL)
4156 || (StrStr(message, "Permission denied") != NULL)) {
4158 cps->maybeThinking = FALSE;
4159 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
4160 cps->which, cps->program, cps->host, message);
4161 RemoveInputSource(cps->isr);
4162 DisplayFatalError(buf1, 0, 1);
4167 * Look for hint output
4169 if (sscanf(message, "Hint: %s", buf1) == 1) {
4170 if (cps == &first && hintRequested) {
4171 hintRequested = FALSE;
4172 if (ParseOneMove(buf1, forwardMostMove, &moveType,
4173 &fromX, &fromY, &toX, &toY, &promoChar)) {
4174 (void) CoordsToAlgebraic(boards[forwardMostMove],
4175 PosFlags(forwardMostMove), EP_UNKNOWN,
4176 fromY, fromX, toY, toX, promoChar, buf1);
4177 sprintf(buf2, "Hint: %s", buf1);
4178 DisplayInformation(buf2);
4180 /* Hint move could not be parsed!? */
4182 _("Illegal hint move \"%s\"\nfrom %s chess program"),
4184 DisplayError(buf2, 0);
4187 strcpy(lastHint, buf1);
4193 * Ignore other messages if game is not in progress
4195 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4196 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4199 * look for win, lose, draw, or draw offer
4201 if (strncmp(message, "1-0", 3) == 0) {
4202 char *p, *q, *r = "";
4203 p = strchr(message, '{');
4211 GameEnds(WhiteWins, r, GE_ENGINE);
4213 } else if (strncmp(message, "0-1", 3) == 0) {
4214 char *p, *q, *r = "";
4215 p = strchr(message, '{');
4223 /* Kludge for Arasan 4.1 bug */
4224 if (strcmp(r, "Black resigns") == 0) {
4225 GameEnds(WhiteWins, r, GE_ENGINE);
4228 GameEnds(BlackWins, r, GE_ENGINE);
4230 } else if (strncmp(message, "1/2", 3) == 0) {
4231 char *p, *q, *r = "";
4232 p = strchr(message, '{');
4240 GameEnds(GameIsDrawn, r, GE_ENGINE);
4243 } else if (strncmp(message, "White resign", 12) == 0) {
4244 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4246 } else if (strncmp(message, "Black resign", 12) == 0) {
4247 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4249 } else if (strncmp(message, "White", 5) == 0 &&
4250 message[5] != '(' &&
4251 StrStr(message, "Black") == NULL) {
4252 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4254 } else if (strncmp(message, "Black", 5) == 0 &&
4255 message[5] != '(') {
4256 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4258 } else if (strcmp(message, "resign") == 0 ||
4259 strcmp(message, "computer resigns") == 0) {
4261 case MachinePlaysBlack:
4262 case IcsPlayingBlack:
4263 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4265 case MachinePlaysWhite:
4266 case IcsPlayingWhite:
4267 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4269 case TwoMachinesPlay:
4270 if (cps->twoMachinesColor[0] == 'w')
4271 GameEnds(BlackWins, "White resigns", GE_ENGINE);
4273 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4280 } else if (strncmp(message, "opponent mates", 14) == 0) {
4282 case MachinePlaysBlack:
4283 case IcsPlayingBlack:
4284 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4286 case MachinePlaysWhite:
4287 case IcsPlayingWhite:
4288 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4290 case TwoMachinesPlay:
4291 if (cps->twoMachinesColor[0] == 'w')
4292 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4294 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4301 } else if (strncmp(message, "computer mates", 14) == 0) {
4303 case MachinePlaysBlack:
4304 case IcsPlayingBlack:
4305 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4307 case MachinePlaysWhite:
4308 case IcsPlayingWhite:
4309 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4311 case TwoMachinesPlay:
4312 if (cps->twoMachinesColor[0] == 'w')
4313 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4315 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4322 } else if (strncmp(message, "checkmate", 9) == 0) {
4323 if (WhiteOnMove(forwardMostMove)) {
4324 GameEnds(BlackWins, "Black mates", GE_ENGINE);
4326 GameEnds(WhiteWins, "White mates", GE_ENGINE);
4329 } else if (strstr(message, "Draw") != NULL ||
4330 strstr(message, "game is a draw") != NULL) {
4331 GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4333 } else if (strstr(message, "offer") != NULL &&
4334 strstr(message, "draw") != NULL) {
4336 if (appData.zippyPlay && first.initDone) {
4337 /* Relay offer to ICS */
4338 SendToICS(ics_prefix);
4339 SendToICS("draw\n");
4342 cps->offeredDraw = 2; /* valid until this engine moves twice */
4343 if (gameMode == TwoMachinesPlay) {
4344 if (cps->other->offeredDraw) {
4345 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4347 if (cps->other->sendDrawOffers) {
4348 SendToProgram("draw\n", cps->other);
4351 } else if (gameMode == MachinePlaysWhite ||
4352 gameMode == MachinePlaysBlack) {
4353 if (userOfferedDraw) {
4354 DisplayInformation(_("Machine accepts your draw offer"));
4355 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4357 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
4364 * Look for thinking output
4366 if (appData.showThinking) {
4367 int plylev, mvleft, mvtot, curscore, time;
4368 char mvname[MOVE_LEN];
4372 int prefixHint = FALSE;
4373 mvname[0] = NULLCHAR;
4376 case MachinePlaysBlack:
4377 case IcsPlayingBlack:
4378 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4380 case MachinePlaysWhite:
4381 case IcsPlayingWhite:
4382 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4386 /* icsEngineAnalyze */
4388 if (!appData.icsEngineAnalyze) ignore = TRUE;
4390 case TwoMachinesPlay:
4391 if ((cps->twoMachinesColor[0] == 'w') !=
4392 WhiteOnMove(forwardMostMove)) {
4403 if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
4404 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4406 if (plyext != ' ' && plyext != '\t') {
4409 programStats.depth = plylev;
4410 programStats.nodes = nodes;
4411 programStats.time = time;
4412 programStats.score = curscore;
4413 programStats.got_only_move = 0;
4415 /* Buffer overflow protection */
4416 if (buf1[0] != NULLCHAR) {
4417 if (strlen(buf1) >= sizeof(programStats.movelist)
4418 && appData.debugMode) {
4420 "PV is too long; using the first %d bytes.\n",
4421 sizeof(programStats.movelist) - 1);
4423 strncpy(programStats.movelist, buf1,
4424 sizeof(programStats.movelist));
4425 programStats.movelist[sizeof(programStats.movelist) - 1]
4428 sprintf(programStats.movelist, " no PV\n");
4431 if (programStats.seen_stat) {
4432 programStats.ok_to_send = 1;
4435 if (strchr(programStats.movelist, '(') != NULL) {
4436 programStats.line_is_book = 1;
4437 programStats.nr_moves = 0;
4438 programStats.moves_left = 0;
4440 programStats.line_is_book = 0;
4443 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4445 (gameMode == TwoMachinesPlay ?
4446 ToUpper(cps->twoMachinesColor[0]) : ' '),
4447 ((double) curscore) / 100.0,
4448 prefixHint ? lastHint : "",
4449 prefixHint ? " " : "", buf1);
4451 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
4452 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4453 DisplayMove(currentMove - 1);
4458 } else if ((p=StrStr(message, "(only move)")) != NULL) {
4459 /* crafty (9.25+) says "(only move) <move>"
4460 * if there is only 1 legal move
4462 sscanf(p, "(only move) %s", buf1);
4463 sprintf(thinkOutput, "%s (only move)", buf1);
4464 sprintf(programStats.movelist, "%s (only move)", buf1);
4465 programStats.depth = 1;
4466 programStats.nr_moves = 1;
4467 programStats.moves_left = 1;
4468 programStats.nodes = 1;
4469 programStats.time = 1;
4470 programStats.got_only_move = 1;
4472 /* Not really, but we also use this member to
4473 mean "line isn't going to change" (Crafty
4474 isn't searching, so stats won't change) */
4475 programStats.line_is_book = 1;
4477 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4478 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4479 DisplayMove(currentMove - 1);
4483 } else if (sscanf(message,"stat01: %d" u64Display "%d %d %d %s",
4484 &time, &nodes, &plylev, &mvleft,
4485 &mvtot, mvname) >= 5) {
4486 /* The stat01: line is from Crafty (9.29+) in response
4487 to the "." command */
4488 programStats.seen_stat = 1;
4489 cps->maybeThinking = TRUE;
4491 if (programStats.got_only_move || !appData.periodicUpdates)
4494 programStats.depth = plylev;
4495 programStats.time = time;
4496 programStats.nodes = nodes;
4497 programStats.moves_left = mvleft;
4498 programStats.nr_moves = mvtot;
4499 strcpy(programStats.move_name, mvname);
4500 programStats.ok_to_send = 1;
4504 } else if (strncmp(message,"++",2) == 0) {
4505 /* Crafty 9.29+ outputs this */
4506 programStats.got_fail = 2;
4509 } else if (strncmp(message,"--",2) == 0) {
4510 /* Crafty 9.29+ outputs this */
4511 programStats.got_fail = 1;
4514 } else if (thinkOutput[0] != NULLCHAR &&
4515 strncmp(message, " ", 4) == 0) {
4517 while (*p && *p == ' ') p++;
4518 strcat(thinkOutput, " ");
4519 strcat(thinkOutput, p);
4520 strcat(programStats.movelist, " ");
4521 strcat(programStats.movelist, p);
4522 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4523 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4524 DisplayMove(currentMove - 1);
4534 /* Parse a game score from the character string "game", and
4535 record it as the history of the current game. The game
4536 score is NOT assumed to start from the standard position.
4537 The display is not updated in any way.
4540 ParseGameHistory(game)
4544 int fromX, fromY, toX, toY, boardIndex;
4549 if (appData.debugMode)
4550 fprintf(debugFP, "Parsing game history: %s\n", game);
4552 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4553 gameInfo.site = StrSave(appData.icsHost);
4554 gameInfo.date = PGNDate();
4555 gameInfo.round = StrSave("-");
4557 /* Parse out names of players */
4558 while (*game == ' ') game++;
4560 while (*game != ' ') *p++ = *game++;
4562 gameInfo.white = StrSave(buf);
4563 while (*game == ' ') game++;
4565 while (*game != ' ' && *game != '\n') *p++ = *game++;
4567 gameInfo.black = StrSave(buf);
4570 boardIndex = blackPlaysFirst ? 1 : 0;
4573 yyboardindex = boardIndex;
4574 moveType = (ChessMove) yylex();
4576 case WhitePromotionQueen:
4577 case BlackPromotionQueen:
4578 case WhitePromotionRook:
4579 case BlackPromotionRook:
4580 case WhitePromotionBishop:
4581 case BlackPromotionBishop:
4582 case WhitePromotionKnight:
4583 case BlackPromotionKnight:
4584 case WhitePromotionKing:
4585 case BlackPromotionKing:
4587 case WhiteCapturesEnPassant:
4588 case BlackCapturesEnPassant:
4589 case WhiteKingSideCastle:
4590 case WhiteQueenSideCastle:
4591 case BlackKingSideCastle:
4592 case BlackQueenSideCastle:
4593 case WhiteKingSideCastleWild:
4594 case WhiteQueenSideCastleWild:
4595 case BlackKingSideCastleWild:
4596 case BlackQueenSideCastleWild:
4597 case IllegalMove: /* maybe suicide chess, etc. */
4598 fromX = currentMoveString[0] - 'a';
4599 fromY = currentMoveString[1] - '1';
4600 toX = currentMoveString[2] - 'a';
4601 toY = currentMoveString[3] - '1';
4602 promoChar = currentMoveString[4];
4606 fromX = moveType == WhiteDrop ?
4607 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4608 (int) CharToPiece(ToLower(currentMoveString[0]));
4610 toX = currentMoveString[2] - 'a';
4611 toY = currentMoveString[3] - '1';
4612 promoChar = NULLCHAR;
4616 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4617 DisplayError(buf, 0);
4619 case ImpossibleMove:
4621 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4622 DisplayError(buf, 0);
4624 case (ChessMove) 0: /* end of file */
4625 if (boardIndex < backwardMostMove) {
4626 /* Oops, gap. How did that happen? */
4627 DisplayError(_("Gap in move list"), 0);
4630 backwardMostMove = blackPlaysFirst ? 1 : 0;
4631 if (boardIndex > forwardMostMove) {
4632 forwardMostMove = boardIndex;
4636 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4637 strcat(parseList[boardIndex-1], " ");
4638 strcat(parseList[boardIndex-1], yy_text);
4650 case GameUnfinished:
4651 if (gameMode == IcsExamining) {
4652 if (boardIndex < backwardMostMove) {
4653 /* Oops, gap. How did that happen? */
4656 backwardMostMove = blackPlaysFirst ? 1 : 0;
4659 gameInfo.result = moveType;
4660 p = strchr(yy_text, '{');
4661 if (p == NULL) p = strchr(yy_text, '(');
4664 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4666 q = strchr(p, *p == '{' ? '}' : ')');
4667 if (q != NULL) *q = NULLCHAR;
4670 gameInfo.resultDetails = StrSave(p);
4673 if (boardIndex >= forwardMostMove &&
4674 !(gameMode == IcsObserving && ics_gamenum == -1)) {
4675 backwardMostMove = blackPlaysFirst ? 1 : 0;
4678 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4679 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4680 parseList[boardIndex]);
4681 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4682 /* currentMoveString is set as a side-effect of yylex */
4683 strcpy(moveList[boardIndex], currentMoveString);
4684 strcat(moveList[boardIndex], "\n");
4686 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4687 switch (MateTest(boards[boardIndex],
4688 PosFlags(boardIndex), EP_UNKNOWN)) {
4694 strcat(parseList[boardIndex - 1], "+");
4697 strcat(parseList[boardIndex - 1], "#");
4704 /* Apply a move to the given board */
4706 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4707 int fromX, fromY, toX, toY;
4711 ChessSquare captured = board[toY][toX];
4712 if (fromY == DROP_RANK) {
4714 board[toY][toX] = (ChessSquare) fromX;
4715 } else if (fromX == toX && fromY == toY) {
4717 } else if (fromY == 0 && fromX == 4
4718 && board[fromY][fromX] == WhiteKing
4719 && toY == 0 && toX == 6) {
4720 board[fromY][fromX] = EmptySquare;
4721 board[toY][toX] = WhiteKing;
4722 board[fromY][7] = EmptySquare;
4723 board[toY][5] = WhiteRook;
4724 } else if (fromY == 0 && fromX == 4
4725 && board[fromY][fromX] == WhiteKing
4726 && toY == 0 && toX == 2) {
4727 board[fromY][fromX] = EmptySquare;
4728 board[toY][toX] = WhiteKing;
4729 board[fromY][0] = EmptySquare;
4730 board[toY][3] = WhiteRook;
4731 } else if (fromY == 0 && fromX == 3
4732 && board[fromY][fromX] == WhiteKing
4733 && toY == 0 && toX == 5) {
4734 board[fromY][fromX] = EmptySquare;
4735 board[toY][toX] = WhiteKing;
4736 board[fromY][7] = EmptySquare;
4737 board[toY][4] = WhiteRook;
4738 } else if (fromY == 0 && fromX == 3
4739 && board[fromY][fromX] == WhiteKing
4740 && toY == 0 && toX == 1) {
4741 board[fromY][fromX] = EmptySquare;
4742 board[toY][toX] = WhiteKing;
4743 board[fromY][0] = EmptySquare;
4744 board[toY][2] = WhiteRook;
4745 } else if (board[fromY][fromX] == WhitePawn
4747 /* white pawn promotion */
4748 board[7][toX] = CharToPiece(ToUpper(promoChar));
4749 if (board[7][toX] == EmptySquare) {
4750 board[7][toX] = WhiteQueen;
4752 board[fromY][fromX] = EmptySquare;
4753 } else if ((fromY == 4)
4755 && (board[fromY][fromX] == WhitePawn)
4756 && (board[toY][toX] == EmptySquare)) {
4757 board[fromY][fromX] = EmptySquare;
4758 board[toY][toX] = WhitePawn;
4759 captured = board[toY - 1][toX];
4760 board[toY - 1][toX] = EmptySquare;
4761 } else if (fromY == 7 && fromX == 4
4762 && board[fromY][fromX] == BlackKing
4763 && toY == 7 && toX == 6) {
4764 board[fromY][fromX] = EmptySquare;
4765 board[toY][toX] = BlackKing;
4766 board[fromY][7] = EmptySquare;
4767 board[toY][5] = BlackRook;
4768 } else if (fromY == 7 && fromX == 4
4769 && board[fromY][fromX] == BlackKing
4770 && toY == 7 && toX == 2) {
4771 board[fromY][fromX] = EmptySquare;
4772 board[toY][toX] = BlackKing;
4773 board[fromY][0] = EmptySquare;
4774 board[toY][3] = BlackRook;
4775 } else if (fromY == 7 && fromX == 3
4776 && board[fromY][fromX] == BlackKing
4777 && toY == 7 && toX == 5) {
4778 board[fromY][fromX] = EmptySquare;
4779 board[toY][toX] = BlackKing;
4780 board[fromY][7] = EmptySquare;
4781 board[toY][4] = BlackRook;
4782 } else if (fromY == 7 && fromX == 3
4783 && board[fromY][fromX] == BlackKing
4784 && toY == 7 && toX == 1) {
4785 board[fromY][fromX] = EmptySquare;
4786 board[toY][toX] = BlackKing;
4787 board[fromY][0] = EmptySquare;
4788 board[toY][2] = BlackRook;
4789 } else if (board[fromY][fromX] == BlackPawn
4791 /* black pawn promotion */
4792 board[0][toX] = CharToPiece(ToLower(promoChar));
4793 if (board[0][toX] == EmptySquare) {
4794 board[0][toX] = BlackQueen;
4796 board[fromY][fromX] = EmptySquare;
4797 } else if ((fromY == 3)
4799 && (board[fromY][fromX] == BlackPawn)
4800 && (board[toY][toX] == EmptySquare)) {
4801 board[fromY][fromX] = EmptySquare;
4802 board[toY][toX] = BlackPawn;
4803 captured = board[toY + 1][toX];
4804 board[toY + 1][toX] = EmptySquare;
4806 board[toY][toX] = board[fromY][fromX];
4807 board[fromY][fromX] = EmptySquare;
4809 if (gameInfo.variant == VariantCrazyhouse) {
4811 /* !!A lot more code needs to be written to support holdings */
4812 if (fromY == DROP_RANK) {
4813 /* Delete from holdings */
4814 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4816 if (captured != EmptySquare) {
4817 /* Add to holdings */
4818 if (captured < BlackPawn) {
4819 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4821 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4825 } else if (gameInfo.variant == VariantAtomic) {
4826 if (captured != EmptySquare) {
4828 for (y = toY-1; y <= toY+1; y++) {
4829 for (x = toX-1; x <= toX+1; x++) {
4830 if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4831 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4832 board[y][x] = EmptySquare;
4836 board[toY][toX] = EmptySquare;
4841 /* Updates forwardMostMove */
4843 MakeMove(fromX, fromY, toX, toY, promoChar)
4844 int fromX, fromY, toX, toY;
4848 if (forwardMostMove >= MAX_MOVES) {
4849 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4854 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4855 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4856 if (commentList[forwardMostMove] != NULL) {
4857 free(commentList[forwardMostMove]);
4858 commentList[forwardMostMove] = NULL;
4860 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4861 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4862 gameInfo.result = GameUnfinished;
4863 if (gameInfo.resultDetails != NULL) {
4864 free(gameInfo.resultDetails);
4865 gameInfo.resultDetails = NULL;
4867 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4868 moveList[forwardMostMove - 1]);
4869 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4870 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4871 fromY, fromX, toY, toX, promoChar,
4872 parseList[forwardMostMove - 1]);
4873 switch (MateTest(boards[forwardMostMove],
4874 PosFlags(forwardMostMove), EP_UNKNOWN)){
4880 strcat(parseList[forwardMostMove - 1], "+");
4883 strcat(parseList[forwardMostMove - 1], "#");
4888 /* Updates currentMove if not pausing */
4890 ShowMove(fromX, fromY, toX, toY)
4892 int instant = (gameMode == PlayFromGameFile) ?
4893 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4894 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4896 if (forwardMostMove == currentMove + 1) {
4897 AnimateMove(boards[forwardMostMove - 1],
4898 fromX, fromY, toX, toY);
4900 if (appData.highlightLastMove) {
4901 SetHighlights(fromX, fromY, toX, toY);
4904 currentMove = forwardMostMove;
4907 if (instant) return;
4908 DisplayMove(currentMove - 1);
4909 DrawPosition(FALSE, boards[currentMove]);
4910 DisplayBothClocks();
4911 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4916 InitChessProgram(cps)
4917 ChessProgramState *cps;
4920 if (appData.noChessProgram) return;
4921 hintRequested = FALSE;
4922 bookRequested = FALSE;
4923 SendToProgram(cps->initString, cps);
4924 if (gameInfo.variant != VariantNormal &&
4925 gameInfo.variant != VariantLoadable) {
4926 char *v = VariantName(gameInfo.variant);
4927 if (StrStr(cps->variants, v) == NULL) {
4928 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4929 DisplayFatalError(buf, 0, 1);
4932 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4933 SendToProgram(buf, cps);
4936 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4937 SendToProgram(buf, cps);
4939 cps->maybeThinking = FALSE;
4940 cps->offeredDraw = 0;
4941 if (!appData.icsActive) {
4942 SendTimeControl(cps, movesPerSession, timeControl,
4943 timeIncrement, appData.searchDepth,
4946 if (appData.showThinking) {
4947 SendToProgram("post\n", cps);
4949 SendToProgram("hard\n", cps);
4950 if (!appData.ponderNextMove) {
4951 /* Warning: "easy" is a toggle in GNU Chess, so don't send
4952 it without being sure what state we are in first. "hard"
4953 is not a toggle, so that one is OK.
4955 SendToProgram("easy\n", cps);
4958 sprintf(buf, "ping %d\n", ++cps->lastPing);
4959 SendToProgram(buf, cps);
4961 cps->initDone = TRUE;
4966 StartChessProgram(cps)
4967 ChessProgramState *cps;
4972 if (appData.noChessProgram) return;
4973 cps->initDone = FALSE;
4975 if (strcmp(cps->host, "localhost") == 0) {
4976 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4977 } else if (*appData.remoteShell == NULLCHAR) {
4978 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4980 if (*appData.remoteUser == NULLCHAR) {
4981 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4984 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
4985 cps->host, appData.remoteUser, cps->program);
4987 err = StartChildProcess(buf, "", &cps->pr);
4991 sprintf(buf, "Startup failure on '%s'", cps->program);
4992 DisplayFatalError(buf, err, 1);
4998 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
4999 if (cps->protocolVersion > 1) {
5000 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5001 SendToProgram(buf, cps);
5003 SendToProgram("xboard\n", cps);
5009 TwoMachinesEventIfReady P((void))
5011 if (first.lastPing != first.lastPong) {
5012 DisplayMessage("", "Waiting for first chess program");
5013 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5016 if (second.lastPing != second.lastPong) {
5017 DisplayMessage("", "Waiting for second chess program");
5018 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5026 NextMatchGame P((void))
5029 if (*appData.loadGameFile != NULLCHAR) {
5030 LoadGameFromFile(appData.loadGameFile,
5031 appData.loadGameIndex,
5032 appData.loadGameFile, FALSE);
5033 } else if (*appData.loadPositionFile != NULLCHAR) {
5034 LoadPositionFromFile(appData.loadPositionFile,
5035 appData.loadPositionIndex,
5036 appData.loadPositionFile);
5038 TwoMachinesEventIfReady();
5042 GameEnds(result, resultDetails, whosays)
5044 char *resultDetails;
5047 GameMode nextGameMode;
5050 if (appData.debugMode) {
5051 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5052 result, resultDetails ? resultDetails : "(null)", whosays);
5055 if (appData.icsActive && whosays == GE_ENGINE) {
5056 /* If we are playing on ICS, the server decides when the
5057 game is over, but the engine can offer to draw, claim
5061 if (appData.zippyPlay && first.initDone) {
5062 if (result == GameIsDrawn) {
5063 /* In case draw still needs to be claimed */
5064 SendToICS(ics_prefix);
5065 SendToICS("draw\n");
5066 } else if (StrCaseStr(resultDetails, "resign")) {
5067 SendToICS(ics_prefix);
5068 SendToICS("resign\n");
5075 /* If we're loading the game from a file, stop */
5076 if (whosays == GE_FILE) {
5077 (void) StopLoadGameTimer();
5081 /* Cancel draw offers */
5082 first.offeredDraw = second.offeredDraw = 0;
5084 /* If this is an ICS game, only ICS can really say it's done;
5085 if not, anyone can. */
5086 isIcsGame = (gameMode == IcsPlayingWhite ||
5087 gameMode == IcsPlayingBlack ||
5088 gameMode == IcsObserving ||
5089 gameMode == IcsExamining);
5091 if (!isIcsGame || whosays == GE_ICS) {
5092 /* OK -- not an ICS game, or ICS said it was done */
5094 if (!isIcsGame && !appData.noChessProgram)
5095 SetUserThinkingEnables();
5097 if (resultDetails != NULL) {
5098 gameInfo.result = result;
5099 gameInfo.resultDetails = StrSave(resultDetails);
5101 /* Tell program how game ended in case it is learning */
5102 if (gameMode == MachinePlaysWhite ||
5103 gameMode == MachinePlaysBlack ||
5104 gameMode == TwoMachinesPlay ||
5105 gameMode == IcsPlayingWhite ||
5106 gameMode == IcsPlayingBlack ||
5107 gameMode == BeginningOfGame) {
5109 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5111 if (first.pr != NoProc) {
5112 SendToProgram(buf, &first);
5114 if (second.pr != NoProc &&
5115 gameMode == TwoMachinesPlay) {
5116 SendToProgram(buf, &second);
5120 /* display last move only if game was not loaded from file */
5121 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5122 DisplayMove(currentMove - 1);
5124 if (forwardMostMove != 0) {
5125 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5126 if (*appData.saveGameFile != NULLCHAR) {
5127 SaveGameToFile(appData.saveGameFile, TRUE);
5128 } else if (appData.autoSaveGames) {
5131 if (*appData.savePositionFile != NULLCHAR) {
5132 SavePositionToFile(appData.savePositionFile);
5138 if (appData.icsActive) {
5139 if (appData.quietPlay &&
5140 (gameMode == IcsPlayingWhite ||
5141 gameMode == IcsPlayingBlack)) {
5142 SendToICS(ics_prefix);
5143 SendToICS("set shout 1\n");
5145 nextGameMode = IcsIdle;
5146 ics_user_moved = FALSE;
5147 /* clean up premove. It's ugly when the game has ended and the
5148 * premove highlights are still on the board.
5152 ClearPremoveHighlights();
5153 DrawPosition(FALSE, boards[currentMove]);
5155 if (whosays == GE_ICS) {
5158 if (gameMode == IcsPlayingWhite)
5160 else if(gameMode == IcsPlayingBlack)
5164 if (gameMode == IcsPlayingBlack)
5166 else if(gameMode == IcsPlayingWhite)
5173 PlayIcsUnfinishedSound();
5176 } else if (gameMode == EditGame ||
5177 gameMode == PlayFromGameFile ||
5178 gameMode == AnalyzeMode ||
5179 gameMode == AnalyzeFile) {
5180 nextGameMode = gameMode;
5182 nextGameMode = EndOfGame;
5187 nextGameMode = gameMode;
5190 if (appData.noChessProgram) {
5191 gameMode = nextGameMode;
5197 /* Put first chess program into idle state */
5198 if (first.pr != NoProc &&
5199 (gameMode == MachinePlaysWhite ||
5200 gameMode == MachinePlaysBlack ||
5201 gameMode == TwoMachinesPlay ||
5202 gameMode == IcsPlayingWhite ||
5203 gameMode == IcsPlayingBlack ||
5204 gameMode == BeginningOfGame)) {
5205 SendToProgram("force\n", &first);
5206 if (first.usePing) {
5208 sprintf(buf, "ping %d\n", ++first.lastPing);
5209 SendToProgram(buf, &first);
5212 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5213 /* Kill off first chess program */
5214 if (first.isr != NULL)
5215 RemoveInputSource(first.isr);
5218 if (first.pr != NoProc) {
5220 SendToProgram("quit\n", &first);
5221 DestroyChildProcess(first.pr, first.useSigterm);
5226 /* Put second chess program into idle state */
5227 if (second.pr != NoProc &&
5228 gameMode == TwoMachinesPlay) {
5229 SendToProgram("force\n", &second);
5230 if (second.usePing) {
5232 sprintf(buf, "ping %d\n", ++second.lastPing);
5233 SendToProgram(buf, &second);
5236 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5237 /* Kill off second chess program */
5238 if (second.isr != NULL)
5239 RemoveInputSource(second.isr);
5242 if (second.pr != NoProc) {
5243 SendToProgram("quit\n", &second);
5244 DestroyChildProcess(second.pr, second.useSigterm);
5249 if (matchMode && gameMode == TwoMachinesPlay) {
5252 if (first.twoMachinesColor[0] == 'w') {
5259 if (first.twoMachinesColor[0] == 'b') {
5268 if (matchGame < appData.matchGames) {
5270 tmp = first.twoMachinesColor;
5271 first.twoMachinesColor = second.twoMachinesColor;
5272 second.twoMachinesColor = tmp;
5273 gameMode = nextGameMode;
5275 ScheduleDelayedEvent(NextMatchGame, 10000);
5279 gameMode = nextGameMode;
5280 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5281 first.tidy, second.tidy,
5282 first.matchWins, second.matchWins,
5283 appData.matchGames - (first.matchWins + second.matchWins));
5284 DisplayFatalError(buf, 0, 0);
5287 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5288 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5290 gameMode = nextGameMode;
5294 /* Assumes program was just initialized (initString sent).
5295 Leaves program in force mode. */
5297 FeedMovesToProgram(cps, upto)
5298 ChessProgramState *cps;
5303 if (appData.debugMode)
5304 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5305 startedFromSetupPosition ? "position and " : "",
5306 backwardMostMove, upto, cps->which);
5307 SendToProgram("force\n", cps);
5308 if (startedFromSetupPosition) {
5309 SendBoard(cps, backwardMostMove);
5311 for (i = backwardMostMove; i < upto; i++) {
5312 SendMoveToProgram(i, cps);
5318 ResurrectChessProgram()
5320 /* The chess program may have exited.
5321 If so, restart it and feed it all the moves made so far. */
5323 if (appData.noChessProgram || first.pr != NoProc) return;
5325 StartChessProgram(&first);
5326 InitChessProgram(&first);
5327 FeedMovesToProgram(&first, currentMove);
5329 if (!first.sendTime) {
5330 /* can't tell gnuchess what its clock should read,
5331 so we bow to its notion. */
5333 timeRemaining[0][currentMove] = whiteTimeRemaining;
5334 timeRemaining[1][currentMove] = blackTimeRemaining;
5337 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5338 first.analysisSupport) {
5339 SendToProgram("analyze\n", &first);
5340 first.analyzing = TRUE;
5353 if (appData.debugMode) {
5354 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5355 redraw, init, gameMode);
5358 pausing = pauseExamInvalid = FALSE;
5359 startedFromSetupPosition = blackPlaysFirst = FALSE;
5361 whiteFlag = blackFlag = FALSE;
5362 userOfferedDraw = FALSE;
5363 hintRequested = bookRequested = FALSE;
5364 first.maybeThinking = FALSE;
5365 second.maybeThinking = FALSE;
5366 thinkOutput[0] = NULLCHAR;
5367 lastHint[0] = NULLCHAR;
5368 ClearGameInfo(&gameInfo);
5369 gameInfo.variant = StringToVariant(appData.variant);
5370 ics_user_moved = ics_clock_paused = FALSE;
5371 ics_getting_history = H_FALSE;
5373 white_holding[0] = black_holding[0] = NULLCHAR;
5374 ClearProgramStats();
5378 flipView = appData.flipView;
5379 ClearPremoveHighlights();
5381 alarmSounded = FALSE;
5383 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5385 gameMode = BeginningOfGame;
5387 InitPosition(redraw);
5388 for (i = 0; i < MAX_MOVES; i++) {
5389 if (commentList[i] != NULL) {
5390 free(commentList[i]);
5391 commentList[i] = NULL;
5395 timeRemaining[0][0] = whiteTimeRemaining;
5396 timeRemaining[1][0] = blackTimeRemaining;
5397 if (first.pr == NULL) {
5398 StartChessProgram(&first);
5400 if (init) InitChessProgram(&first);
5402 DisplayMessage("", "");
5403 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5410 if (!AutoPlayOneMove())
5412 if (matchMode || appData.timeDelay == 0)
5414 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5416 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5425 int fromX, fromY, toX, toY;
5427 if (appData.debugMode) {
5428 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5431 if (gameMode != PlayFromGameFile)
5434 if (currentMove >= forwardMostMove) {
5435 gameMode = EditGame;
5440 toX = moveList[currentMove][2] - 'a';
5441 toY = moveList[currentMove][3] - '1';
5443 if (moveList[currentMove][1] == '@') {
5444 if (appData.highlightLastMove) {
5445 SetHighlights(-1, -1, toX, toY);
5448 fromX = moveList[currentMove][0] - 'a';
5449 fromY = moveList[currentMove][1] - '1';
5450 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5452 if (appData.highlightLastMove) {
5453 SetHighlights(fromX, fromY, toX, toY);
5456 DisplayMove(currentMove);
5457 SendMoveToProgram(currentMove++, &first);
5458 DisplayBothClocks();
5459 DrawPosition(FALSE, boards[currentMove]);
5460 if (commentList[currentMove] != NULL) {
5461 DisplayComment(currentMove - 1, commentList[currentMove]);
5468 LoadGameOneMove(readAhead)
5469 ChessMove readAhead;
5471 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5472 char promoChar = NULLCHAR;
5477 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5478 gameMode != AnalyzeMode && gameMode != Training) {
5483 yyboardindex = forwardMostMove;
5484 if (readAhead != (ChessMove)0) {
5485 moveType = readAhead;
5487 if (gameFileFP == NULL)
5489 moveType = (ChessMove) yylex();
5495 if (appData.debugMode)
5496 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5498 if (*p == '{' || *p == '[' || *p == '(') {
5499 p[strlen(p) - 1] = NULLCHAR;
5503 /* append the comment but don't display it */
5504 while (*p == '\n') p++;
5505 AppendComment(currentMove, p);
5508 case WhiteCapturesEnPassant:
5509 case BlackCapturesEnPassant:
5510 case WhitePromotionQueen:
5511 case BlackPromotionQueen:
5512 case WhitePromotionRook:
5513 case BlackPromotionRook:
5514 case WhitePromotionBishop:
5515 case BlackPromotionBishop:
5516 case WhitePromotionKnight:
5517 case BlackPromotionKnight:
5518 case WhitePromotionKing:
5519 case BlackPromotionKing:
5521 case WhiteKingSideCastle:
5522 case WhiteQueenSideCastle:
5523 case BlackKingSideCastle:
5524 case BlackQueenSideCastle:
5525 case WhiteKingSideCastleWild:
5526 case WhiteQueenSideCastleWild:
5527 case BlackKingSideCastleWild:
5528 case BlackQueenSideCastleWild:
5529 if (appData.debugMode)
5530 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5531 fromX = currentMoveString[0] - 'a';
5532 fromY = currentMoveString[1] - '1';
5533 toX = currentMoveString[2] - 'a';
5534 toY = currentMoveString[3] - '1';
5535 promoChar = currentMoveString[4];
5540 if (appData.debugMode)
5541 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5542 fromX = moveType == WhiteDrop ?
5543 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5544 (int) CharToPiece(ToLower(currentMoveString[0]));
5546 toX = currentMoveString[2] - 'a';
5547 toY = currentMoveString[3] - '1';
5553 case GameUnfinished:
5554 if (appData.debugMode)
5555 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5556 p = strchr(yy_text, '{');
5557 if (p == NULL) p = strchr(yy_text, '(');
5560 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5562 q = strchr(p, *p == '{' ? '}' : ')');
5563 if (q != NULL) *q = NULLCHAR;
5566 GameEnds(moveType, p, GE_FILE);
5568 if (cmailMsgLoaded) {
5570 flipView = WhiteOnMove(currentMove);
5571 if (moveType == GameUnfinished) flipView = !flipView;
5572 if (appData.debugMode)
5573 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5577 case (ChessMove) 0: /* end of file */
5578 if (appData.debugMode)
5579 fprintf(debugFP, "Parser hit end of file\n");
5580 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5586 if (WhiteOnMove(currentMove)) {
5587 GameEnds(BlackWins, "Black mates", GE_FILE);
5589 GameEnds(WhiteWins, "White mates", GE_FILE);
5593 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5600 if (lastLoadGameStart == GNUChessGame) {
5601 /* GNUChessGames have numbers, but they aren't move numbers */
5602 if (appData.debugMode)
5603 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5604 yy_text, (int) moveType);
5605 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5607 /* else fall thru */
5612 /* Reached start of next game in file */
5613 if (appData.debugMode)
5614 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5615 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5621 if (WhiteOnMove(currentMove)) {
5622 GameEnds(BlackWins, "Black mates", GE_FILE);
5624 GameEnds(WhiteWins, "White mates", GE_FILE);
5628 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5634 case PositionDiagram: /* should not happen; ignore */
5635 case ElapsedTime: /* ignore */
5636 case NAG: /* ignore */
5637 if (appData.debugMode)
5638 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5639 yy_text, (int) moveType);
5640 return LoadGameOneMove((ChessMove)0); /* tail recursion */
5643 if (appData.testLegality) {
5644 if (appData.debugMode)
5645 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5646 sprintf(move, _("Illegal move: %d.%s%s"),
5647 (forwardMostMove / 2) + 1,
5648 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5649 DisplayError(move, 0);
5652 if (appData.debugMode)
5653 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5654 yy_text, currentMoveString);
5655 fromX = currentMoveString[0] - 'a';
5656 fromY = currentMoveString[1] - '1';
5657 toX = currentMoveString[2] - 'a';
5658 toY = currentMoveString[3] - '1';
5659 promoChar = currentMoveString[4];
5664 if (appData.debugMode)
5665 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5666 sprintf(move, _("Ambiguous move: %d.%s%s"),
5667 (forwardMostMove / 2) + 1,
5668 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5669 DisplayError(move, 0);
5674 case ImpossibleMove:
5675 if (appData.debugMode)
5676 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5677 sprintf(move, _("Illegal move: %d.%s%s"),
5678 (forwardMostMove / 2) + 1,
5679 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5680 DisplayError(move, 0);
5686 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5687 DrawPosition(FALSE, boards[currentMove]);
5688 DisplayBothClocks();
5689 if (!appData.matchMode && commentList[currentMove] != NULL)
5690 DisplayComment(currentMove - 1, commentList[currentMove]);
5692 (void) StopLoadGameTimer();
5694 cmailOldMove = forwardMostMove;
5697 /* currentMoveString is set as a side-effect of yylex */
5698 strcat(currentMoveString, "\n");
5699 strcpy(moveList[forwardMostMove], currentMoveString);
5701 thinkOutput[0] = NULLCHAR;
5702 MakeMove(fromX, fromY, toX, toY, promoChar);
5703 currentMove = forwardMostMove;
5708 /* Load the nth game from the given file */
5710 LoadGameFromFile(filename, n, title, useList)
5714 /*Boolean*/ int useList;
5719 if (strcmp(filename, "-") == 0) {
5723 f = fopen(filename, "rb");
5725 sprintf(buf, _("Can't open \"%s\""), filename);
5726 DisplayError(buf, errno);
5730 if (fseek(f, 0, 0) == -1) {
5731 /* f is not seekable; probably a pipe */
5734 if (useList && n == 0) {
5735 int error = GameListBuild(f);
5737 DisplayError(_("Cannot build game list"), error);
5738 } else if (!ListEmpty(&gameList) &&
5739 ((ListGame *) gameList.tailPred)->number > 1) {
5740 GameListPopUp(f, title);
5747 return LoadGame(f, n, title, FALSE);
5752 MakeRegisteredMove()
5754 int fromX, fromY, toX, toY;
5756 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5757 switch (cmailMoveType[lastLoadGameNumber - 1]) {
5760 if (appData.debugMode)
5761 fprintf(debugFP, "Restoring %s for game %d\n",
5762 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5764 thinkOutput[0] = NULLCHAR;
5765 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5766 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5767 fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5768 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5769 toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5770 promoChar = cmailMove[lastLoadGameNumber - 1][4];
5771 MakeMove(fromX, fromY, toX, toY, promoChar);
5772 ShowMove(fromX, fromY, toX, toY);
5774 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5781 if (WhiteOnMove(currentMove)) {
5782 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5784 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5789 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5796 if (WhiteOnMove(currentMove)) {
5797 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5799 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5804 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5815 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5817 CmailLoadGame(f, gameNumber, title, useList)
5825 if (gameNumber > nCmailGames) {
5826 DisplayError(_("No more games in this message"), 0);
5829 if (f == lastLoadGameFP) {
5830 int offset = gameNumber - lastLoadGameNumber;
5832 cmailMsg[0] = NULLCHAR;
5833 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5834 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5835 nCmailMovesRegistered--;
5837 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5838 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5839 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5842 if (! RegisterMove()) return FALSE;
5846 retVal = LoadGame(f, gameNumber, title, useList);
5848 /* Make move registered during previous look at this game, if any */
5849 MakeRegisteredMove();
5851 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5852 commentList[currentMove]
5853 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5854 DisplayComment(currentMove - 1, commentList[currentMove]);
5860 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5865 int gameNumber = lastLoadGameNumber + offset;
5866 if (lastLoadGameFP == NULL) {
5867 DisplayError(_("No game has been loaded yet"), 0);
5870 if (gameNumber <= 0) {
5871 DisplayError(_("Can't back up any further"), 0);
5874 if (cmailMsgLoaded) {
5875 return CmailLoadGame(lastLoadGameFP, gameNumber,
5876 lastLoadGameTitle, lastLoadGameUseList);
5878 return LoadGame(lastLoadGameFP, gameNumber,
5879 lastLoadGameTitle, lastLoadGameUseList);
5885 /* Load the nth game from open file f */
5887 LoadGame(f, gameNumber, title, useList)
5895 int gn = gameNumber;
5896 ListGame *lg = NULL;
5899 GameMode oldGameMode;
5901 if (appData.debugMode)
5902 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5904 if (gameMode == Training )
5905 SetTrainingModeOff();
5907 oldGameMode = gameMode;
5908 if (gameMode != BeginningOfGame) {
5913 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5914 fclose(lastLoadGameFP);
5918 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5921 fseek(f, lg->offset, 0);
5922 GameListHighlight(gameNumber);
5926 DisplayError(_("Game number out of range"), 0);
5931 if (fseek(f, 0, 0) == -1) {
5932 if (f == lastLoadGameFP ?
5933 gameNumber == lastLoadGameNumber + 1 :
5937 DisplayError(_("Can't seek on game file"), 0);
5943 lastLoadGameNumber = gameNumber;
5944 strcpy(lastLoadGameTitle, title);
5945 lastLoadGameUseList = useList;
5950 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5951 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5952 lg->gameInfo.black);
5954 } else if (*title != NULLCHAR) {
5955 if (gameNumber > 1) {
5956 sprintf(buf, "%s %d", title, gameNumber);
5959 DisplayTitle(title);
5963 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5964 gameMode = PlayFromGameFile;
5968 currentMove = forwardMostMove = backwardMostMove = 0;
5969 CopyBoard(boards[0], initialPosition);
5973 * Skip the first gn-1 games in the file.
5974 * Also skip over anything that precedes an identifiable
5975 * start of game marker, to avoid being confused by
5976 * garbage at the start of the file. Currently
5977 * recognized start of game markers are the move number "1",
5978 * the pattern "gnuchess .* game", the pattern
5979 * "^[#;%] [^ ]* game file", and a PGN tag block.
5980 * A game that starts with one of the latter two patterns
5981 * will also have a move number 1, possibly
5982 * following a position diagram.
5983 * 5-4-02: Let's try being more lenient and allowing a game to
5984 * start with an unnumbered move. Does that break anything?
5986 cm = lastLoadGameStart = (ChessMove) 0;
5988 yyboardindex = forwardMostMove;
5989 cm = (ChessMove) yylex();
5992 if (cmailMsgLoaded) {
5993 nCmailGames = CMAIL_MAX_GAMES - gn;
5996 DisplayError(_("Game not found in file"), 0);
6003 lastLoadGameStart = cm;
6007 switch (lastLoadGameStart) {
6014 gn--; /* count this game */
6015 lastLoadGameStart = cm;
6024 switch (lastLoadGameStart) {
6029 gn--; /* count this game */
6030 lastLoadGameStart = cm;
6033 lastLoadGameStart = cm; /* game counted already */
6041 yyboardindex = forwardMostMove;
6042 cm = (ChessMove) yylex();
6043 } while (cm == PGNTag || cm == Comment);
6050 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6051 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
6052 != CMAIL_OLD_RESULT) {
6054 cmailResult[ CMAIL_MAX_GAMES
6055 - gn - 1] = CMAIL_OLD_RESULT;
6061 /* Only a NormalMove can be at the start of a game
6062 * without a position diagram. */
6063 if (lastLoadGameStart == (ChessMove) 0) {
6065 lastLoadGameStart = MoveNumberOne;
6074 if (appData.debugMode)
6075 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6077 if (cm == XBoardGame) {
6078 /* Skip any header junk before position diagram and/or move 1 */
6080 yyboardindex = forwardMostMove;
6081 cm = (ChessMove) yylex();
6083 if (cm == (ChessMove) 0 ||
6084 cm == GNUChessGame || cm == XBoardGame) {
6085 /* Empty game; pretend end-of-file and handle later */
6090 if (cm == MoveNumberOne || cm == PositionDiagram ||
6091 cm == PGNTag || cm == Comment)
6094 } else if (cm == GNUChessGame) {
6095 if (gameInfo.event != NULL) {
6096 free(gameInfo.event);
6098 gameInfo.event = StrSave(yy_text);
6101 startedFromSetupPosition = FALSE;
6102 while (cm == PGNTag) {
6103 if (appData.debugMode)
6104 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6105 err = ParsePGNTag(yy_text, &gameInfo);
6106 if (!err) numPGNTags++;
6108 if (gameInfo.fen != NULL) {
6109 Board initial_position;
6110 startedFromSetupPosition = TRUE;
6111 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6113 DisplayError(_("Bad FEN position in file"), 0);
6116 CopyBoard(boards[0], initial_position);
6117 if (blackPlaysFirst) {
6118 currentMove = forwardMostMove = backwardMostMove = 1;
6119 CopyBoard(boards[1], initial_position);
6120 strcpy(moveList[0], "");
6121 strcpy(parseList[0], "");
6122 timeRemaining[0][1] = whiteTimeRemaining;
6123 timeRemaining[1][1] = blackTimeRemaining;
6124 if (commentList[0] != NULL) {
6125 commentList[1] = commentList[0];
6126 commentList[0] = NULL;
6129 currentMove = forwardMostMove = backwardMostMove = 0;
6131 yyboardindex = forwardMostMove;
6133 gameInfo.fen = NULL;
6136 yyboardindex = forwardMostMove;
6137 cm = (ChessMove) yylex();
6139 /* Handle comments interspersed among the tags */
6140 while (cm == Comment) {
6142 if (appData.debugMode)
6143 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6145 if (*p == '{' || *p == '[' || *p == '(') {
6146 p[strlen(p) - 1] = NULLCHAR;
6149 while (*p == '\n') p++;
6150 AppendComment(currentMove, p);
6151 yyboardindex = forwardMostMove;
6152 cm = (ChessMove) yylex();
6156 /* don't rely on existence of Event tag since if game was
6157 * pasted from clipboard the Event tag may not exist
6159 if (numPGNTags > 0){
6161 if (gameInfo.variant == VariantNormal) {
6162 gameInfo.variant = StringToVariant(gameInfo.event);
6165 tags = PGNTags(&gameInfo);
6166 TagsPopUp(tags, CmailMsg());
6170 /* Make something up, but don't display it now */
6175 if (cm == PositionDiagram) {
6178 Board initial_position;
6180 if (appData.debugMode)
6181 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6183 if (!startedFromSetupPosition) {
6185 for (i = BOARD_SIZE - 1; i >= 0; i--)
6186 for (j = 0; j < BOARD_SIZE; p++)
6196 initial_position[i][j++] = CharToPiece(*p);
6199 while (*p == ' ' || *p == '\t' ||
6200 *p == '\n' || *p == '\r') p++;
6202 if (strncmp(p, "black", strlen("black"))==0)
6203 blackPlaysFirst = TRUE;
6205 blackPlaysFirst = FALSE;
6206 startedFromSetupPosition = TRUE;
6208 CopyBoard(boards[0], initial_position);
6209 if (blackPlaysFirst) {
6210 currentMove = forwardMostMove = backwardMostMove = 1;
6211 CopyBoard(boards[1], initial_position);
6212 strcpy(moveList[0], "");
6213 strcpy(parseList[0], "");
6214 timeRemaining[0][1] = whiteTimeRemaining;
6215 timeRemaining[1][1] = blackTimeRemaining;
6216 if (commentList[0] != NULL) {
6217 commentList[1] = commentList[0];
6218 commentList[0] = NULL;
6221 currentMove = forwardMostMove = backwardMostMove = 0;
6224 yyboardindex = forwardMostMove;
6225 cm = (ChessMove) yylex();
6228 if (first.pr == NoProc) {
6229 StartChessProgram(&first);
6231 InitChessProgram(&first);
6232 SendToProgram("force\n", &first);
6233 if (startedFromSetupPosition) {
6234 SendBoard(&first, forwardMostMove);
6235 DisplayBothClocks();
6238 while (cm == Comment) {
6240 if (appData.debugMode)
6241 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6243 if (*p == '{' || *p == '[' || *p == '(') {
6244 p[strlen(p) - 1] = NULLCHAR;
6247 while (*p == '\n') p++;
6248 AppendComment(currentMove, p);
6249 yyboardindex = forwardMostMove;
6250 cm = (ChessMove) yylex();
6253 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6254 cm == WhiteWins || cm == BlackWins ||
6255 cm == GameIsDrawn || cm == GameUnfinished) {
6256 DisplayMessage("", _("No moves in game"));
6257 if (cmailMsgLoaded) {
6258 if (appData.debugMode)
6259 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6263 DrawPosition(FALSE, boards[currentMove]);
6264 DisplayBothClocks();
6265 gameMode = EditGame;
6272 if (commentList[currentMove] != NULL) {
6273 if (!matchMode && (pausing || appData.timeDelay != 0)) {
6274 DisplayComment(currentMove - 1, commentList[currentMove]);
6277 if (!matchMode && appData.timeDelay != 0)
6278 DrawPosition(FALSE, boards[currentMove]);
6280 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6281 programStats.ok_to_send = 1;
6284 /* if the first token after the PGN tags is a move
6285 * and not move number 1, retrieve it from the parser
6287 if (cm != MoveNumberOne)
6288 LoadGameOneMove(cm);
6290 /* load the remaining moves from the file */
6291 while (LoadGameOneMove((ChessMove)0)) {
6292 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6293 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6296 /* rewind to the start of the game */
6297 currentMove = backwardMostMove;
6299 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6301 if (oldGameMode == AnalyzeFile ||
6302 oldGameMode == AnalyzeMode) {
6306 if (matchMode || appData.timeDelay == 0) {
6308 gameMode = EditGame;
6310 } else if (appData.timeDelay > 0) {
6314 if (appData.debugMode)
6315 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6319 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6321 ReloadPosition(offset)
6324 int positionNumber = lastLoadPositionNumber + offset;
6325 if (lastLoadPositionFP == NULL) {
6326 DisplayError(_("No position has been loaded yet"), 0);
6329 if (positionNumber <= 0) {
6330 DisplayError(_("Can't back up any further"), 0);
6333 return LoadPosition(lastLoadPositionFP, positionNumber,
6334 lastLoadPositionTitle);
6337 /* Load the nth position from the given file */
6339 LoadPositionFromFile(filename, n, title)
6347 if (strcmp(filename, "-") == 0) {
6348 return LoadPosition(stdin, n, "stdin");
6350 f = fopen(filename, "rb");
6352 sprintf(buf, _("Can't open \"%s\""), filename);
6353 DisplayError(buf, errno);
6356 return LoadPosition(f, n, title);
6361 /* Load the nth position from the given open file, and close it */
6363 LoadPosition(f, positionNumber, title)
6368 char *p, line[MSG_SIZ];
6369 Board initial_position;
6370 int i, j, fenMode, pn;
6372 if (gameMode == Training )
6373 SetTrainingModeOff();
6375 if (gameMode != BeginningOfGame) {
6378 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6379 fclose(lastLoadPositionFP);
6381 if (positionNumber == 0) positionNumber = 1;
6382 lastLoadPositionFP = f;
6383 lastLoadPositionNumber = positionNumber;
6384 strcpy(lastLoadPositionTitle, title);
6385 if (first.pr == NoProc) {
6386 StartChessProgram(&first);
6387 InitChessProgram(&first);
6389 pn = positionNumber;
6390 if (positionNumber < 0) {
6391 /* Negative position number means to seek to that byte offset */
6392 if (fseek(f, -positionNumber, 0) == -1) {
6393 DisplayError(_("Can't seek on position file"), 0);
6398 if (fseek(f, 0, 0) == -1) {
6399 if (f == lastLoadPositionFP ?
6400 positionNumber == lastLoadPositionNumber + 1 :
6401 positionNumber == 1) {
6404 DisplayError(_("Can't seek on position file"), 0);
6409 /* See if this file is FEN or old-style xboard */
6410 if (fgets(line, MSG_SIZ, f) == NULL) {
6411 DisplayError(_("Position not found in file"), 0);
6419 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
6420 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
6421 case '1': case '2': case '3': case '4': case '5': case '6':
6428 if (fenMode || line[0] == '#') pn--;
6430 /* skip postions before number pn */
6431 if (fgets(line, MSG_SIZ, f) == NULL) {
6433 DisplayError(_("Position not found in file"), 0);
6436 if (fenMode || line[0] == '#') pn--;
6441 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6442 DisplayError(_("Bad FEN position in file"), 0);
6446 (void) fgets(line, MSG_SIZ, f);
6447 (void) fgets(line, MSG_SIZ, f);
6449 for (i = BOARD_SIZE - 1; i >= 0; i--) {
6450 (void) fgets(line, MSG_SIZ, f);
6451 for (p = line, j = 0; j < BOARD_SIZE; p++) {
6454 initial_position[i][j++] = CharToPiece(*p);
6458 blackPlaysFirst = FALSE;
6460 (void) fgets(line, MSG_SIZ, f);
6461 if (strncmp(line, "black", strlen("black"))==0)
6462 blackPlaysFirst = TRUE;
6465 startedFromSetupPosition = TRUE;
6467 SendToProgram("force\n", &first);
6468 CopyBoard(boards[0], initial_position);
6469 if (blackPlaysFirst) {
6470 currentMove = forwardMostMove = backwardMostMove = 1;
6471 strcpy(moveList[0], "");
6472 strcpy(parseList[0], "");
6473 CopyBoard(boards[1], initial_position);
6474 DisplayMessage("", _("Black to play"));
6476 currentMove = forwardMostMove = backwardMostMove = 0;
6477 DisplayMessage("", _("White to play"));
6479 SendBoard(&first, forwardMostMove);
6481 if (positionNumber > 1) {
6482 sprintf(line, "%s %d", title, positionNumber);
6485 DisplayTitle(title);
6487 gameMode = EditGame;
6490 timeRemaining[0][1] = whiteTimeRemaining;
6491 timeRemaining[1][1] = blackTimeRemaining;
6492 DrawPosition(FALSE, boards[currentMove]);
6499 CopyPlayerNameIntoFileName(dest, src)
6502 while (*src != NULLCHAR && *src != ',') {
6507 *(*dest)++ = *src++;
6512 char *DefaultFileName(ext)
6515 static char def[MSG_SIZ];
6518 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6520 CopyPlayerNameIntoFileName(&p, gameInfo.white);
6522 CopyPlayerNameIntoFileName(&p, gameInfo.black);
6531 /* Save the current game to the given file */
6533 SaveGameToFile(filename, append)
6540 if (strcmp(filename, "-") == 0) {
6541 return SaveGame(stdout, 0, NULL);
6543 f = fopen(filename, append ? "a" : "w");
6545 sprintf(buf, _("Can't open \"%s\""), filename);
6546 DisplayError(buf, errno);
6549 return SaveGame(f, 0, NULL);
6558 static char buf[MSG_SIZ];
6561 p = strchr(str, ' ');
6562 if (p == NULL) return str;
6563 strncpy(buf, str, p - str);
6564 buf[p - str] = NULLCHAR;
6568 #define PGN_MAX_LINE 75
6570 /* Save game in PGN style and close the file */
6575 int i, offset, linelen, newblock;
6579 int movelen, numlen, blank;
6581 tm = time((time_t *) NULL);
6583 PrintPGNTags(f, &gameInfo);
6585 if (backwardMostMove > 0 || startedFromSetupPosition) {
6586 char *fen = PositionToFEN(backwardMostMove);
6587 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6588 fprintf(f, "\n{--------------\n");
6589 PrintPosition(f, backwardMostMove);
6590 fprintf(f, "--------------}\n");
6596 i = backwardMostMove;
6597 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6601 while (i < forwardMostMove) {
6602 /* Print comments preceding this move */
6603 if (commentList[i] != NULL) {
6604 if (linelen > 0) fprintf(f, "\n");
6605 fprintf(f, "{\n%s}\n", commentList[i]);
6610 /* Format move number */
6612 sprintf(numtext, "%d.", (i - offset)/2 + 1);
6615 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6617 numtext[0] = NULLCHAR;
6620 numlen = strlen(numtext);
6623 /* Print move number */
6624 blank = linelen > 0 && numlen > 0;
6625 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6634 fprintf(f, numtext);
6638 movetext = SavePart(parseList[i]);
6639 movelen = strlen(movetext);
6642 blank = linelen > 0 && movelen > 0;
6643 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6652 fprintf(f, movetext);
6658 /* Start a new line */
6659 if (linelen > 0) fprintf(f, "\n");
6661 /* Print comments after last move */
6662 if (commentList[i] != NULL) {
6663 fprintf(f, "{\n%s}\n", commentList[i]);
6667 if (gameInfo.resultDetails != NULL &&
6668 gameInfo.resultDetails[0] != NULLCHAR) {
6669 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6670 PGNResult(gameInfo.result));
6672 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6679 /* Save game in old style and close the file */
6687 tm = time((time_t *) NULL);
6689 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6692 if (backwardMostMove > 0 || startedFromSetupPosition) {
6693 fprintf(f, "\n[--------------\n");
6694 PrintPosition(f, backwardMostMove);
6695 fprintf(f, "--------------]\n");
6700 i = backwardMostMove;
6701 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6703 while (i < forwardMostMove) {
6704 if (commentList[i] != NULL) {
6705 fprintf(f, "[%s]\n", commentList[i]);
6709 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
6712 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
6714 if (commentList[i] != NULL) {
6718 if (i >= forwardMostMove) {
6722 fprintf(f, "%s\n", parseList[i]);
6727 if (commentList[i] != NULL) {
6728 fprintf(f, "[%s]\n", commentList[i]);
6731 /* This isn't really the old style, but it's close enough */
6732 if (gameInfo.resultDetails != NULL &&
6733 gameInfo.resultDetails[0] != NULLCHAR) {
6734 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6735 gameInfo.resultDetails);
6737 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6744 /* Save the current game to open file f and close the file */
6746 SaveGame(f, dummy, dummy2)
6751 if (gameMode == EditPosition) EditPositionDone();
6752 if (appData.oldSaveStyle)
6753 return SaveGameOldStyle(f);
6755 return SaveGamePGN(f);
6758 /* Save the current position to the given file */
6760 SavePositionToFile(filename)
6766 if (strcmp(filename, "-") == 0) {
6767 return SavePosition(stdout, 0, NULL);
6769 f = fopen(filename, "a");
6771 sprintf(buf, _("Can't open \"%s\""), filename);
6772 DisplayError(buf, errno);
6775 SavePosition(f, 0, NULL);
6781 /* Save the current position to the given open file and close the file */
6783 SavePosition(f, dummy, dummy2)
6791 if (appData.oldSaveStyle) {
6792 tm = time((time_t *) NULL);
6794 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6796 fprintf(f, "[--------------\n");
6797 PrintPosition(f, currentMove);
6798 fprintf(f, "--------------]\n");
6800 fen = PositionToFEN(currentMove);
6801 fprintf(f, "%s\n", fen);
6809 ReloadCmailMsgEvent(unregister)
6813 static char *inFilename = NULL;
6814 static char *outFilename;
6816 struct stat inbuf, outbuf;
6819 /* Any registered moves are unregistered if unregister is set, */
6820 /* i.e. invoked by the signal handler */
6822 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6823 cmailMoveRegistered[i] = FALSE;
6824 if (cmailCommentList[i] != NULL) {
6825 free(cmailCommentList[i]);
6826 cmailCommentList[i] = NULL;
6829 nCmailMovesRegistered = 0;
6832 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6833 cmailResult[i] = CMAIL_NOT_RESULT;
6837 if (inFilename == NULL) {
6838 /* Because the filenames are static they only get malloced once */
6839 /* and they never get freed */
6840 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6841 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6843 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6844 sprintf(outFilename, "%s.out", appData.cmailGameName);
6847 status = stat(outFilename, &outbuf);
6849 cmailMailedMove = FALSE;
6851 status = stat(inFilename, &inbuf);
6852 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6855 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6856 counts the games, notes how each one terminated, etc.
6858 It would be nice to remove this kludge and instead gather all
6859 the information while building the game list. (And to keep it
6860 in the game list nodes instead of having a bunch of fixed-size
6861 parallel arrays.) Note this will require getting each game's
6862 termination from the PGN tags, as the game list builder does
6863 not process the game moves. --mann
6865 cmailMsgLoaded = TRUE;
6866 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6868 /* Load first game in the file or popup game menu */
6869 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6879 char string[MSG_SIZ];
6881 if ( cmailMailedMove
6882 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6883 return TRUE; /* Allow free viewing */
6886 /* Unregister move to ensure that we don't leave RegisterMove */
6887 /* with the move registered when the conditions for registering no */
6889 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6890 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6891 nCmailMovesRegistered --;
6893 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6895 free(cmailCommentList[lastLoadGameNumber - 1]);
6896 cmailCommentList[lastLoadGameNumber - 1] = NULL;
6900 if (cmailOldMove == -1) {
6901 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6905 if (currentMove > cmailOldMove + 1) {
6906 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6910 if (currentMove < cmailOldMove) {
6911 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6915 if (forwardMostMove > currentMove) {
6916 /* Silently truncate extra moves */
6920 if ( (currentMove == cmailOldMove + 1)
6921 || ( (currentMove == cmailOldMove)
6922 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6923 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6924 if (gameInfo.result != GameUnfinished) {
6925 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6928 if (commentList[currentMove] != NULL) {
6929 cmailCommentList[lastLoadGameNumber - 1]
6930 = StrSave(commentList[currentMove]);
6932 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6934 if (appData.debugMode)
6935 fprintf(debugFP, "Saving %s for game %d\n",
6936 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6939 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6941 f = fopen(string, "w");
6942 if (appData.oldSaveStyle) {
6943 SaveGameOldStyle(f); /* also closes the file */
6945 sprintf(string, "%s.pos.out", appData.cmailGameName);
6946 f = fopen(string, "w");
6947 SavePosition(f, 0, NULL); /* also closes the file */
6949 fprintf(f, "{--------------\n");
6950 PrintPosition(f, currentMove);
6951 fprintf(f, "--------------}\n\n");
6953 SaveGame(f, 0, NULL); /* also closes the file*/
6956 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6957 nCmailMovesRegistered ++;
6958 } else if (nCmailGames == 1) {
6959 DisplayError(_("You have not made a move yet"), 0);
6970 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6971 FILE *commandOutput;
6972 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6973 int nBytes = 0; /* Suppress warnings on uninitialized variables */
6979 if (! cmailMsgLoaded) {
6980 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
6984 if (nCmailGames == nCmailResults) {
6985 DisplayError(_("No unfinished games"), 0);
6989 #if CMAIL_PROHIBIT_REMAIL
6990 if (cmailMailedMove) {
6991 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);
6992 DisplayError(msg, 0);
6997 if (! (cmailMailedMove || RegisterMove())) return;
6999 if ( cmailMailedMove
7000 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7001 sprintf(string, partCommandString,
7002 appData.debugMode ? " -v" : "", appData.cmailGameName);
7003 commandOutput = popen(string, "r");
7005 if (commandOutput == NULL) {
7006 DisplayError(_("Failed to invoke cmail"), 0);
7008 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7009 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7012 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7013 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7014 nBytes = MSG_SIZ - 1;
7016 (void) memcpy(msg, buffer, nBytes);
7018 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7020 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7021 cmailMailedMove = TRUE; /* Prevent >1 moves */
7024 for (i = 0; i < nCmailGames; i ++) {
7025 if (cmailResult[i] == CMAIL_NOT_RESULT) {
7030 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7032 sprintf(buffer, "%s/%s.%s.archive",
7034 appData.cmailGameName,
7036 LoadGameFromFile(buffer, 1, buffer, FALSE);
7037 cmailMsgLoaded = FALSE;
7041 DisplayInformation(msg);
7042 pclose(commandOutput);
7045 if ((*cmailMsg) != '\0') {
7046 DisplayInformation(cmailMsg);
7060 int prependComma = 0;
7062 char string[MSG_SIZ]; /* Space for game-list */
7065 if (!cmailMsgLoaded) return "";
7067 if (cmailMailedMove) {
7068 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7070 /* Create a list of games left */
7071 sprintf(string, "[");
7072 for (i = 0; i < nCmailGames; i ++) {
7073 if (! ( cmailMoveRegistered[i]
7074 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7076 sprintf(number, ",%d", i + 1);
7078 sprintf(number, "%d", i + 1);
7082 strcat(string, number);
7085 strcat(string, "]");
7087 if (nCmailMovesRegistered + nCmailResults == 0) {
7088 switch (nCmailGames) {
7091 _("Still need to make move for game\n"));
7096 _("Still need to make moves for both games\n"));
7101 _("Still need to make moves for all %d games\n"),
7106 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7109 _("Still need to make a move for game %s\n"),
7114 if (nCmailResults == nCmailGames) {
7115 sprintf(cmailMsg, _("No unfinished games\n"));
7117 sprintf(cmailMsg, _("Ready to send mail\n"));
7123 _("Still need to make moves for games %s\n"),
7135 if (gameMode == Training)
7136 SetTrainingModeOff();
7139 cmailMsgLoaded = FALSE;
7140 if (appData.icsActive) {
7141 SendToICS(ics_prefix);
7142 SendToICS("refresh\n");
7146 static int exiting = 0;
7154 /* Give up on clean exit */
7158 /* Keep trying for clean exit */
7162 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7164 if (telnetISR != NULL) {
7165 RemoveInputSource(telnetISR);
7167 if (icsPR != NoProc) {
7168 DestroyChildProcess(icsPR, TRUE);
7170 /* Save game if resource set and not already saved by GameEnds() */
7171 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7172 if (*appData.saveGameFile != NULLCHAR) {
7173 SaveGameToFile(appData.saveGameFile, TRUE);
7174 } else if (appData.autoSaveGames) {
7177 if (*appData.savePositionFile != NULLCHAR) {
7178 SavePositionToFile(appData.savePositionFile);
7181 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7183 /* Kill off chess programs */
7184 if (first.pr != NoProc) {
7186 SendToProgram("quit\n", &first);
7187 DestroyChildProcess(first.pr, first.useSigterm);
7189 if (second.pr != NoProc) {
7190 SendToProgram("quit\n", &second);
7191 DestroyChildProcess(second.pr, second.useSigterm);
7193 if (first.isr != NULL) {
7194 RemoveInputSource(first.isr);
7196 if (second.isr != NULL) {
7197 RemoveInputSource(second.isr);
7207 if (appData.debugMode)
7208 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7212 if (gameMode == MachinePlaysWhite ||
7213 gameMode == MachinePlaysBlack) {
7216 DisplayBothClocks();
7218 if (gameMode == PlayFromGameFile) {
7219 if (appData.timeDelay >= 0)
7221 } else if (gameMode == IcsExamining && pauseExamInvalid) {
7223 SendToICS(ics_prefix);
7224 SendToICS("refresh\n");
7225 } else if (currentMove < forwardMostMove) {
7226 ForwardInner(forwardMostMove);
7228 pauseExamInvalid = FALSE;
7234 pauseExamForwardMostMove = forwardMostMove;
7235 pauseExamInvalid = FALSE;
7238 case IcsPlayingWhite:
7239 case IcsPlayingBlack:
7243 case PlayFromGameFile:
7244 (void) StopLoadGameTimer();
7248 case BeginningOfGame:
7249 if (appData.icsActive) return;
7250 /* else fall through */
7251 case MachinePlaysWhite:
7252 case MachinePlaysBlack:
7253 case TwoMachinesPlay:
7254 if (forwardMostMove == 0)
7255 return; /* don't pause if no one has moved */
7256 if ((gameMode == MachinePlaysWhite &&
7257 !WhiteOnMove(forwardMostMove)) ||
7258 (gameMode == MachinePlaysBlack &&
7259 WhiteOnMove(forwardMostMove))) {
7272 char title[MSG_SIZ];
7274 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7275 strcpy(title, _("Edit comment"));
7277 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7278 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7279 parseList[currentMove - 1]);
7282 EditCommentPopUp(currentMove, title, commentList[currentMove]);
7289 char *tags = PGNTags(&gameInfo);
7290 EditTagsPopUp(tags);
7297 if (appData.noChessProgram || gameMode == AnalyzeMode)
7300 if (gameMode != AnalyzeFile) {
7301 if (!appData.icsEngineAnalyze) {
7303 if (gameMode != EditGame) return;
7305 ResurrectChessProgram();
7306 SendToProgram("analyze\n", &first);
7307 first.analyzing = TRUE;
7308 /*first.maybeThinking = TRUE;*/
7309 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7310 AnalysisPopUp(_("Analysis"),
7311 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7313 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
7318 StartAnalysisClock();
7319 GetTimeMark(&lastNodeCountTime);
7326 if (appData.noChessProgram || gameMode == AnalyzeFile)
7329 if (gameMode != AnalyzeMode) {
7331 if (gameMode != EditGame) return;
7332 ResurrectChessProgram();
7333 SendToProgram("analyze\n", &first);
7334 first.analyzing = TRUE;
7335 /*first.maybeThinking = TRUE;*/
7336 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7337 AnalysisPopUp(_("Analysis"),
7338 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7340 gameMode = AnalyzeFile;
7345 StartAnalysisClock();
7346 GetTimeMark(&lastNodeCountTime);
7355 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7359 if (gameMode == PlayFromGameFile ||
7360 gameMode == TwoMachinesPlay ||
7361 gameMode == Training ||
7362 gameMode == AnalyzeMode ||
7363 gameMode == EndOfGame)
7366 if (gameMode == EditPosition)
7369 if (!WhiteOnMove(currentMove)) {
7370 DisplayError(_("It is not White's turn"), 0);
7374 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7377 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7378 gameMode == AnalyzeFile)
7381 ResurrectChessProgram(); /* in case it isn't running */
7382 gameMode = MachinePlaysWhite;
7386 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7388 if (first.sendName) {
7389 sprintf(buf, "name %s\n", gameInfo.black);
7390 SendToProgram(buf, &first);
7392 if (first.sendTime) {
7393 if (first.useColors) {
7394 SendToProgram("black\n", &first); /*gnu kludge*/
7396 SendTimeRemaining(&first, TRUE);
7398 if (first.useColors) {
7399 SendToProgram("white\ngo\n", &first);
7401 SendToProgram("go\n", &first);
7403 SetMachineThinkingEnables();
7404 first.maybeThinking = TRUE;
7407 if (appData.autoFlipView && !flipView) {
7408 flipView = !flipView;
7409 DrawPosition(FALSE, NULL);
7418 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7422 if (gameMode == PlayFromGameFile ||
7423 gameMode == TwoMachinesPlay ||
7424 gameMode == Training ||
7425 gameMode == AnalyzeMode ||
7426 gameMode == EndOfGame)
7429 if (gameMode == EditPosition)
7432 if (WhiteOnMove(currentMove)) {
7433 DisplayError(_("It is not Black's turn"), 0);
7437 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7440 if (gameMode == EditGame || gameMode == AnalyzeMode ||
7441 gameMode == AnalyzeFile)
7444 ResurrectChessProgram(); /* in case it isn't running */
7445 gameMode = MachinePlaysBlack;
7449 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7451 if (first.sendName) {
7452 sprintf(buf, "name %s\n", gameInfo.white);
7453 SendToProgram(buf, &first);
7455 if (first.sendTime) {
7456 if (first.useColors) {
7457 SendToProgram("white\n", &first); /*gnu kludge*/
7459 SendTimeRemaining(&first, FALSE);
7461 if (first.useColors) {
7462 SendToProgram("black\ngo\n", &first);
7464 SendToProgram("go\n", &first);
7466 SetMachineThinkingEnables();
7467 first.maybeThinking = TRUE;
7470 if (appData.autoFlipView && flipView) {
7471 flipView = !flipView;
7472 DrawPosition(FALSE, NULL);
7478 DisplayTwoMachinesTitle()
7481 if (appData.matchGames > 0) {
7482 if (first.twoMachinesColor[0] == 'w') {
7483 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7484 gameInfo.white, gameInfo.black,
7485 first.matchWins, second.matchWins,
7486 matchGame - 1 - (first.matchWins + second.matchWins));
7488 sprintf(buf, "%s vs. %s (%d-%d-%d)",
7489 gameInfo.white, gameInfo.black,
7490 second.matchWins, first.matchWins,
7491 matchGame - 1 - (first.matchWins + second.matchWins));
7494 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7500 TwoMachinesEvent P((void))
7504 ChessProgramState *onmove;
7506 if (appData.noChessProgram) return;
7509 case TwoMachinesPlay:
7511 case MachinePlaysWhite:
7512 case MachinePlaysBlack:
7513 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7514 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7518 case BeginningOfGame:
7519 case PlayFromGameFile:
7522 if (gameMode != EditGame) return;
7536 forwardMostMove = currentMove;
7537 ResurrectChessProgram(); /* in case first program isn't running */
7539 if (second.pr == NULL) {
7540 StartChessProgram(&second);
7541 if (second.protocolVersion == 1) {
7542 TwoMachinesEventIfReady();
7544 /* kludge: allow timeout for initial "feature" command */
7546 DisplayMessage("", _("Starting second chess program"));
7547 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7551 DisplayMessage("", "");
7552 InitChessProgram(&second);
7553 SendToProgram("force\n", &second);
7554 if (startedFromSetupPosition) {
7555 SendBoard(&second, backwardMostMove);
7557 for (i = backwardMostMove; i < forwardMostMove; i++) {
7558 SendMoveToProgram(i, &second);
7561 gameMode = TwoMachinesPlay;
7565 DisplayTwoMachinesTitle();
7567 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7573 SendToProgram(first.computerString, &first);
7574 if (first.sendName) {
7575 sprintf(buf, "name %s\n", second.tidy);
7576 SendToProgram(buf, &first);
7578 SendToProgram(second.computerString, &second);
7579 if (second.sendName) {
7580 sprintf(buf, "name %s\n", first.tidy);
7581 SendToProgram(buf, &second);
7584 if (!first.sendTime || !second.sendTime) {
7586 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7587 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7589 if (onmove->sendTime) {
7590 if (onmove->useColors) {
7591 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7593 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7595 if (onmove->useColors) {
7596 SendToProgram(onmove->twoMachinesColor, onmove);
7598 SendToProgram("go\n", onmove);
7599 onmove->maybeThinking = TRUE;
7600 SetMachineThinkingEnables();
7608 if (gameMode == Training) {
7609 SetTrainingModeOff();
7610 gameMode = PlayFromGameFile;
7611 DisplayMessage("", _("Training mode off"));
7613 gameMode = Training;
7614 animateTraining = appData.animate;
7616 /* make sure we are not already at the end of the game */
7617 if (currentMove < forwardMostMove) {
7618 SetTrainingModeOn();
7619 DisplayMessage("", _("Training mode on"));
7621 gameMode = PlayFromGameFile;
7622 DisplayError(_("Already at end of game"), 0);
7631 if (!appData.icsActive) return;
7633 case IcsPlayingWhite:
7634 case IcsPlayingBlack:
7637 case BeginningOfGame:
7671 SetTrainingModeOff();
7673 case MachinePlaysWhite:
7674 case MachinePlaysBlack:
7675 case BeginningOfGame:
7676 SendToProgram("force\n", &first);
7677 SetUserThinkingEnables();
7679 case PlayFromGameFile:
7680 (void) StopLoadGameTimer();
7681 if (gameFileFP != NULL) {
7691 SendToProgram("force\n", &first);
7693 case TwoMachinesPlay:
7694 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7695 ResurrectChessProgram();
7696 SetUserThinkingEnables();
7699 ResurrectChessProgram();
7701 case IcsPlayingBlack:
7702 case IcsPlayingWhite:
7703 DisplayError(_("Warning: You are still playing a game"), 0);
7706 DisplayError(_("Warning: You are still observing a game"), 0);
7709 DisplayError(_("Warning: You are still examining a game"), 0);
7720 first.offeredDraw = second.offeredDraw = 0;
7722 if (gameMode == PlayFromGameFile) {
7723 whiteTimeRemaining = timeRemaining[0][currentMove];
7724 blackTimeRemaining = timeRemaining[1][currentMove];
7728 if (gameMode == MachinePlaysWhite ||
7729 gameMode == MachinePlaysBlack ||
7730 gameMode == TwoMachinesPlay ||
7731 gameMode == EndOfGame) {
7732 i = forwardMostMove;
7733 while (i > currentMove) {
7734 SendToProgram("undo\n", &first);
7737 whiteTimeRemaining = timeRemaining[0][currentMove];
7738 blackTimeRemaining = timeRemaining[1][currentMove];
7739 DisplayBothClocks();
7740 if (whiteFlag || blackFlag) {
7741 whiteFlag = blackFlag = 0;
7746 gameMode = EditGame;
7755 if (gameMode == EditPosition) {
7761 if (gameMode != EditGame) return;
7763 gameMode = EditPosition;
7766 if (currentMove > 0)
7767 CopyBoard(boards[0], boards[currentMove]);
7769 blackPlaysFirst = !WhiteOnMove(currentMove);
7771 currentMove = forwardMostMove = backwardMostMove = 0;
7772 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7779 /* icsEngineAnalyze - possible call of other functions */
7780 if (appData.icsEngineAnalyze) appData.icsEngineAnalyze = FALSE;
7782 if (first.analysisSupport && first.analyzing) {
7783 SendToProgram("exit\n", &first);
7784 first.analyzing = FALSE;
7787 thinkOutput[0] = NULLCHAR;
7793 startedFromSetupPosition = TRUE;
7794 InitChessProgram(&first);
7795 SendToProgram("force\n", &first);
7796 if (blackPlaysFirst) {
7797 strcpy(moveList[0], "");
7798 strcpy(parseList[0], "");
7799 currentMove = forwardMostMove = backwardMostMove = 1;
7800 CopyBoard(boards[1], boards[0]);
7802 currentMove = forwardMostMove = backwardMostMove = 0;
7804 SendBoard(&first, forwardMostMove);
7806 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7807 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7808 gameMode = EditGame;
7810 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7813 /* Pause for `ms' milliseconds */
7814 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7824 } while (SubtractTimeMarks(&m2, &m1) < ms);
7827 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7829 SendMultiLineToICS(buf)
7832 char temp[MSG_SIZ+1], *p;
7839 strncpy(temp, buf, len);
7844 if (*p == '\n' || *p == '\r')
7851 SendToPlayer(temp, strlen(temp));
7855 SetWhiteToPlayEvent()
7857 if (gameMode == EditPosition) {
7858 blackPlaysFirst = FALSE;
7859 DisplayBothClocks(); /* works because currentMove is 0 */
7860 } else if (gameMode == IcsExamining) {
7861 SendToICS(ics_prefix);
7862 SendToICS("tomove white\n");
7867 SetBlackToPlayEvent()
7869 if (gameMode == EditPosition) {
7870 blackPlaysFirst = TRUE;
7871 currentMove = 1; /* kludge */
7872 DisplayBothClocks();
7874 } else if (gameMode == IcsExamining) {
7875 SendToICS(ics_prefix);
7876 SendToICS("tomove black\n");
7881 EditPositionMenuEvent(selection, x, y)
7882 ChessSquare selection;
7887 if (gameMode != EditPosition && gameMode != IcsExamining) return;
7889 switch (selection) {
7891 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7892 SendToICS(ics_prefix);
7893 SendToICS("bsetup clear\n");
7894 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7895 SendToICS(ics_prefix);
7896 SendToICS("clearboard\n");
7898 for (x = 0; x < BOARD_SIZE; x++) {
7899 for (y = 0; y < BOARD_SIZE; y++) {
7900 if (gameMode == IcsExamining) {
7901 if (boards[currentMove][y][x] != EmptySquare) {
7902 sprintf(buf, "%sx@%c%c\n", ics_prefix,
7907 boards[0][y][x] = EmptySquare;
7912 if (gameMode == EditPosition) {
7913 DrawPosition(FALSE, boards[0]);
7918 SetWhiteToPlayEvent();
7922 SetBlackToPlayEvent();
7926 if (gameMode == IcsExamining) {
7927 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7930 boards[0][y][x] = EmptySquare;
7931 DrawPosition(FALSE, boards[0]);
7936 if (gameMode == IcsExamining) {
7937 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7938 PieceToChar(selection), 'a' + x, '1' + y);
7941 boards[0][y][x] = selection;
7942 DrawPosition(FALSE, boards[0]);
7950 DropMenuEvent(selection, x, y)
7951 ChessSquare selection;
7957 case IcsPlayingWhite:
7958 case MachinePlaysBlack:
7959 if (!WhiteOnMove(currentMove)) {
7960 DisplayMoveError(_("It is Black's turn"));
7963 moveType = WhiteDrop;
7965 case IcsPlayingBlack:
7966 case MachinePlaysWhite:
7967 if (WhiteOnMove(currentMove)) {
7968 DisplayMoveError(_("It is White's turn"));
7971 moveType = BlackDrop;
7974 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7980 if (moveType == BlackDrop && selection < BlackPawn) {
7981 selection = (ChessSquare) ((int) selection
7982 + (int) BlackPawn - (int) WhitePawn);
7984 if (boards[currentMove][y][x] != EmptySquare) {
7985 DisplayMoveError(_("That square is occupied"));
7989 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
7995 /* Accept a pending offer of any kind from opponent */
7997 if (appData.icsActive) {
7998 SendToICS(ics_prefix);
7999 SendToICS("accept\n");
8000 } else if (cmailMsgLoaded) {
8001 if (currentMove == cmailOldMove &&
8002 commentList[cmailOldMove] != NULL &&
8003 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8004 "Black offers a draw" : "White offers a draw")) {
8006 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8007 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8009 DisplayError(_("There is no pending offer on this move"), 0);
8010 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8013 /* Not used for offers from chess program */
8020 /* Decline a pending offer of any kind from opponent */
8022 if (appData.icsActive) {
8023 SendToICS(ics_prefix);
8024 SendToICS("decline\n");
8025 } else if (cmailMsgLoaded) {
8026 if (currentMove == cmailOldMove &&
8027 commentList[cmailOldMove] != NULL &&
8028 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8029 "Black offers a draw" : "White offers a draw")) {
8031 AppendComment(cmailOldMove, "Draw declined");
8032 DisplayComment(cmailOldMove - 1, "Draw declined");
8035 DisplayError(_("There is no pending offer on this move"), 0);
8038 /* Not used for offers from chess program */
8045 /* Issue ICS rematch command */
8046 if (appData.icsActive) {
8047 SendToICS(ics_prefix);
8048 SendToICS("rematch\n");
8055 /* Call your opponent's flag (claim a win on time) */
8056 if (appData.icsActive) {
8057 SendToICS(ics_prefix);
8058 SendToICS("flag\n");
8063 case MachinePlaysWhite:
8066 GameEnds(GameIsDrawn, "Both players ran out of time",
8069 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8071 DisplayError(_("Your opponent is not out of time"), 0);
8074 case MachinePlaysBlack:
8077 GameEnds(GameIsDrawn, "Both players ran out of time",
8080 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8082 DisplayError(_("Your opponent is not out of time"), 0);
8092 /* Offer draw or accept pending draw offer from opponent */
8094 if (appData.icsActive) {
8095 /* Note: tournament rules require draw offers to be
8096 made after you make your move but before you punch
8097 your clock. Currently ICS doesn't let you do that;
8098 instead, you immediately punch your clock after making
8099 a move, but you can offer a draw at any time. */
8101 SendToICS(ics_prefix);
8102 SendToICS("draw\n");
8103 } else if (cmailMsgLoaded) {
8104 if (currentMove == cmailOldMove &&
8105 commentList[cmailOldMove] != NULL &&
8106 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8107 "Black offers a draw" : "White offers a draw")) {
8108 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8109 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8110 } else if (currentMove == cmailOldMove + 1) {
8111 char *offer = WhiteOnMove(cmailOldMove) ?
8112 "White offers a draw" : "Black offers a draw";
8113 AppendComment(currentMove, offer);
8114 DisplayComment(currentMove - 1, offer);
8115 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8117 DisplayError(_("You must make your move before offering a draw"), 0);
8118 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8120 } else if (first.offeredDraw) {
8121 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8123 if (first.sendDrawOffers) {
8124 SendToProgram("draw\n", &first);
8125 userOfferedDraw = TRUE;
8133 /* Offer Adjourn or accept pending Adjourn offer from opponent */
8135 if (appData.icsActive) {
8136 SendToICS(ics_prefix);
8137 SendToICS("adjourn\n");
8139 /* Currently GNU Chess doesn't offer or accept Adjourns */
8147 /* Offer Abort or accept pending Abort offer from opponent */
8149 if (appData.icsActive) {
8150 SendToICS(ics_prefix);
8151 SendToICS("abort\n");
8153 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8160 /* Resign. You can do this even if it's not your turn. */
8162 if (appData.icsActive) {
8163 SendToICS(ics_prefix);
8164 SendToICS("resign\n");
8167 case MachinePlaysWhite:
8168 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8170 case MachinePlaysBlack:
8171 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8174 if (cmailMsgLoaded) {
8176 if (WhiteOnMove(cmailOldMove)) {
8177 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8179 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8181 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8192 StopObservingEvent()
8194 /* Stop observing current games */
8195 SendToICS(ics_prefix);
8196 SendToICS("unobserve\n");
8200 StopExaminingEvent()
8202 /* Stop observing current game */
8203 SendToICS(ics_prefix);
8204 SendToICS("unexamine\n");
8208 ForwardInner(target)
8213 if (appData.debugMode)
8214 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8215 target, currentMove, forwardMostMove);
8217 if (gameMode == EditPosition)
8220 if (gameMode == PlayFromGameFile && !pausing)
8223 if (gameMode == IcsExamining && pausing)
8224 limit = pauseExamForwardMostMove;
8226 limit = forwardMostMove;
8228 if (target > limit) target = limit;
8230 if (target > 0 && moveList[target - 1][0]) {
8231 int fromX, fromY, toX, toY;
8232 toX = moveList[target - 1][2] - 'a';
8233 toY = moveList[target - 1][3] - '1';
8234 if (moveList[target - 1][1] == '@') {
8235 if (appData.highlightLastMove) {
8236 SetHighlights(-1, -1, toX, toY);
8239 fromX = moveList[target - 1][0] - 'a';
8240 fromY = moveList[target - 1][1] - '1';
8241 if (target == currentMove + 1) {
8242 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8244 if (appData.highlightLastMove) {
8245 SetHighlights(fromX, fromY, toX, toY);
8249 if (gameMode == EditGame || gameMode == AnalyzeMode ||
8250 gameMode == Training || gameMode == PlayFromGameFile ||
8251 gameMode == AnalyzeFile) {
8252 while (currentMove < target) {
8253 SendMoveToProgram(currentMove++, &first);
8256 currentMove = target;
8259 if (gameMode == EditGame || gameMode == EndOfGame) {
8260 whiteTimeRemaining = timeRemaining[0][currentMove];
8261 blackTimeRemaining = timeRemaining[1][currentMove];
8263 DisplayBothClocks();
8264 DisplayMove(currentMove - 1);
8265 DrawPosition(FALSE, boards[currentMove]);
8266 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8267 if (commentList[currentMove] && !matchMode && gameMode != Training) {
8268 DisplayComment(currentMove - 1, commentList[currentMove]);
8276 if (gameMode == IcsExamining && !pausing) {
8277 SendToICS(ics_prefix);
8278 SendToICS("forward\n");
8280 ForwardInner(currentMove + 1);
8287 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8288 /* to optimze, we temporarily turn off analysis mode while we feed
8289 * the remaining moves to the engine. Otherwise we get analysis output
8292 if (first.analysisSupport) {
8293 SendToProgram("exit\nforce\n", &first);
8294 first.analyzing = FALSE;
8298 if (gameMode == IcsExamining && !pausing) {
8299 SendToICS(ics_prefix);
8300 SendToICS("forward 999999\n");
8302 ForwardInner(forwardMostMove);
8305 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8306 /* we have fed all the moves, so reactivate analysis mode */
8307 SendToProgram("analyze\n", &first);
8308 first.analyzing = TRUE;
8309 /*first.maybeThinking = TRUE;*/
8310 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8315 BackwardInner(target)
8318 if (appData.debugMode)
8319 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8320 target, currentMove, forwardMostMove);
8322 if (gameMode == EditPosition) return;
8323 if (currentMove <= backwardMostMove) {
8325 DrawPosition(FALSE, boards[currentMove]);
8328 if (gameMode == PlayFromGameFile && !pausing)
8331 if (moveList[target][0]) {
8332 int fromX, fromY, toX, toY;
8333 toX = moveList[target][2] - 'a';
8334 toY = moveList[target][3] - '1';
8335 if (moveList[target][1] == '@') {
8336 if (appData.highlightLastMove) {
8337 SetHighlights(-1, -1, toX, toY);
8340 fromX = moveList[target][0] - 'a';
8341 fromY = moveList[target][1] - '1';
8342 if (target == currentMove - 1) {
8343 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8345 if (appData.highlightLastMove) {
8346 SetHighlights(fromX, fromY, toX, toY);
8350 if (gameMode == EditGame || gameMode==AnalyzeMode ||
8351 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8352 while (currentMove > target) {
8353 SendToProgram("undo\n", &first);
8357 currentMove = target;
8360 if (gameMode == EditGame || gameMode == EndOfGame) {
8361 whiteTimeRemaining = timeRemaining[0][currentMove];
8362 blackTimeRemaining = timeRemaining[1][currentMove];
8364 DisplayBothClocks();
8365 DisplayMove(currentMove - 1);
8366 DrawPosition(FALSE, boards[currentMove]);
8367 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8368 if (commentList[currentMove] != NULL) {
8369 DisplayComment(currentMove - 1, commentList[currentMove]);
8376 if (gameMode == IcsExamining && !pausing) {
8377 SendToICS(ics_prefix);
8378 SendToICS("backward\n");
8380 BackwardInner(currentMove - 1);
8387 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8388 /* to optimze, we temporarily turn off analysis mode while we undo
8389 * all the moves. Otherwise we get analysis output after each undo.
8391 if (first.analysisSupport) {
8392 SendToProgram("exit\nforce\n", &first);
8393 first.analyzing = FALSE;
8397 if (gameMode == IcsExamining && !pausing) {
8398 SendToICS(ics_prefix);
8399 SendToICS("backward 999999\n");
8401 BackwardInner(backwardMostMove);
8404 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8405 /* we have fed all the moves, so reactivate analysis mode */
8406 SendToProgram("analyze\n", &first);
8407 first.analyzing = TRUE;
8408 /*first.maybeThinking = TRUE;*/
8409 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8416 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8417 if (to >= forwardMostMove) to = forwardMostMove;
8418 if (to <= backwardMostMove) to = backwardMostMove;
8419 if (to < currentMove) {
8429 if (gameMode != IcsExamining) {
8430 DisplayError(_("You are not examining a game"), 0);
8434 DisplayError(_("You can't revert while pausing"), 0);
8437 SendToICS(ics_prefix);
8438 SendToICS("revert\n");
8445 case MachinePlaysWhite:
8446 case MachinePlaysBlack:
8447 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8448 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8451 if (forwardMostMove < 2) return;
8452 currentMove = forwardMostMove = forwardMostMove - 2;
8453 whiteTimeRemaining = timeRemaining[0][currentMove];
8454 blackTimeRemaining = timeRemaining[1][currentMove];
8455 DisplayBothClocks();
8456 DisplayMove(currentMove - 1);
8457 ClearHighlights();/*!! could figure this out*/
8458 DrawPosition(FALSE, boards[currentMove]);
8459 SendToProgram("remove\n", &first);
8460 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8463 case BeginningOfGame:
8467 case IcsPlayingWhite:
8468 case IcsPlayingBlack:
8469 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8470 SendToICS(ics_prefix);
8471 SendToICS("takeback 2\n");
8473 SendToICS(ics_prefix);
8474 SendToICS("takeback 1\n");
8483 ChessProgramState *cps;
8486 case MachinePlaysWhite:
8487 if (!WhiteOnMove(forwardMostMove)) {
8488 DisplayError(_("It is your turn"), 0);
8493 case MachinePlaysBlack:
8494 if (WhiteOnMove(forwardMostMove)) {
8495 DisplayError(_("It is your turn"), 0);
8500 case TwoMachinesPlay:
8501 if (WhiteOnMove(forwardMostMove) ==
8502 (first.twoMachinesColor[0] == 'w')) {
8508 case BeginningOfGame:
8512 SendToProgram("?\n", cps);
8519 if (gameMode != EditGame) return;
8526 if (forwardMostMove > currentMove) {
8527 if (gameInfo.resultDetails != NULL) {
8528 free(gameInfo.resultDetails);
8529 gameInfo.resultDetails = NULL;
8530 gameInfo.result = GameUnfinished;
8532 forwardMostMove = currentMove;
8533 HistorySet(parseList, backwardMostMove, forwardMostMove,
8541 if (appData.noChessProgram) return;
8543 case MachinePlaysWhite:
8544 if (WhiteOnMove(forwardMostMove)) {
8545 DisplayError(_("Wait until your turn"), 0);
8549 case BeginningOfGame:
8550 case MachinePlaysBlack:
8551 if (!WhiteOnMove(forwardMostMove)) {
8552 DisplayError(_("Wait until your turn"), 0);
8557 DisplayError(_("No hint available"), 0);
8560 SendToProgram("hint\n", &first);
8561 hintRequested = TRUE;
8567 if (appData.noChessProgram) return;
8569 case MachinePlaysWhite:
8570 if (WhiteOnMove(forwardMostMove)) {
8571 DisplayError(_("Wait until your turn"), 0);
8575 case BeginningOfGame:
8576 case MachinePlaysBlack:
8577 if (!WhiteOnMove(forwardMostMove)) {
8578 DisplayError(_("Wait until your turn"), 0);
8585 case TwoMachinesPlay:
8590 SendToProgram("bk\n", &first);
8591 bookOutput[0] = NULLCHAR;
8592 bookRequested = TRUE;
8598 char *tags = PGNTags(&gameInfo);
8599 TagsPopUp(tags, CmailMsg());
8603 /* end button procedures */
8606 PrintPosition(fp, move)
8612 for (i = BOARD_SIZE - 1; i >= 0; i--) {
8613 for (j = 0; j < BOARD_SIZE; j++) {
8614 char c = PieceToChar(boards[move][i][j]);
8615 fputc(c == 'x' ? '.' : c, fp);
8616 fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8619 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8620 fprintf(fp, "white to play\n");
8622 fprintf(fp, "black to play\n");
8629 if (gameInfo.white != NULL) {
8630 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8636 /* Find last component of program's own name, using some heuristics */
8638 TidyProgramName(prog, host, buf)
8639 char *prog, *host, buf[MSG_SIZ];
8642 int local = (strcmp(host, "localhost") == 0);
8643 while (!local && (p = strchr(prog, ';')) != NULL) {
8645 while (*p == ' ') p++;
8648 if (*prog == '"' || *prog == '\'') {
8649 q = strchr(prog + 1, *prog);
8651 q = strchr(prog, ' ');
8653 if (q == NULL) q = prog + strlen(prog);
8655 while (p >= prog && *p != '/' && *p != '\\') p--;
8657 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8658 memcpy(buf, p, q - p);
8659 buf[q - p] = NULLCHAR;
8667 TimeControlTagValue()
8670 if (!appData.clockMode) {
8672 } else if (movesPerSession > 0) {
8673 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8674 } else if (timeIncrement == 0) {
8675 sprintf(buf, "%ld", timeControl/1000);
8677 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8679 return StrSave(buf);
8685 /* This routine is used only for certain modes */
8686 VariantClass v = gameInfo.variant;
8687 ClearGameInfo(&gameInfo);
8688 gameInfo.variant = v;
8691 case MachinePlaysWhite:
8692 gameInfo.event = StrSave("Computer chess game");
8693 gameInfo.site = StrSave(HostName());
8694 gameInfo.date = PGNDate();
8695 gameInfo.round = StrSave("-");
8696 gameInfo.white = StrSave(first.tidy);
8697 gameInfo.black = StrSave(UserName());
8698 gameInfo.timeControl = TimeControlTagValue();
8701 case MachinePlaysBlack:
8702 gameInfo.event = StrSave("Computer chess game");
8703 gameInfo.site = StrSave(HostName());
8704 gameInfo.date = PGNDate();
8705 gameInfo.round = StrSave("-");
8706 gameInfo.white = StrSave(UserName());
8707 gameInfo.black = StrSave(first.tidy);
8708 gameInfo.timeControl = TimeControlTagValue();
8711 case TwoMachinesPlay:
8712 gameInfo.event = StrSave("Computer chess game");
8713 gameInfo.site = StrSave(HostName());
8714 gameInfo.date = PGNDate();
8715 if (matchGame > 0) {
8717 sprintf(buf, "%d", matchGame);
8718 gameInfo.round = StrSave(buf);
8720 gameInfo.round = StrSave("-");
8722 if (first.twoMachinesColor[0] == 'w') {
8723 gameInfo.white = StrSave(first.tidy);
8724 gameInfo.black = StrSave(second.tidy);
8726 gameInfo.white = StrSave(second.tidy);
8727 gameInfo.black = StrSave(first.tidy);
8729 gameInfo.timeControl = TimeControlTagValue();
8733 gameInfo.event = StrSave("Edited game");
8734 gameInfo.site = StrSave(HostName());
8735 gameInfo.date = PGNDate();
8736 gameInfo.round = StrSave("-");
8737 gameInfo.white = StrSave("-");
8738 gameInfo.black = StrSave("-");
8742 gameInfo.event = StrSave("Edited position");
8743 gameInfo.site = StrSave(HostName());
8744 gameInfo.date = PGNDate();
8745 gameInfo.round = StrSave("-");
8746 gameInfo.white = StrSave("-");
8747 gameInfo.black = StrSave("-");
8750 case IcsPlayingWhite:
8751 case IcsPlayingBlack:
8756 case PlayFromGameFile:
8757 gameInfo.event = StrSave("Game from non-PGN file");
8758 gameInfo.site = StrSave(HostName());
8759 gameInfo.date = PGNDate();
8760 gameInfo.round = StrSave("-");
8761 gameInfo.white = StrSave("?");
8762 gameInfo.black = StrSave("?");
8771 ReplaceComment(index, text)
8777 while (*text == '\n') text++;
8779 while (len > 0 && text[len - 1] == '\n') len--;
8781 if (commentList[index] != NULL)
8782 free(commentList[index]);
8785 commentList[index] = NULL;
8788 commentList[index] = (char *) malloc(len + 2);
8789 strncpy(commentList[index], text, len);
8790 commentList[index][len] = '\n';
8791 commentList[index][len + 1] = NULLCHAR;
8804 if (ch == '\r') continue;
8806 } while (ch != '\0');
8810 AppendComment(index, text)
8818 while (*text == '\n') text++;
8820 while (len > 0 && text[len - 1] == '\n') len--;
8822 if (len == 0) return;
8824 if (commentList[index] != NULL) {
8825 old = commentList[index];
8826 oldlen = strlen(old);
8827 commentList[index] = (char *) malloc(oldlen + len + 2);
8828 strcpy(commentList[index], old);
8830 strncpy(&commentList[index][oldlen], text, len);
8831 commentList[index][oldlen + len] = '\n';
8832 commentList[index][oldlen + len + 1] = NULLCHAR;
8834 commentList[index] = (char *) malloc(len + 2);
8835 strncpy(commentList[index], text, len);
8836 commentList[index][len] = '\n';
8837 commentList[index][len + 1] = NULLCHAR;
8842 SendToProgram(message, cps)
8844 ChessProgramState *cps;
8846 int count, outCount, error;
8849 if (cps->pr == NULL) return;
8852 if (appData.debugMode) {
8855 fprintf(debugFP, "%ld >%-6s: %s",
8856 SubtractTimeMarks(&now, &programStartTime),
8857 cps->which, message);
8860 count = strlen(message);
8861 outCount = OutputToProcess(cps->pr, message, count, &error);
8862 if (outCount < count && !exiting) {
8863 sprintf(buf, _("Error writing to %s chess program"), cps->which);
8864 DisplayFatalError(buf, error, 1);
8869 ReceiveFromProgram(isr, closure, message, count, error)
8878 ChessProgramState *cps = (ChessProgramState *)closure;
8880 if (isr != cps->isr) return; /* Killed intentionally */
8884 _("Error: %s chess program (%s) exited unexpectedly"),
8885 cps->which, cps->program);
8886 RemoveInputSource(cps->isr);
8887 DisplayFatalError(buf, 0, 1);
8890 _("Error reading from %s chess program (%s)"),
8891 cps->which, cps->program);
8892 RemoveInputSource(cps->isr);
8893 DisplayFatalError(buf, error, 1);
8895 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8899 if ((end_str = strchr(message, '\r')) != NULL)
8900 *end_str = NULLCHAR;
8901 if ((end_str = strchr(message, '\n')) != NULL)
8902 *end_str = NULLCHAR;
8904 if (appData.debugMode) {
8907 fprintf(debugFP, "%ld <%-6s: %s\n",
8908 SubtractTimeMarks(&now, &programStartTime),
8909 cps->which, message);
8911 HandleMachineMove(message, cps);
8916 SendTimeControl(cps, mps, tc, inc, sd, st)
8917 ChessProgramState *cps;
8918 int mps, inc, sd, st;
8922 int seconds = (tc / 1000) % 60;
8925 /* Set exact time per move, normally using st command */
8926 if (cps->stKludge) {
8927 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8930 sprintf(buf, "level 1 %d\n", st/60);
8932 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8935 sprintf(buf, "st %d\n", st);
8938 /* Set conventional or incremental time control, using level command */
8940 /* Note old gnuchess bug -- minutes:seconds used to not work.
8941 Fixed in later versions, but still avoid :seconds
8942 when seconds is 0. */
8943 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8945 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8949 SendToProgram(buf, cps);
8951 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8952 /* Orthogonally, limit search to given depth */
8954 if (cps->sdKludge) {
8955 sprintf(buf, "depth\n%d\n", sd);
8957 sprintf(buf, "sd %d\n", sd);
8959 SendToProgram(buf, cps);
8964 SendTimeRemaining(cps, machineWhite)
8965 ChessProgramState *cps;
8966 int /*boolean*/ machineWhite;
8968 char message[MSG_SIZ];
8971 /* Note: this routine must be called when the clocks are stopped
8972 or when they have *just* been set or switched; otherwise
8973 it will be off by the time since the current tick started.
8976 time = whiteTimeRemaining / 10;
8977 otime = blackTimeRemaining / 10;
8979 time = blackTimeRemaining / 10;
8980 otime = whiteTimeRemaining / 10;
8982 if (time <= 0) time = 1;
8983 if (otime <= 0) otime = 1;
8985 sprintf(message, "time %ld\notim %ld\n", time, otime);
8986 SendToProgram(message, cps);
8990 BoolFeature(p, name, loc, cps)
8994 ChessProgramState *cps;
8997 int len = strlen(name);
8999 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9001 sscanf(*p, "%d", &val);
9003 while (**p && **p != ' ') (*p)++;
9004 sprintf(buf, "accepted %s\n", name);
9005 SendToProgram(buf, cps);
9012 IntFeature(p, name, loc, cps)
9016 ChessProgramState *cps;
9019 int len = strlen(name);
9020 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9022 sscanf(*p, "%d", loc);
9023 while (**p && **p != ' ') (*p)++;
9024 sprintf(buf, "accepted %s\n", name);
9025 SendToProgram(buf, cps);
9032 StringFeature(p, name, loc, cps)
9036 ChessProgramState *cps;
9039 int len = strlen(name);
9040 if (strncmp((*p), name, len) == 0
9041 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9043 sscanf(*p, "%[^\"]", loc);
9044 while (**p && **p != '\"') (*p)++;
9045 if (**p == '\"') (*p)++;
9046 sprintf(buf, "accepted %s\n", name);
9047 SendToProgram(buf, cps);
9054 FeatureDone(cps, val)
9055 ChessProgramState* cps;
9058 DelayedEventCallback cb = GetDelayedEvent();
9059 if ((cb == InitBackEnd3 && cps == &first) ||
9060 (cb == TwoMachinesEventIfReady && cps == &second)) {
9061 CancelDelayedEvent();
9062 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9064 cps->initDone = val;
9067 /* Parse feature command from engine */
9069 ParseFeatures(args, cps)
9071 ChessProgramState *cps;
9079 while (*p == ' ') p++;
9080 if (*p == NULLCHAR) return;
9082 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9083 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9084 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9085 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9086 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9087 if (BoolFeature(&p, "reuse", &val, cps)) {
9088 /* Engine can disable reuse, but can't enable it if user said no */
9089 if (!val) cps->reuse = FALSE;
9092 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9093 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9094 if (gameMode == TwoMachinesPlay) {
9095 DisplayTwoMachinesTitle();
9101 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9102 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9103 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9104 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9105 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9106 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9107 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9108 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9109 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9110 if (IntFeature(&p, "done", &val, cps)) {
9111 FeatureDone(cps, val);
9115 /* unknown feature: complain and skip */
9117 while (*q && *q != '=') q++;
9118 sprintf(buf, "rejected %.*s\n", q-p, p);
9119 SendToProgram(buf, cps);
9125 while (*p && *p != '\"') p++;
9126 if (*p == '\"') p++;
9128 while (*p && *p != ' ') p++;
9136 PeriodicUpdatesEvent(newState)
9139 if (newState == appData.periodicUpdates)
9142 appData.periodicUpdates=newState;
9144 /* Display type changes, so update it now */
9147 /* Get the ball rolling again... */
9149 AnalysisPeriodicEvent(1);
9150 StartAnalysisClock();
9155 PonderNextMoveEvent(newState)
9158 if (newState == appData.ponderNextMove) return;
9159 if (gameMode == EditPosition) EditPositionDone();
9161 SendToProgram("hard\n", &first);
9162 if (gameMode == TwoMachinesPlay) {
9163 SendToProgram("hard\n", &second);
9166 SendToProgram("easy\n", &first);
9167 thinkOutput[0] = NULLCHAR;
9168 if (gameMode == TwoMachinesPlay) {
9169 SendToProgram("easy\n", &second);
9172 appData.ponderNextMove = newState;
9176 ShowThinkingEvent(newState)
9179 if (newState == appData.showThinking) return;
9180 if (gameMode == EditPosition) EditPositionDone();
9182 SendToProgram("post\n", &first);
9183 if (gameMode == TwoMachinesPlay) {
9184 SendToProgram("post\n", &second);
9187 SendToProgram("nopost\n", &first);
9188 thinkOutput[0] = NULLCHAR;
9189 if (gameMode == TwoMachinesPlay) {
9190 SendToProgram("nopost\n", &second);
9193 appData.showThinking = newState;
9197 AskQuestionEvent(title, question, replyPrefix, which)
9198 char *title; char *question; char *replyPrefix; char *which;
9200 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9201 if (pr == NoProc) return;
9202 AskQuestion(title, question, replyPrefix, pr);
9206 DisplayMove(moveNumber)
9209 char message[MSG_SIZ];
9211 char cpThinkOutput[MSG_SIZ];
9213 if (moveNumber == forwardMostMove - 1 ||
9214 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9216 strcpy(cpThinkOutput, thinkOutput);
9217 if (strchr(cpThinkOutput, '\n'))
9218 *strchr(cpThinkOutput, '\n') = NULLCHAR;
9220 *cpThinkOutput = NULLCHAR;
9223 if (moveNumber == forwardMostMove - 1 &&
9224 gameInfo.resultDetails != NULL) {
9225 if (gameInfo.resultDetails[0] == NULLCHAR) {
9226 sprintf(res, " %s", PGNResult(gameInfo.result));
9228 sprintf(res, " {%s} %s",
9229 gameInfo.resultDetails, PGNResult(gameInfo.result));
9235 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9236 DisplayMessage(res, cpThinkOutput);
9238 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9239 WhiteOnMove(moveNumber) ? " " : ".. ",
9240 parseList[moveNumber], res);
9241 DisplayMessage(message, cpThinkOutput);
9246 DisplayAnalysisText(text)
9251 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
9252 || appData.icsEngineAnalyze) {
9253 sprintf(buf, "Analysis (%s)", first.tidy);
9254 AnalysisPopUp(buf, text);
9262 while (*str && isspace(*str)) ++str;
9263 while (*str && !isspace(*str)) ++str;
9264 if (!*str) return 1;
9265 while (*str && isspace(*str)) ++str;
9266 if (!*str) return 1;
9275 static char *xtra[] = { "", " (--)", " (++)" };
9278 if (programStats.time == 0) {
9279 programStats.time = 1;
9282 if (programStats.got_only_move) {
9283 strcpy(buf, programStats.movelist);
9285 nps = (u64ToDouble(programStats.nodes) /
9286 ((double)programStats.time /100.0));
9288 cs = programStats.time % 100;
9289 s = programStats.time / 100;
9295 if (programStats.moves_left > 0 && appData.periodicUpdates) {
9296 if (programStats.move_name[0] != NULLCHAR) {
9297 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9299 programStats.nr_moves-programStats.moves_left,
9300 programStats.nr_moves, programStats.move_name,
9301 ((float)programStats.score)/100.0, programStats.movelist,
9302 only_one_move(programStats.movelist)?
9303 xtra[programStats.got_fail] : "",
9304 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9306 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9308 programStats.nr_moves-programStats.moves_left,
9309 programStats.nr_moves, ((float)programStats.score)/100.0,
9310 programStats.movelist,
9311 only_one_move(programStats.movelist)?
9312 xtra[programStats.got_fail] : "",
9313 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9316 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9318 ((float)programStats.score)/100.0,
9319 programStats.movelist,
9320 only_one_move(programStats.movelist)?
9321 xtra[programStats.got_fail] : "",
9322 (u64)programStats.nodes, (int)nps, h, m, s, cs);
9325 DisplayAnalysisText(buf);
9329 DisplayComment(moveNumber, text)
9333 char title[MSG_SIZ];
9335 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9336 strcpy(title, "Comment");
9338 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9339 WhiteOnMove(moveNumber) ? " " : ".. ",
9340 parseList[moveNumber]);
9343 CommentPopUp(title, text);
9346 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9347 * might be busy thinking or pondering. It can be omitted if your
9348 * gnuchess is configured to stop thinking immediately on any user
9349 * input. However, that gnuchess feature depends on the FIONREAD
9350 * ioctl, which does not work properly on some flavors of Unix.
9354 ChessProgramState *cps;
9357 if (!cps->useSigint) return;
9358 if (appData.noChessProgram || (cps->pr == NoProc)) return;
9360 case MachinePlaysWhite:
9361 case MachinePlaysBlack:
9362 case TwoMachinesPlay:
9363 case IcsPlayingWhite:
9364 case IcsPlayingBlack:
9367 /* Skip if we know it isn't thinking */
9368 if (!cps->maybeThinking) return;
9369 if (appData.debugMode)
9370 fprintf(debugFP, "Interrupting %s\n", cps->which);
9371 InterruptChildProcess(cps->pr);
9372 cps->maybeThinking = FALSE;
9377 #endif /*ATTENTION*/
9383 if (whiteTimeRemaining <= 0) {
9386 if (appData.icsActive) {
9387 if (appData.autoCallFlag &&
9388 gameMode == IcsPlayingBlack && !blackFlag) {
9389 SendToICS(ics_prefix);
9390 SendToICS("flag\n");
9394 DisplayTitle(_("Both flags fell"));
9396 DisplayTitle(_("White's flag fell"));
9397 if (appData.autoCallFlag) {
9398 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9405 if (blackTimeRemaining <= 0) {
9408 if (appData.icsActive) {
9409 if (appData.autoCallFlag &&
9410 gameMode == IcsPlayingWhite && !whiteFlag) {
9411 SendToICS(ics_prefix);
9412 SendToICS("flag\n");
9416 DisplayTitle(_("Both flags fell"));
9418 DisplayTitle(_("Black's flag fell"));
9419 if (appData.autoCallFlag) {
9420 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9433 if (!appData.clockMode || appData.icsActive ||
9434 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9436 if (timeIncrement >= 0) {
9437 if (WhiteOnMove(forwardMostMove)) {
9438 blackTimeRemaining += timeIncrement;
9440 whiteTimeRemaining += timeIncrement;
9444 * add time to clocks when time control is achieved
9446 if (movesPerSession) {
9447 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9449 /* White made time control */
9450 whiteTimeRemaining += timeControl;
9453 /* Black made time control */
9454 blackTimeRemaining += timeControl;
9465 int wom = gameMode == EditPosition ?
9466 !blackPlaysFirst : WhiteOnMove(currentMove);
9467 DisplayWhiteClock(whiteTimeRemaining, wom);
9468 DisplayBlackClock(blackTimeRemaining, !wom);
9472 /* Timekeeping seems to be a portability nightmare. I think everyone
9473 has ftime(), but I'm really not sure, so I'm including some ifdefs
9474 to use other calls if you don't. Clocks will be less accurate if
9475 you have neither ftime nor gettimeofday.
9478 /* Get the current time as a TimeMark */
9483 #if HAVE_GETTIMEOFDAY
9485 struct timeval timeVal;
9486 struct timezone timeZone;
9488 gettimeofday(&timeVal, &timeZone);
9489 tm->sec = (long) timeVal.tv_sec;
9490 tm->ms = (int) (timeVal.tv_usec / 1000L);
9492 #else /*!HAVE_GETTIMEOFDAY*/
9495 #include <sys/timeb.h>
9499 tm->sec = (long) timeB.time;
9500 tm->ms = (int) timeB.millitm;
9502 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9503 tm->sec = (long) time(NULL);
9509 /* Return the difference in milliseconds between two
9510 time marks. We assume the difference will fit in a long!
9513 SubtractTimeMarks(tm2, tm1)
9514 TimeMark *tm2, *tm1;
9516 return 1000L*(tm2->sec - tm1->sec) +
9517 (long) (tm2->ms - tm1->ms);
9522 * Code to manage the game clocks.
9524 * In tournament play, black starts the clock and then white makes a move.
9525 * We give the human user a slight advantage if he is playing white---the
9526 * clocks don't run until he makes his first move, so it takes zero time.
9527 * Also, we don't account for network lag, so we could get out of sync
9528 * with GNU Chess's clock -- but then, referees are always right.
9531 static TimeMark tickStartTM;
9532 static long intendedTickLength;
9535 NextTickLength(timeRemaining)
9538 long nominalTickLength, nextTickLength;
9540 if (timeRemaining > 0L && timeRemaining <= 10000L)
9541 nominalTickLength = 100L;
9543 nominalTickLength = 1000L;
9544 nextTickLength = timeRemaining % nominalTickLength;
9545 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9547 return nextTickLength;
9550 /* Stop clocks and reset to a fresh time control */
9554 (void) StopClockTimer();
9555 if (appData.icsActive) {
9556 whiteTimeRemaining = blackTimeRemaining = 0;
9558 whiteTimeRemaining = blackTimeRemaining = timeControl;
9560 if (whiteFlag || blackFlag) {
9562 whiteFlag = blackFlag = FALSE;
9564 DisplayBothClocks();
9567 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9569 /* Decrement running clock by amount of time that has passed */
9574 long lastTickLength, fudge;
9577 if (!appData.clockMode) return;
9578 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9582 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9584 /* Fudge if we woke up a little too soon */
9585 fudge = intendedTickLength - lastTickLength;
9586 if (fudge < 0 || fudge > FUDGE) fudge = 0;
9588 if (WhiteOnMove(forwardMostMove)) {
9589 timeRemaining = whiteTimeRemaining -= lastTickLength;
9590 DisplayWhiteClock(whiteTimeRemaining - fudge,
9591 WhiteOnMove(currentMove));
9593 timeRemaining = blackTimeRemaining -= lastTickLength;
9594 DisplayBlackClock(blackTimeRemaining - fudge,
9595 !WhiteOnMove(currentMove));
9598 if (CheckFlags()) return;
9601 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9602 StartClockTimer(intendedTickLength);
9604 /* if the time remaining has fallen below the alarm threshold, sound the
9605 * alarm. if the alarm has sounded and (due to a takeback or time control
9606 * with increment) the time remaining has increased to a level above the
9607 * threshold, reset the alarm so it can sound again.
9610 if (appData.icsActive && appData.icsAlarm) {
9612 /* make sure we are dealing with the user's clock */
9613 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9614 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9617 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9618 alarmSounded = FALSE;
9619 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9621 alarmSounded = TRUE;
9627 /* A player has just moved, so stop the previously running
9628 clock and (if in clock mode) start the other one.
9629 We redisplay both clocks in case we're in ICS mode, because
9630 ICS gives us an update to both clocks after every move.
9631 Note that this routine is called *after* forwardMostMove
9632 is updated, so the last fractional tick must be subtracted
9633 from the color that is *not* on move now.
9638 long lastTickLength;
9640 int flagged = FALSE;
9644 if (StopClockTimer() && appData.clockMode) {
9645 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9646 if (WhiteOnMove(forwardMostMove)) {
9647 blackTimeRemaining -= lastTickLength;
9649 whiteTimeRemaining -= lastTickLength;
9651 flagged = CheckFlags();
9655 if (flagged || !appData.clockMode) return;
9658 case MachinePlaysBlack:
9659 case MachinePlaysWhite:
9660 case BeginningOfGame:
9661 if (pausing) return;
9665 case PlayFromGameFile:
9674 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9675 whiteTimeRemaining : blackTimeRemaining);
9676 StartClockTimer(intendedTickLength);
9680 /* Stop both clocks */
9684 long lastTickLength;
9687 if (!StopClockTimer()) return;
9688 if (!appData.clockMode) return;
9692 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9693 if (WhiteOnMove(forwardMostMove)) {
9694 whiteTimeRemaining -= lastTickLength;
9695 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9697 blackTimeRemaining -= lastTickLength;
9698 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9703 /* Start clock of player on move. Time may have been reset, so
9704 if clock is already running, stop and restart it. */
9708 (void) StopClockTimer(); /* in case it was running already */
9709 DisplayBothClocks();
9710 if (CheckFlags()) return;
9712 if (!appData.clockMode) return;
9713 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9715 GetTimeMark(&tickStartTM);
9716 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9717 whiteTimeRemaining : blackTimeRemaining);
9718 StartClockTimer(intendedTickLength);
9725 long second, minute, hour, day;
9727 static char buf[32];
9729 if (ms > 0 && ms <= 9900) {
9730 /* convert milliseconds to tenths, rounding up */
9731 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9733 sprintf(buf, " %03.1f ", tenths/10.0);
9737 /* convert milliseconds to seconds, rounding up */
9738 /* use floating point to avoid strangeness of integer division
9739 with negative dividends on many machines */
9740 second = (long) floor(((double) (ms + 999L)) / 1000.0);
9747 day = second / (60 * 60 * 24);
9748 second = second % (60 * 60 * 24);
9749 hour = second / (60 * 60);
9750 second = second % (60 * 60);
9751 minute = second / 60;
9752 second = second % 60;
9755 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9756 sign, day, hour, minute, second);
9758 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9760 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9767 * This is necessary because some C libraries aren't ANSI C compliant yet.
9770 StrStr(string, match)
9771 char *string, *match;
9775 length = strlen(match);
9777 for (i = strlen(string) - length; i >= 0; i--, string++)
9778 if (!strncmp(match, string, length))
9785 StrCaseStr(string, match)
9786 char *string, *match;
9790 length = strlen(match);
9792 for (i = strlen(string) - length; i >= 0; i--, string++) {
9793 for (j = 0; j < length; j++) {
9794 if (ToLower(match[j]) != ToLower(string[j]))
9797 if (j == length) return string;
9811 c1 = ToLower(*s1++);
9812 c2 = ToLower(*s2++);
9813 if (c1 > c2) return 1;
9814 if (c1 < c2) return -1;
9815 if (c1 == NULLCHAR) return 0;
9824 return isupper(c) ? tolower(c) : c;
9832 return islower(c) ? toupper(c) : c;
9834 #endif /* !_amigados */
9842 if ((ret = (char *) malloc(strlen(s) + 1))) {
9849 StrSavePtr(s, savePtr)
9855 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9856 strcpy(*savePtr, s);
9868 clock = time((time_t *)NULL);
9869 tm = localtime(&clock);
9870 sprintf(buf, "%04d.%02d.%02d",
9871 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9872 return StrSave(buf);
9880 int i, j, fromX, fromY, toX, toY;
9886 whiteToPlay = (gameMode == EditPosition) ?
9887 !blackPlaysFirst : (move % 2 == 0);
9890 /* Piece placement data */
9891 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9893 for (j = 0; j < BOARD_SIZE; j++) {
9894 if (boards[move][i][j] == EmptySquare) {
9897 if (emptycount > 0) {
9898 *p++ = '0' + emptycount;
9901 *p++ = PieceToChar(boards[move][i][j]);
9904 if (emptycount > 0) {
9905 *p++ = '0' + emptycount;
9913 *p++ = whiteToPlay ? 'w' : 'b';
9916 /* !!We don't keep track of castling availability, so fake it */
9918 if (boards[move][0][4] == WhiteKing) {
9919 if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9920 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9922 if (boards[move][7][4] == BlackKing) {
9923 if (boards[move][7][7] == BlackRook) *p++ = 'k';
9924 if (boards[move][7][0] == BlackRook) *p++ = 'q';
9926 if (q == p) *p++ = '-';
9929 /* En passant target square */
9930 if (move > backwardMostMove) {
9931 fromX = moveList[move - 1][0] - 'a';
9932 fromY = moveList[move - 1][1] - '1';
9933 toX = moveList[move - 1][2] - 'a';
9934 toY = moveList[move - 1][3] - '1';
9935 if (fromY == (whiteToPlay ? 6 : 1) &&
9936 toY == (whiteToPlay ? 4 : 3) &&
9937 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9939 /* 2-square pawn move just happened */
9941 *p++ = whiteToPlay ? '6' : '3';
9949 /* !!We don't keep track of halfmove clock for 50-move rule */
9953 /* Fullmove number */
9954 sprintf(p, "%d", (move / 2) + 1);
9956 return StrSave(buf);
9960 ParseFEN(board, blackPlaysFirst, fen)
9962 int *blackPlaysFirst;
9971 /* Piece placement data */
9972 for (i = BOARD_SIZE - 1; i >= 0; i--) {
9975 if (*p == '/' || *p == ' ') {
9977 emptycount = BOARD_SIZE - j;
9978 while (emptycount--) board[i][j++] = EmptySquare;
9980 } else if (isdigit(*p)) {
9981 emptycount = *p++ - '0';
9982 if (j + emptycount > BOARD_SIZE) return FALSE;
9983 while (emptycount--) board[i][j++] = EmptySquare;
9984 } else if (isalpha(*p)) {
9985 if (j >= BOARD_SIZE) return FALSE;
9986 board[i][j++] = CharToPiece(*p++);
9992 while (*p == '/' || *p == ' ') p++;
9997 *blackPlaysFirst = FALSE;
10000 *blackPlaysFirst = TRUE;
10005 /* !!We ignore the rest of the FEN notation */
10010 EditPositionPasteFEN(char *fen)
10013 Board initial_position;
10015 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10016 DisplayError(_("Bad FEN position in clipboard"), 0);
10019 int savedBlackPlaysFirst = blackPlaysFirst;
10020 EditPositionEvent();
10021 blackPlaysFirst = savedBlackPlaysFirst;
10022 CopyBoard(boards[0], initial_position);
10023 EditPositionDone();
10024 DisplayBothClocks();
10025 DrawPosition(FALSE, boards[currentMove]);