close icsEngineAnalyze if user remove observe games from the list
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  * XBoard $Id$
4  *
5  * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
6  * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
7  *
8  * The following terms apply to Digital Equipment Corporation's copyright
9  * interest in XBoard:
10  * ------------------------------------------------------------------------
11  * All Rights Reserved
12  *
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.
20  *
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
27  * SOFTWARE.
28  * ------------------------------------------------------------------------
29  *
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.
37  *
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.
42  *
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  * ------------------------------------------------------------------------
47  *
48  * See the file ChangeLog for a revision history.  */
49
50 #include "config.h"
51
52 #include <stdio.h>
53 #include <ctype.h>
54 #include <errno.h>
55 #include <sys/types.h>
56 #include <sys/stat.h>
57 #include <math.h>
58
59 #if STDC_HEADERS
60 # include <stdlib.h>
61 # include <string.h>
62 #else /* not STDC_HEADERS */
63 # if HAVE_STRING_H
64 #  include <string.h>
65 # else /* not HAVE_STRING_H */
66 #  include <strings.h>
67 # endif /* not HAVE_STRING_H */
68 #endif /* not STDC_HEADERS */
69
70 #if HAVE_SYS_FCNTL_H
71 # include <sys/fcntl.h>
72 #else /* not HAVE_SYS_FCNTL_H */
73 # if HAVE_FCNTL_H
74 #  include <fcntl.h>
75 # endif /* HAVE_FCNTL_H */
76 #endif /* not HAVE_SYS_FCNTL_H */
77
78 #if TIME_WITH_SYS_TIME
79 # include <sys/time.h>
80 # include <time.h>
81 #else
82 # if HAVE_SYS_TIME_H
83 #  include <sys/time.h>
84 # else
85 #  include <time.h>
86 # endif
87 #endif
88
89 #if defined(_amigados) && !defined(__GNUC__)
90 struct timezone {
91     int tz_minuteswest;
92     int tz_dsttime;
93 };
94 extern int gettimeofday(struct timeval *, struct timezone *);
95 #endif
96
97 #if HAVE_UNISTD_H
98 # include <unistd.h>
99 #endif
100
101 #include "common.h"
102 #include "frontend.h"
103 #include "backend.h"
104 #include "parser.h"
105 #include "moves.h"
106 #if ZIPPY
107 # include "zippy.h"
108 #endif
109 #include "backendz.h"
110 #include "gettext.h"
111
112 #ifdef ENABLE_NLS
113 # define  _(s) gettext (s)
114 # define N_(s) gettext_noop (s)
115 #else
116 # define  _(s) (s)
117 # define N_(s)  s
118 #endif
119
120
121 /* A point in time */
122 typedef struct {
123     long sec;  /* Assuming this is >= 32 bits */
124     int ms;    /* Assuming this is >= 16 bits */
125 } TimeMark;
126
127 /* Search stats from chessprogram */
128 typedef struct {
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 */
142 } ChessProgramStats;
143
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,
152                       int toX, int toY));
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,
161                   Board board));
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));
186
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));
216
217 #ifdef WIN32
218         extern void ConsoleCreate();
219 #endif
220
221 extern int tinyLayout, smallLayout;
222 static ChessProgramStats programStats;
223
224 /* States for ics_getting_history */
225 #define H_FALSE 0
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
231
232 /* whosays values for GameEnds */
233 #define GE_ICS 0
234 #define GE_ENGINE 1
235 #define GE_PLAYER 2
236 #define GE_FILE 3
237 #define GE_XBOARD 4
238
239 /* Maximum number of games in a cmail message */
240 #define CMAIL_MAX_GAMES 20
241
242 /* Different types of move when calling RegisterMove */
243 #define CMAIL_MOVE   0
244 #define CMAIL_RESIGN 1
245 #define CMAIL_DRAW   2
246 #define CMAIL_ACCEPT 3
247
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
252
253 /* Telnet protocol constants */
254 #define TN_WILL 0373
255 #define TN_WONT 0374
256 #define TN_DO   0375
257 #define TN_DONT 0376
258 #define TN_IAC  0377
259 #define TN_ECHO 0001
260 #define TN_SGA  0003
261 #define TN_PORT 23
262
263 /* Some compiler can't cast u64 to double
264  * This function do the job for us:
265
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)
269  *
270  * We used this for all compiler
271  */
272 double
273 u64ToDouble(u64 value)
274 {
275   double r;
276   u64 tmp = value & 0x7fffffffffffffff;
277   r = (double)(s64)tmp;
278   if (value & 0x8000000000000000)
279         r +=  9.2233720368547758080e18; /* 2^63 */
280  return r;
281 }
282
283 /* Fake up flags for now, as we aren't keeping track of castling
284    availability yet */
285 int
286 PosFlags(index)
287 {
288   int flags = F_ALL_CASTLE_OK;
289   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
290   switch (gameInfo.variant) {
291   case VariantSuicide:
292   case VariantGiveaway:
293     flags |= F_IGNORE_CHECK;
294     flags &= ~F_ALL_CASTLE_OK;
295     break;
296   case VariantAtomic:
297     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
298     break;
299   case VariantKriegspiel:
300     flags |= F_KRIEGSPIEL_CAPTURE;
301     break;
302   case VariantNoCastle:
303     flags &= ~F_ALL_CASTLE_OK;
304     break;
305   default:
306     break;
307   }
308   return flags;
309 }
310
311 FILE *gameFileFP, *debugFP;
312
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];
316
317 ChessProgramState first, second;
318
319 /* premove variables */
320 int premoveToX = 0;
321 int premoveToY = 0;
322 int premoveFromX = 0;
323 int premoveFromY = 0;
324 int premovePromoChar = 0;
325 int gotPremove = 0;
326 Boolean alarmSounded;
327 /* end premove variables */
328
329 char *ics_prefix = "$";
330 int ics_type = ICS_GENERIC;
331
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;
355 int movesPerSession;
356 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
357 long timeRemaining[2][MAX_MOVES];
358 int matchGame = 0;
359 TimeMark programStartTime;
360 char ics_handle[MSG_SIZ];
361 int have_set_title = 0;
362
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.
367  */
368 Boolean animateTraining;
369
370 GameInfo gameInfo;
371
372 AppData appData;
373
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 }
392 };
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 }
410 };
411
412
413 /* Convert str to a rating. Checks for special cases of "----",
414    "++++", etc. Also strips ()'s */
415 int
416 string_to_rating(str)
417   char *str;
418 {
419   while(*str && !isdigit(*str)) ++str;
420   if (!*str)
421     return 0;   /* One of the special "no rating" cases */
422   else
423     return atoi(str);
424 }
425
426 void
427 ClearProgramStats()
428 {
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;
440 }
441
442 void
443 InitBackEnd1()
444 {
445     int matched, min, sec;
446
447     GetTimeMark(&programStartTime);
448
449     ClearProgramStats();
450     programStats.ok_to_send = 1;
451     programStats.seen_stat = 0;
452
453     /*
454      * Initialize game list
455      */
456     ListNew(&gameList);
457
458
459     /*
460      * Internet chess server status
461      */
462     if (appData.icsActive) {
463         appData.matchMode = FALSE;
464         appData.matchGames = 0;
465 #if ZIPPY
466         appData.noChessProgram = !appData.zippyPlay;
467 #else
468         appData.zippyPlay = FALSE;
469         appData.zippyTalk = FALSE;
470         appData.noChessProgram = TRUE;
471 #endif
472         if (*appData.icsHelper != NULLCHAR) {
473             appData.useTelnet = TRUE;
474             appData.telnetProgram = appData.icsHelper;
475         }
476     } else {
477         appData.zippyTalk = appData.zippyPlay = FALSE;
478     }
479
480     /*
481      * Parse timeControl resource
482      */
483     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
484                           appData.movesPerSession)) {
485         char buf[MSG_SIZ];
486         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
487         DisplayFatalError(buf, 0, 2);
488     }
489
490     /*
491      * Parse searchTime resource
492      */
493     if (*appData.searchTime != NULLCHAR) {
494         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
495         if (matched == 1) {
496             searchTime = min * 60;
497         } else if (matched == 2) {
498             searchTime = min * 60 + sec;
499         } else {
500             char buf[MSG_SIZ];
501             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
502             DisplayFatalError(buf, 0, 2);
503         }
504     }
505
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";
516     } else {
517         first.twoMachinesColor = "white\n";
518         second.twoMachinesColor = "black\n";
519     }
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;
556
557     if (appData.firstProtocolVersion > PROTOVER ||
558         appData.firstProtocolVersion < 1) {
559       char buf[MSG_SIZ];
560       sprintf(buf, _("protocol version %d not supported"),
561               appData.firstProtocolVersion);
562       DisplayFatalError(buf, 0, 2);
563     } else {
564       first.protocolVersion = appData.firstProtocolVersion;
565     }
566
567     if (appData.secondProtocolVersion > PROTOVER ||
568         appData.secondProtocolVersion < 1) {
569       char buf[MSG_SIZ];
570       sprintf(buf, _("protocol version %d not supported"),
571               appData.secondProtocolVersion);
572       DisplayFatalError(buf, 0, 2);
573     } else {
574       second.protocolVersion = appData.secondProtocolVersion;
575     }
576
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;
582     }
583
584 #if ZIPPY
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.
588     */
589     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
590       ZippyInit();
591     }
592 #endif
593
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);
598     } else {
599         char *p, *q;
600         q = first.program;
601         while (*q != ' ' && *q != NULLCHAR) q++;
602         p = 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);
608     }
609
610     if (!appData.icsActive) {
611       char buf[MSG_SIZ];
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.
615       */
616       VariantClass variant = StringToVariant(appData.variant);
617       switch (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);
623         return;
624
625       case VariantUnknown:
626       case VariantLoadable:
627       case Variant29:
628       case Variant30:
629       case Variant31:
630       case Variant32:
631       case Variant33:
632       case Variant34:
633       case Variant35:
634       case Variant36:
635       default:
636         sprintf(buf, _("Unknown variant name %s"), appData.variant);
637         DisplayFatalError(buf, 0, 2);
638         return;
639
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 */
655         break;
656       }
657     }
658 }
659
660 int
661 ParseTimeControl(tc, ti, mps)
662      char *tc;
663      int ti;
664      int mps;
665 {
666     int matched, min, sec;
667
668     matched = sscanf(tc, "%d:%d", &min, &sec);
669     if (matched == 1) {
670         timeControl = min * 60 * 1000;
671     } else if (matched == 2) {
672         timeControl = (min * 60 + sec) * 1000;
673     } else {
674         return FALSE;
675     }
676
677     if (ti >= 0) {
678         timeIncrement = ti * 1000;  /* convert to ms */
679         movesPerSession = 0;
680     } else {
681         timeIncrement = 0;
682         movesPerSession = mps;
683     }
684     return TRUE;
685 }
686
687 void
688 InitBackEnd2()
689 {
690     if (appData.debugMode) {
691         fprintf(debugFP, "%s\n", programVersion);
692     }
693
694     if (appData.matchGames > 0) {
695         appData.matchMode = TRUE;
696     } else if (appData.matchMode) {
697         appData.matchGames = 1;
698     }
699     Reset(TRUE, FALSE);
700     if (appData.noChessProgram || first.protocolVersion == 1) {
701       InitBackEnd3();
702     } else {
703       /* kludge: allow timeout for initial "feature" commands */
704       FreezeUI();
705       DisplayMessage("", "Starting chess program");
706       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
707     }
708 }
709
710 void
711 InitBackEnd3 P((void))
712 {
713     GameMode initialMode;
714     char buf[MSG_SIZ];
715     int err;
716
717     InitChessProgram(&first);
718
719     #ifdef WIN32
720                 /* Make a console window if needed */
721                 if (appData.icsActive) ConsoleCreate();
722         #endif
723
724     if (appData.icsActive) {
725         err = establish();
726         if (err != 0) {
727             if (*appData.icsCommPort != NULLCHAR) {
728                 sprintf(buf, _("Could not open comm port %s"),
729                         appData.icsCommPort);
730             } else {
731                 sprintf(buf, _("Could not connect to host %s, port %s"),
732                         appData.icsHost, appData.icsPort);
733             }
734             DisplayFatalError(buf, err, 1);
735             return;
736         }
737         SetICSMode();
738         telnetISR =
739           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
740         fromUserISR =
741           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
742     } else if (appData.noChessProgram) {
743         SetNCPMode();
744     } else {
745         SetGNUMode();
746     }
747
748     if (*appData.cmailGameName != NULLCHAR) {
749         SetCmailMode();
750         OpenLoopback(&cmailPR);
751         cmailISR =
752           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
753     }
754
755     ThawUI();
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;
775     } else {
776       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
777       DisplayFatalError(buf, 0, 2);
778       return;
779     }
780
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"),
785                               0, 2);
786             return;
787         }
788         matchMode = TRUE;
789         matchGame = 1;
790         if (*appData.loadGameFile != NULLCHAR) {
791             if (!LoadGameFromFile(appData.loadGameFile,
792                                   appData.loadGameIndex,
793                                   appData.loadGameFile, FALSE)) {
794                 DisplayFatalError(_("Bad game file"), 0, 1);
795                 return;
796             }
797         } else if (*appData.loadPositionFile != NULLCHAR) {
798             if (!LoadPositionFromFile(appData.loadPositionFile,
799                                       appData.loadPositionIndex,
800                                       appData.loadPositionFile)) {
801                 DisplayFatalError(_("Bad position file"), 0, 1);
802                 return;
803             }
804         }
805         TwoMachinesEvent();
806     } else if (*appData.cmailGameName != NULLCHAR) {
807         /* Set up cmail mode */
808         ReloadCmailMsgEvent(TRUE);
809     } else {
810         /* Set up other modes */
811         if (initialMode == AnalyzeFile) {
812           if (*appData.loadGameFile == NULLCHAR) {
813             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
814             return;
815           }
816         }
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);
825         }
826         if (initialMode == AnalyzeMode) {
827           if (appData.noChessProgram) {
828             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
829             return;
830           }
831           if (appData.icsActive) {
832             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
833             return;
834           }
835           AnalyzeModeEvent();
836         } else if (initialMode == AnalyzeFile) {
837           ShowThinkingEvent(TRUE);
838           AnalyzeFileEvent();
839           AnalysisPeriodicEvent(1);
840         } else if (initialMode == MachinePlaysWhite) {
841           if (appData.noChessProgram) {
842             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
843                               0, 2);
844             return;
845           }
846           if (appData.icsActive) {
847             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
848                               0, 2);
849             return;
850           }
851           MachineWhiteEvent();
852         } else if (initialMode == MachinePlaysBlack) {
853           if (appData.noChessProgram) {
854             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
855                               0, 2);
856             return;
857           }
858           if (appData.icsActive) {
859             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
860                               0, 2);
861             return;
862           }
863           MachineBlackEvent();
864         } else if (initialMode == TwoMachinesPlay) {
865           if (appData.noChessProgram) {
866             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
867                               0, 2);
868             return;
869           }
870           if (appData.icsActive) {
871             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
872                               0, 2);
873             return;
874           }
875           TwoMachinesEvent();
876         } else if (initialMode == EditGame) {
877           EditGameEvent();
878         } else if (initialMode == EditPosition) {
879           EditPositionEvent();
880         } else if (initialMode == Training) {
881           if (*appData.loadGameFile == NULLCHAR) {
882             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
883             return;
884           }
885           TrainingEvent();
886         }
887     }
888 }
889
890 /*
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.
895  */
896 int
897 establish()
898 {
899     char buf[MSG_SIZ];
900
901     if (*appData.icsCommPort != NULLCHAR) {
902         /* Talk to the host through a serial comm port */
903         return OpenCommPort(appData.icsCommPort, &icsPR);
904
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);
911
912         } else {
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);
918             } else {
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);
923             }
924             return StartChildProcess(buf, "", &icsPR);
925
926         }
927     } else if (appData.useTelnet) {
928         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
929
930     } else {
931         /* TCP socket interface differs somewhat between
932            Unix and NT; handle details in the front end.
933            */
934         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
935     }
936 }
937
938 void
939 show_bytes(fp, buf, count)
940      FILE *fp;
941      char *buf;
942      int count;
943 {
944     while (count--) {
945         if (*buf < 040 || *(unsigned char *) buf > 0177) {
946             fprintf(fp, "\\%03o", *buf & 0xff);
947         } else {
948             putc(*buf, fp);
949         }
950         buf++;
951     }
952     fflush(fp);
953 }
954
955 /* Returns an errno value */
956 int
957 OutputMaybeTelnet(pr, message, count, outError)
958      ProcRef pr;
959      char *message;
960      int count;
961      int *outError;
962 {
963     char buf[8192], *p, *q, *buflim;
964     int left, newcount, outcount;
965
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");
972         }
973         return OutputToProcess(pr, message, count, outError);
974     }
975
976     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
977     p = message;
978     q = buf;
979     left = count;
980     newcount = 0;
981     while (left) {
982         if (q >= buflim) {
983             if (appData.debugMode) {
984                 fprintf(debugFP, ">ICS: ");
985                 show_bytes(debugFP, buf, newcount);
986                 fprintf(debugFP, "\n");
987             }
988             outcount = OutputToProcess(pr, buf, newcount, outError);
989             if (outcount < newcount) return -1; /* to be sure */
990             q = buf;
991             newcount = 0;
992         }
993         if (*p == '\n') {
994             *q++ = '\r';
995             newcount++;
996         } else if (((unsigned char) *p) == TN_IAC) {
997             *q++ = (char) TN_IAC;
998             newcount ++;
999         }
1000         *q++ = *p++;
1001         newcount++;
1002         left--;
1003     }
1004     if (appData.debugMode) {
1005         fprintf(debugFP, ">ICS: ");
1006         show_bytes(debugFP, buf, newcount);
1007         fprintf(debugFP, "\n");
1008     }
1009     outcount = OutputToProcess(pr, buf, newcount, outError);
1010     if (outcount < newcount) return -1; /* to be sure */
1011     return count;
1012 }
1013
1014 void
1015 read_from_player(isr, closure, message, count, error)
1016      InputSourceRef isr;
1017      VOIDSTAR closure;
1018      char *message;
1019      int count;
1020      int error;
1021 {
1022     int outError, outCount;
1023     static int gotEof = 0;
1024
1025     /* Pass data read from player on to ICS */
1026     if (count > 0) {
1027         gotEof = 0;
1028         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1029         if (outCount < count) {
1030             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1031         }
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);
1038     }
1039 }
1040
1041 void
1042 SendToICS(s)
1043      char *s;
1044 {
1045     int count, outCount, outError;
1046
1047     if (icsPR == NULL) return;
1048
1049     count = strlen(s);
1050     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1051     if (outCount < count) {
1052         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1053     }
1054 }
1055
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). */
1059 void
1060 SendToICSDelayed(s,msdelay)
1061      char *s;
1062      long msdelay;
1063 {
1064     int count, outCount, outError;
1065
1066     if (icsPR == NULL) return;
1067
1068     count = strlen(s);
1069     if (appData.debugMode) {
1070         fprintf(debugFP, ">ICS: ");
1071         show_bytes(debugFP, s, count);
1072         fprintf(debugFP, "\n");
1073     }
1074     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1075                                       msdelay);
1076     if (outCount < count) {
1077         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1078     }
1079 }
1080
1081
1082 /* Remove all highlighting escape sequences in s
1083    Also deletes any suffix starting with '('
1084    */
1085 char *
1086 StripHighlightAndTitle(s)
1087      char *s;
1088 {
1089     static char retbuf[MSG_SIZ];
1090     char *p = retbuf;
1091
1092     while (*s != NULLCHAR) {
1093         while (*s == '\033') {
1094             while (*s != NULLCHAR && !isalpha(*s)) s++;
1095             if (*s != NULLCHAR) s++;
1096         }
1097         while (*s != NULLCHAR && *s != '\033') {
1098             if (*s == '(' || *s == '[') {
1099                 *p = NULLCHAR;
1100                 return retbuf;
1101             }
1102             *p++ = *s++;
1103         }
1104     }
1105     *p = NULLCHAR;
1106     return retbuf;
1107 }
1108
1109 /* Remove all highlighting escape sequences in s */
1110 char *
1111 StripHighlight(s)
1112      char *s;
1113 {
1114     static char retbuf[MSG_SIZ];
1115     char *p = retbuf;
1116
1117     while (*s != NULLCHAR) {
1118         while (*s == '\033') {
1119             while (*s != NULLCHAR && !isalpha(*s)) s++;
1120             if (*s != NULLCHAR) s++;
1121         }
1122         while (*s != NULLCHAR && *s != '\033') {
1123             *p++ = *s++;
1124         }
1125     }
1126     *p = NULLCHAR;
1127     return retbuf;
1128 }
1129
1130 char *variantNames[] = VARIANT_NAMES;
1131 char *
1132 VariantName(v)
1133      VariantClass v;
1134 {
1135     return variantNames[v];
1136 }
1137
1138
1139 /* Identify a variant from the strings the chess servers use or the
1140    PGN Variant tag names we use. */
1141 VariantClass
1142 StringToVariant(e)
1143      char *e;
1144 {
1145     char *p;
1146     int wnum = -1;
1147     VariantClass v = VariantNormal;
1148     int i, found = FALSE;
1149     char buf[MSG_SIZ];
1150
1151     if (!e) return v;
1152
1153     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1154       if (StrCaseStr(e, variantNames[i])) {
1155         v = (VariantClass) i;
1156         found = TRUE;
1157         break;
1158       }
1159     }
1160
1161     if (!found) {
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"))) {
1167         p += i;
1168         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1169         if (isdigit(*p)) {
1170           wnum = atoi(p);
1171         } else {
1172           wnum = -1;
1173         }
1174         switch (wnum) {
1175         case 0: /* FICS only, actually */
1176         case 1:
1177           /* Castling legal even if K starts on d-file */
1178           v = VariantWildCastle;
1179           break;
1180         case 2:
1181         case 3:
1182         case 4:
1183           /* Castling illegal even if K & R happen to start in
1184              normal positions. */
1185           v = VariantNoCastle;
1186           break;
1187         case 5:
1188         case 7:
1189         case 8:
1190         case 10:
1191         case 11:
1192         case 12:
1193         case 13:
1194         case 14:
1195         case 15:
1196         case 18:
1197         case 19:
1198           /* Castling legal iff K & R start in normal positions */
1199           v = VariantNormal;
1200           break;
1201         case 6:
1202         case 20:
1203         case 21:
1204           /* Special wilds for position setup; unclear what to do here */
1205           v = VariantLoadable;
1206           break;
1207         case 9:
1208           /* Bizarre ICC game */
1209           v = VariantTwoKings;
1210           break;
1211         case 16:
1212           v = VariantKriegspiel;
1213           break;
1214         case 17:
1215           v = VariantLosers;
1216           break;
1217         case 22:
1218           v = VariantFischeRandom;
1219           break;
1220         case 23:
1221           v = VariantCrazyhouse;
1222           break;
1223         case 24:
1224           v = VariantBughouse;
1225           break;
1226         case 25:
1227           v = Variant3Check;
1228           break;
1229         case 26:
1230           /* Not quite the same as FICS suicide! */
1231           v = VariantGiveaway;
1232           break;
1233         case 27:
1234           v = VariantAtomic;
1235           break;
1236         case 28:
1237           v = VariantShatranj;
1238           break;
1239
1240         /* Temporary names for future ICC types.  The name *will* change in
1241            the next xboard/WinBoard release after ICC defines it. */
1242         case 29:
1243           v = Variant29;
1244           break;
1245         case 30:
1246           v = Variant30;
1247           break;
1248         case 31:
1249           v = Variant31;
1250           break;
1251         case 32:
1252           v = Variant32;
1253           break;
1254         case 33:
1255           v = Variant33;
1256           break;
1257         case 34:
1258           v = Variant34;
1259           break;
1260         case 35:
1261           v = Variant35;
1262           break;
1263         case 36:
1264           v = Variant36;
1265           break;
1266
1267         case -1:
1268           /* Found "wild" or "w" in the string but no number;
1269              must assume it's normal chess. */
1270           v = VariantNormal;
1271           break;
1272         default:
1273           sprintf(buf, "Unknown wild type %d", wnum);
1274           DisplayError(buf, 0);
1275           v = VariantUnknown;
1276           break;
1277         }
1278       }
1279     }
1280     if (appData.debugMode) {
1281       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1282               e, wnum, VariantName(v));
1283     }
1284     return v;
1285 }
1286
1287 static int leftover_start = 0, leftover_len = 0;
1288 char star_match[STAR_MATCH_N][MSG_SIZ];
1289
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.
1296    */
1297 int
1298 looking_at(buf, index, pattern)
1299      char *buf;
1300      int *index;
1301      char *pattern;
1302 {
1303     char *bufp = &buf[*index], *patternp = pattern;
1304     int star_count = 0;
1305     char *matchp = star_match[0];
1306
1307     for (;;) {
1308         if (*patternp == NULLCHAR) {
1309             *index = leftover_start = bufp - buf;
1310             *matchp = NULLCHAR;
1311             return TRUE;
1312         }
1313         if (*bufp == NULLCHAR) return FALSE;
1314         if (*patternp == '*') {
1315             if (*bufp == *(patternp + 1)) {
1316                 *matchp = NULLCHAR;
1317                 matchp = star_match[++star_count];
1318                 patternp += 2;
1319                 bufp++;
1320                 continue;
1321             } else if (*bufp == '\n' || *bufp == '\r') {
1322                 patternp++;
1323                 if (*patternp == NULLCHAR)
1324                   continue;
1325                 else
1326                   return FALSE;
1327             } else {
1328                 *matchp++ = *bufp++;
1329                 continue;
1330             }
1331         }
1332         if (*patternp != *bufp) return FALSE;
1333         patternp++;
1334         bufp++;
1335     }
1336 }
1337
1338 void
1339 SendToPlayer(data, length)
1340      char *data;
1341      int length;
1342 {
1343     int error, outCount;
1344     outCount = OutputToProcess(NoProc, data, length, &error);
1345     if (outCount < length) {
1346         DisplayFatalError(_("Error writing to display"), error, 1);
1347     }
1348 }
1349
1350 void
1351 PackHolding(packed, holding)
1352      char packed[];
1353      char *holding;
1354 {
1355     char *p = holding;
1356     char *q = packed;
1357     int runlength = 0;
1358     int curr = 9999;
1359     do {
1360         if (*p == curr) {
1361             runlength++;
1362         } else {
1363             switch (runlength) {
1364               case 0:
1365                 break;
1366               case 1:
1367                 *q++ = curr;
1368                 break;
1369               case 2:
1370                 *q++ = curr;
1371                 *q++ = curr;
1372                 break;
1373               default:
1374                 sprintf(q, "%d", runlength);
1375                 while (*q) q++;
1376                 *q++ = curr;
1377                 break;
1378             }
1379             runlength = 1;
1380             curr = *p;
1381         }
1382     } while (*p++);
1383     *q = NULLCHAR;
1384 }
1385
1386 /* Telnet protocol requests from the front end */
1387 void
1388 TelnetRequest(ddww, option)
1389      unsigned char ddww, option;
1390 {
1391     unsigned char msg[3];
1392     int outCount, outError;
1393
1394     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1395
1396     if (appData.debugMode) {
1397         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1398         switch (ddww) {
1399           case TN_DO:
1400             ddwwStr = "DO";
1401             break;
1402           case TN_DONT:
1403             ddwwStr = "DONT";
1404             break;
1405           case TN_WILL:
1406             ddwwStr = "WILL";
1407             break;
1408           case TN_WONT:
1409             ddwwStr = "WONT";
1410             break;
1411           default:
1412             ddwwStr = buf1;
1413             sprintf(buf1, "%d", ddww);
1414             break;
1415         }
1416         switch (option) {
1417           case TN_ECHO:
1418             optionStr = "ECHO";
1419             break;
1420           default:
1421             optionStr = buf2;
1422             sprintf(buf2, "%d", option);
1423             break;
1424         }
1425         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1426     }
1427     msg[0] = TN_IAC;
1428     msg[1] = ddww;
1429     msg[2] = option;
1430     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1431     if (outCount < 3) {
1432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1433     }
1434 }
1435
1436 void
1437 DoEcho()
1438 {
1439     if (!appData.icsActive) return;
1440     TelnetRequest(TN_DO, TN_ECHO);
1441 }
1442
1443 void
1444 DontEcho()
1445 {
1446     if (!appData.icsActive) return;
1447     TelnetRequest(TN_DONT, TN_ECHO);
1448 }
1449
1450 static int loggedOn = FALSE;
1451
1452 /*-- Game start info cache: --*/
1453 int gs_gamenum;
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 /*----------------------------*/
1460
1461 void
1462 read_from_ics(isr, closure, data, count, error)
1463      InputSourceRef isr;
1464      VOIDSTAR closure;
1465      char *data;
1466      int count;
1467      int error;
1468 {
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
1478
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;
1487     char str[500];
1488     int i, oldi;
1489     int buf_len;
1490     int next_out;
1491     int tkind;
1492 #ifdef WIN32
1493         /* For zippy color lines of winboard
1494          * cleanup for gcc compiler */
1495         int backup;
1496 #endif
1497     char *p;
1498
1499 #ifdef WIN32
1500     if (appData.debugMode) {
1501       if (!error) {
1502         fprintf(debugFP, "<ICS: ");
1503         show_bytes(debugFP, data, count);
1504         fprintf(debugFP, "\n");
1505       }
1506     }
1507 #endif
1508
1509     if (count > 0) {
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];
1515         }
1516
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];
1522         }
1523
1524         buf[buf_len] = NULLCHAR;
1525         next_out = leftover_len;
1526         leftover_start = 0;
1527
1528         i = 0;
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.
1538              */
1539             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1540                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1541                 unsigned char option;
1542                 oldi = i;
1543                 switch ((unsigned char) buf[++i]) {
1544                   case TN_WILL:
1545                     if (appData.debugMode)
1546                       fprintf(debugFP, "\n<WILL ");
1547                     switch (option = (unsigned char) buf[++i]) {
1548                       case TN_ECHO:
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);
1557                         } else {
1558                             EchoOff();
1559                             TelnetRequest(TN_DO, TN_ECHO);
1560                             remoteEchoOption = TRUE;
1561                         }
1562                         break;
1563                       default:
1564                         if (appData.debugMode)
1565                           fprintf(debugFP, "%d ", option);
1566                         /* Whatever this is, we don't want it. */
1567                         TelnetRequest(TN_DONT, option);
1568                         break;
1569                     }
1570                     break;
1571                   case TN_WONT:
1572                     if (appData.debugMode)
1573                       fprintf(debugFP, "\n<WONT ");
1574                     switch (option = (unsigned char) buf[++i]) {
1575                       case TN_ECHO:
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;
1581                         EchoOn();
1582                         TelnetRequest(TN_DONT, TN_ECHO);
1583                         remoteEchoOption = FALSE;
1584                         break;
1585                       default:
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. */
1592                         break;
1593                     }
1594                     break;
1595                   case TN_DO:
1596                     if (appData.debugMode)
1597                       fprintf(debugFP, "\n<DO ");
1598                     switch (option = (unsigned char) buf[++i]) {
1599                       default:
1600                         /* Whatever this is, we refuse to do it. */
1601                         if (appData.debugMode)
1602                           fprintf(debugFP, "%d ", option);
1603                         TelnetRequest(TN_WONT, option);
1604                         break;
1605                     }
1606                     break;
1607                   case TN_DONT:
1608                     if (appData.debugMode)
1609                       fprintf(debugFP, "\n<DONT ");
1610                     switch (option = (unsigned char) buf[++i]) {
1611                       default:
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. */
1618                         break;
1619                     }
1620                     break;
1621                   case TN_IAC:
1622                     if (appData.debugMode)
1623                       fprintf(debugFP, "\n<IAC ");
1624                     /* Doubled IAC; pass it through */
1625                     i--;
1626                     break;
1627                   default:
1628                     if (appData.debugMode)
1629                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1630                     /* Drop all other telnet commands on the floor */
1631                     break;
1632                 }
1633                 if (oldi > next_out)
1634                   SendToPlayer(&buf[next_out], oldi - next_out);
1635                 if (++i > next_out)
1636                   next_out = i;
1637                 continue;
1638             }
1639
1640             /* OK, this at least will *usually* work */
1641             if (!loggedOn && looking_at(buf, &i, "ics%")) {
1642                 loggedOn = TRUE;
1643             }
1644
1645             if (loggedOn && !intfSet) {
1646                 if (ics_type == ICS_ICC) {
1647                   sprintf(str,
1648                           "/set-quietly interface %s\n/set-quietly style 12\n",
1649                           programVersion);
1650
1651                 } else if (ics_type == ICS_CHESSNET) {
1652                   sprintf(str, "/style 12\n");
1653                 } else {
1654                   strcpy(str, "alias $ @\n$set interface ");
1655                   strcat(str, programVersion);
1656                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1657 #ifdef WIN32
1658                   strcat(str, "$iset nohighlight 1\n");
1659 #endif
1660                   strcat(str, "$iset lock 1\n$style 12\n");
1661                 }
1662                 SendToICS(str);
1663                 intfSet = TRUE;
1664             }
1665
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;
1673                 } else {
1674                     /* Don't match patterns against characters in chatter */
1675                     i++;
1676                     continue;
1677                 }
1678             }
1679             if (started == STARTED_CHATTER) {
1680                 if (buf[i] != '\n') {
1681                     /* Don't match patterns against characters in chatter */
1682                     i++;
1683                     continue;
1684                 }
1685                 started = STARTED_NONE;
1686             }
1687
1688             /* Kludge to deal with rcmd protocol */
1689             if (firstTime && looking_at(buf, &i, "\001*")) {
1690                 DisplayFatalError(&buf[1], 0, 1);
1691                 continue;
1692             } else {
1693                 firstTime = FALSE;
1694             }
1695
1696             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1697                 ics_type = ICS_ICC;
1698                 ics_prefix = "/";
1699                 if (appData.debugMode)
1700                   fprintf(debugFP, "ics_type %d\n", ics_type);
1701                 continue;
1702             }
1703             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1704                 ics_type = ICS_FICS;
1705                 ics_prefix = "$";
1706                 if (appData.debugMode)
1707                   fprintf(debugFP, "ics_type %d\n", ics_type);
1708                 continue;
1709             }
1710             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1711                 ics_type = ICS_CHESSNET;
1712                 ics_prefix = "/";
1713                 if (appData.debugMode)
1714                   fprintf(debugFP, "ics_type %d\n", ics_type);
1715                 continue;
1716             }
1717
1718             if (!loggedOn &&
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]);
1723               continue;
1724             }
1725
1726             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1727               char buf[MSG_SIZ];
1728               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1729               DisplayIcsInteractionTitle(buf);
1730               have_set_title = TRUE;
1731             }
1732
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;
1739               i += 3;
1740               continue;
1741             }
1742
1743             /* skip formula vars */
1744             if (started == STARTED_NONE &&
1745                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1746               started = STARTED_CHATTER;
1747               i += 3;
1748               continue;
1749             }
1750
1751             oldi = i;
1752             if (appData.zippyTalk || appData.zippyPlay) {
1753 #if ZIPPY
1754         #ifdef WIN32
1755                 /* Backup address for color zippy lines */
1756                 backup = i;
1757                 if (loggedOn == TRUE)
1758                         if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
1759                                 (appData.zippyPlay && ZippyMatch(buf, &backup)));
1760                 #else
1761                 if (ZippyControl(buf, &i) ||
1762                     ZippyConverse(buf, &i) ||
1763                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
1764                     loggedOn = TRUE;
1765                     continue;
1766                 }
1767         #endif
1768 #endif
1769             }
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: ") ||
1784                     /* Channel tells */
1785                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1786
1787                   if (tkind == 1 && strchr(star_match[0], ':')) {
1788                       /* Avoid "tells you:" spoofs in channels */
1789                      tkind = 3;
1790                   }
1791                   if (star_match[0][0] == NULLCHAR ||
1792                       strchr(star_match[0], ' ') ||
1793                       (tkind == 3 && strchr(star_match[1], ' '))) {
1794                     /* Reject bogus matches */
1795                     i = oldi;
1796                   } else {
1797                     if (appData.colorize) {
1798                       if (oldi > next_out) {
1799                         SendToPlayer(&buf[next_out], oldi - next_out);
1800                         next_out = oldi;
1801                       }
1802                       switch (tkind) {
1803                       case 1:
1804                         Colorize(ColorTell, FALSE);
1805                         curColor = ColorTell;
1806                         break;
1807                       case 2:
1808                         Colorize(ColorKibitz, FALSE);
1809                         curColor = ColorKibitz;
1810                         break;
1811                       case 3:
1812                         p = strrchr(star_match[1], '(');
1813                         if (p == NULL) {
1814                           p = star_match[1];
1815                         } else {
1816                           p++;
1817                         }
1818                         if (atoi(p) == 1) {
1819                           Colorize(ColorChannel1, FALSE);
1820                           curColor = ColorChannel1;
1821                         } else {
1822                           Colorize(ColorChannel, FALSE);
1823                           curColor = ColorChannel;
1824                         }
1825                         break;
1826                       case 5:
1827                         curColor = ColorNormal;
1828                         break;
1829                       }
1830                     }
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;
1840                     } else {
1841                       started = STARTED_CHATTER;
1842                       savingComment = FALSE;
1843                     }
1844                     loggedOn = TRUE;
1845                     continue;
1846                   }
1847                 }
1848
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);
1854                             next_out = oldi;
1855                         }
1856                         Colorize(ColorSShout, FALSE);
1857                         curColor = ColorSShout;
1858                     }
1859                     loggedOn = TRUE;
1860                     started = STARTED_CHATTER;
1861                     continue;
1862                 }
1863
1864                 if (looking_at(buf, &i, "--->")) {
1865                     loggedOn = TRUE;
1866                     continue;
1867                 }
1868
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);
1874                             next_out = oldi;
1875                         }
1876                         Colorize(ColorShout, FALSE);
1877                         curColor = ColorShout;
1878                     }
1879                     loggedOn = TRUE;
1880                     started = STARTED_CHATTER;
1881                     continue;
1882                 }
1883
1884                 if (looking_at( buf, &i, "Challenge:")) {
1885                     if (appData.colorize) {
1886                         if (oldi > next_out) {
1887                             SendToPlayer(&buf[next_out], oldi - next_out);
1888                             next_out = oldi;
1889                         }
1890                         Colorize(ColorChallenge, FALSE);
1891                         curColor = ColorChallenge;
1892                     }
1893                     loggedOn = TRUE;
1894                     continue;
1895                 }
1896
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")) {
1903
1904                     if (appData.colorize) {
1905                         if (oldi > next_out) {
1906                             SendToPlayer(&buf[next_out], oldi - next_out);
1907                             next_out = oldi;
1908                         }
1909                         Colorize(ColorRequest, FALSE);
1910                         curColor = ColorRequest;
1911                     }
1912                     continue;
1913                 }
1914
1915                 if (looking_at(buf, &i, "* (*) seeking")) {
1916                     if (appData.colorize) {
1917                         if (oldi > next_out) {
1918                             SendToPlayer(&buf[next_out], oldi - next_out);
1919                             next_out = oldi;
1920                         }
1921                         Colorize(ColorSeek, FALSE);
1922                         curColor = ColorSeek;
1923                     }
1924                     continue;
1925             }
1926
1927             if (looking_at(buf, &i, "\\   ")) {
1928                 if (prevColor != ColorNormal) {
1929                     if (oldi > next_out) {
1930                         SendToPlayer(&buf[next_out], oldi - next_out);
1931                         next_out = oldi;
1932                     }
1933                     Colorize(prevColor, TRUE);
1934                     curColor = prevColor;
1935                 }
1936                 if (savingComment) {
1937                     parse_pos = i - oldi;
1938                     memcpy(parse, &buf[oldi], parse_pos);
1939                     parse[parse_pos] = NULLCHAR;
1940                     started = STARTED_COMMENT;
1941                 } else {
1942                     started = STARTED_CHATTER;
1943                 }
1944                 continue;
1945             }
1946
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 */
1952                 loggedOn = TRUE;
1953                 SendToICS(ics_prefix);
1954                 SendToICS("set style 12\n");
1955                 SendToICS(ics_prefix);
1956                 SendToICS("refresh\n");
1957                 continue;
1958             }
1959
1960             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
1961                 ICSInitScript();
1962                 have_sent_ICS_logon = 1;
1963                 continue;
1964             }
1965
1966             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
1967                 (looking_at(buf, &i, "\n<12> ") ||
1968                  looking_at(buf, &i, "<12> "))) {
1969                 loggedOn = TRUE;
1970                 if (oldi > next_out) {
1971                     SendToPlayer(&buf[next_out], oldi - next_out);
1972                 }
1973                 next_out = i;
1974                 started = STARTED_BOARD;
1975                 parse_pos = 0;
1976                 continue;
1977             }
1978
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);
1983                 }
1984                 next_out = i;
1985                 started = STARTED_HOLDINGS;
1986                 parse_pos = 0;
1987                 continue;
1988             }
1989
1990             if (looking_at(buf, &i, "* *vs. * *--- *")) {
1991                 loggedOn = TRUE;
1992                 /* Header for a move list -- first line */
1993
1994                 switch (ics_getting_history) {
1995                   case H_FALSE:
1996                     switch (gameMode) {
1997                       case IcsIdle:
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.
2003                            */
2004                         Reset(FALSE, TRUE);
2005                         gameMode = IcsObserving;
2006                         ModeHighlight();
2007                         ics_gamenum = -1;
2008                         ics_getting_history = H_GOT_UNREQ_HEADER;
2009                         break;
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;
2015                         break;
2016                       default:
2017                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2018                         break;
2019                     }
2020                     break;
2021                   case H_REQUESTED:
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) {
2026                         /* All is well */
2027                         ics_getting_history = H_GOT_REQ_HEADER;
2028                     }
2029                     break;
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;
2037                     break;
2038                 }
2039
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)) {
2045
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);
2051                 }
2052                 continue;
2053             }
2054
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 */
2059
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);
2064                 continue;
2065             }
2066
2067             if (looking_at(buf, &i, "Move  ")) {
2068                 /* Beginning of a move list */
2069                 switch (ics_getting_history) {
2070                   case H_FALSE:
2071                     /* Normally should not happen */
2072                     /* Maybe user hit reset while we were parsing */
2073                     break;
2074                   case H_REQUESTED:
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
2078                      * getMoveList */
2079                     break;
2080                   case H_GETTING_MOVES:
2081                     /* Should not happen */
2082                     DisplayError(_("Error gathering move list: nested"), 0);
2083                     ics_getting_history = H_FALSE;
2084                     break;
2085                   case H_GOT_REQ_HEADER:
2086                     ics_getting_history = H_GETTING_MOVES;
2087                     started = STARTED_MOVES;
2088                     parse_pos = 0;
2089                     if (oldi > next_out) {
2090                         SendToPlayer(&buf[next_out], oldi - next_out);
2091                     }
2092                     break;
2093                   case H_GOT_UNREQ_HEADER:
2094                     ics_getting_history = H_GETTING_MOVES;
2095                     started = STARTED_MOVES_NOHIDE;
2096                     parse_pos = 0;
2097                     break;
2098                   case H_GOT_UNWANTED_HEADER:
2099                     ics_getting_history = H_FALSE;
2100                     break;
2101                 }
2102                 continue;
2103             }
2104
2105             if (looking_at(buf, &i, "% ") ||
2106                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2107                  && looking_at(buf, &i, "}*"))) {
2108                 savingComment = FALSE;
2109                 switch (started) {
2110                   case STARTED_MOVES:
2111                   case STARTED_MOVES_NOHIDE:
2112                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2113                     parse[parse_pos + i - oldi] = NULLCHAR;
2114                     ParseGameHistory(parse);
2115 #if ZIPPY
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);
2123                                   }
2124                                   SendTimeRemaining(&first, TRUE);
2125                                 }
2126                                 if (first.useColors) {
2127                                   SendToProgram("white\ngo\n", &first);
2128                                 } else {
2129                                   SendToProgram("go\n", &first);
2130                                 }
2131                                 first.maybeThinking = TRUE;
2132                             } else {
2133                                 if (first.usePlayother) {
2134                                   if (first.sendTime) {
2135                                     SendTimeRemaining(&first, TRUE);
2136                                   }
2137                                   SendToProgram("playother\n", &first);
2138                                   firstMove = FALSE;
2139                                 } else {
2140                                   firstMove = TRUE;
2141                                 }
2142                             }
2143                         } else if (gameMode == IcsPlayingBlack) {
2144                             if (!WhiteOnMove(forwardMostMove)) {
2145                                 if (first.sendTime) {
2146                                   if (first.useColors) {
2147                                     SendToProgram("white\n", &first);
2148                                   }
2149                                   SendTimeRemaining(&first, FALSE);
2150                                 }
2151                                 if (first.useColors) {
2152                                   SendToProgram("black\ngo\n", &first);
2153                                 } else {
2154                                   SendToProgram("go\n", &first);
2155                                 }
2156                                 first.maybeThinking = TRUE;
2157                             } else {
2158                                 if (first.usePlayother) {
2159                                   if (first.sendTime) {
2160                                     SendTimeRemaining(&first, FALSE);
2161                                   }
2162                                   SendToProgram("playother\n", &first);
2163                                   firstMove = FALSE;
2164                                 } else {
2165                                   firstMove = TRUE;
2166                                 }
2167                             }
2168                         }
2169                     }
2170 #endif
2171                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2172                         /* Moves came from oldmoves or moves command
2173                            while we weren't doing anything else.
2174                            */
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);
2182                         DisplayTitle(str);
2183                         gameMode = IcsIdle;
2184                     } else {
2185                         /* Moves were history of an active game */
2186                         if (gameInfo.resultDetails != NULL) {
2187                             free(gameInfo.resultDetails);
2188                             gameInfo.resultDetails = NULL;
2189                         }
2190                     }
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;
2197                     break;
2198
2199                   case STARTED_OBSERVE:
2200                     started = STARTED_NONE;
2201                     SendToICS(ics_prefix);
2202                     SendToICS("refresh\n");
2203                     break;
2204
2205                   default:
2206                     break;
2207                 }
2208                 continue;
2209             }
2210
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];
2216             }
2217
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;
2224                 continue;
2225             }
2226
2227             /* Handle auto-observe */
2228             if (appData.autoObserve &&
2229                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2230                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2231                 char *player;
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];
2236                 } else {
2237                     player = star_match[2];
2238                 }
2239                 sprintf(str, "%sobserve %s\n",
2240                         ics_prefix, StripHighlightAndTitle(player));
2241                 SendToICS(str);
2242
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]);
2248
2249                 if (appData.debugMode)
2250                   fprintf(debugFP,
2251                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2252                           player1Name, player1Rating,
2253                           player2Name, player2Rating);
2254
2255                 continue;
2256             }
2257
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 *")) {
2262
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;
2269                     if (pausing) {
2270                         pauseExamForwardMostMove = forwardMostMove;
2271                     } else if (currentMove < forwardMostMove) {
2272                         ForwardInner(forwardMostMove);
2273                     }
2274                 } else {
2275                     /* I don't think this case really can happen */
2276                     SendToICS(ics_prefix);
2277                     SendToICS("refresh\n");
2278                 }
2279                 continue;
2280             }
2281
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")) {
2289                     /* Illegal move */
2290                     ics_user_moved = 0;
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]);
2296                         SwitchClocks();
2297                         DisplayBothClocks();
2298                     }
2299                     continue;
2300                 }
2301             }
2302
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;
2309                 continue;
2310             }
2311
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");
2317                 continue;
2318             }
2319
2320             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2321                 ics_clock_paused = TRUE;
2322                 StopClocks();
2323                 continue;
2324             }
2325
2326             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2327                 ics_clock_paused = FALSE;
2328                 StartClocks();
2329                 continue;
2330             }
2331
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: * (*) [*] * (*)")) {
2337                 /* star_matches:
2338                    0    player 1 name (not necessarily white)
2339                    1    player 1 rating
2340                    2    empty, white, or black (IGNORED)
2341                    3    player 2 name (not necessarily black)
2342                    4    player 2 rating
2343
2344                    The names/ratings are sorted out when the game
2345                    actually starts (below).
2346                 */
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]);
2351
2352                 if (appData.debugMode)
2353                   fprintf(debugFP,
2354                           "Ratings from 'Creating:' %s %d, %s %d\n",
2355                           player1Name, player1Rating,
2356                           player2Name, player2Rating);
2357
2358                 continue;
2359             }
2360
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;
2377
2378                 if (tkind == 0) {
2379                   whitename = star_match[1];
2380                   blackname = star_match[2];
2381                   why = star_match[3];
2382                   endtoken = star_match[4];
2383                 } else {
2384                   whitename = star_match[1];
2385                   blackname = star_match[3];
2386                   why = star_match[5];
2387                   endtoken = star_match[6];
2388                 }
2389
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);
2395 #if ZIPPY
2396                     if (appData.zippyPlay) {
2397                         ZippyGameStart(whitename, blackname);
2398                     }
2399 #endif /*ZIPPY*/
2400                     continue;
2401                 }
2402
2403                 /* Game end messages */
2404                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2405                     ics_gamenum != gamenum) {
2406                     continue;
2407                 }
2408                 while (endtoken[0] == ' ') endtoken++;
2409                 switch (endtoken[0]) {
2410                   case '*':
2411                   default:
2412                     endtype = GameUnfinished;
2413                     break;
2414                   case '0':
2415                     endtype = BlackWins;
2416                     break;
2417                   case '1':
2418                     if (endtoken[1] == '/')
2419                       endtype = GameIsDrawn;
2420                     else
2421                       endtype = WhiteWins;
2422                     break;
2423                 }
2424                 GameEnds(endtype, why, GE_ICS);
2425 #if ZIPPY
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);
2432                     }
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. */
2436                     Reset(TRUE, TRUE);
2437                 }
2438 #endif /*ZIPPY*/
2439                 continue;
2440             }
2441
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)
2447                   {
2448                           /* icsEngineAnalyze */
2449                           if (appData.icsEngineAnalyze) {
2450                              ExitAnalyzeMode();
2451                                  ModeHighlight();
2452                           }
2453                       StopClocks();
2454                       gameMode = IcsIdle;
2455                       ics_gamenum = -1;
2456                       ics_user_moved = FALSE;
2457                   }
2458                 continue;
2459             }
2460
2461             if (looking_at(buf, &i, "no longer examining game *")) {
2462                 if (gameMode == IcsExamining &&
2463                     atoi(star_match[0]) == ics_gamenum)
2464                   {
2465                       gameMode = IcsIdle;
2466                       ics_gamenum = -1;
2467                       ics_user_moved = FALSE;
2468                   }
2469                 continue;
2470             }
2471
2472             /* Advance leftover_start past any newlines we find,
2473                so only partial lines can get reparsed */
2474             if (looking_at(buf, &i, "\n")) {
2475                 prevColor = curColor;
2476                 if (curColor != ColorNormal) {
2477                     if (oldi > next_out) {
2478                         SendToPlayer(&buf[next_out], oldi - next_out);
2479                         next_out = oldi;
2480                     }
2481                     Colorize(ColorNormal, FALSE);
2482                     curColor = ColorNormal;
2483                 }
2484                 if (started == STARTED_BOARD) {
2485                     started = STARTED_NONE;
2486                     parse[parse_pos] = NULLCHAR;
2487                     ParseBoard12(parse);
2488                     ics_user_moved = 0;
2489
2490                     /* Send premove here */
2491                     if (appData.premove) {
2492                       char str[MSG_SIZ];
2493                       if (currentMove == 0 &&
2494                           gameMode == IcsPlayingWhite &&
2495                           appData.premoveWhite) {
2496                         sprintf(str, "%s%s\n", ics_prefix,
2497                                 appData.premoveWhiteText);
2498                         if (appData.debugMode)
2499                           fprintf(debugFP, "Sending premove:\n");
2500                         SendToICS(str);
2501                       } else if (currentMove == 1 &&
2502                                  gameMode == IcsPlayingBlack &&
2503                                  appData.premoveBlack) {
2504                         sprintf(str, "%s%s\n", ics_prefix,
2505                                 appData.premoveBlackText);
2506                         if (appData.debugMode)
2507                           fprintf(debugFP, "Sending premove:\n");
2508                         SendToICS(str);
2509                       } else if (gotPremove) {
2510                         gotPremove = 0;
2511                         ClearPremoveHighlights();
2512                         if (appData.debugMode)
2513                           fprintf(debugFP, "Sending premove:\n");
2514                           UserMoveEvent(premoveFromX, premoveFromY,
2515                                         premoveToX, premoveToY,
2516                                         premovePromoChar);
2517                       }
2518                     }
2519
2520                     /* Usually suppress following prompt */
2521                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2522                         if (looking_at(buf, &i, "*% ")) {
2523                             savingComment = FALSE;
2524                         }
2525                     }
2526                     next_out = i;
2527                 } else if (started == STARTED_HOLDINGS) {
2528                     int gamenum;
2529                     char new_piece[MSG_SIZ];
2530                     started = STARTED_NONE;
2531                     parse[parse_pos] = NULLCHAR;
2532                     if (appData.debugMode)
2533                       fprintf(debugFP, "Parsing holdings: %s\n", parse);
2534                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
2535                         gamenum == ics_gamenum) {
2536                         if (gameInfo.variant == VariantNormal) {
2537                           gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2538                           /* Get a move list just to see the header, which
2539                              will tell us whether this is really bug or zh */
2540                           if (ics_getting_history == H_FALSE) {
2541                             ics_getting_history = H_REQUESTED;
2542                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2543                             SendToICS(str);
2544                           }
2545                         }
2546                         new_piece[0] = NULLCHAR;
2547                         sscanf(parse, "game %d white [%s black [%s <- %s",
2548                                &gamenum, white_holding, black_holding,
2549                                new_piece);
2550                         white_holding[strlen(white_holding)-1] = NULLCHAR;
2551                         black_holding[strlen(black_holding)-1] = NULLCHAR;
2552 #if ZIPPY
2553                         if (appData.zippyPlay && first.initDone) {
2554                             ZippyHoldings(white_holding, black_holding,
2555                                           new_piece);
2556                         }
2557 #endif /*ZIPPY*/
2558                         if (tinyLayout || smallLayout) {
2559                             char wh[16], bh[16];
2560                             PackHolding(wh, white_holding);
2561                             PackHolding(bh, black_holding);
2562                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
2563                                     gameInfo.white, gameInfo.black);
2564                         } else {
2565                             sprintf(str, "%s [%s] vs. %s [%s]",
2566                                     gameInfo.white, white_holding,
2567                                     gameInfo.black, black_holding);
2568                         }
2569                         DrawPosition(FALSE, NULL);
2570                         DisplayTitle(str);
2571                     }
2572                     /* Suppress following prompt */
2573                     if (looking_at(buf, &i, "*% ")) {
2574                         savingComment = FALSE;
2575                     }
2576                     next_out = i;
2577                 }
2578                 continue;
2579             }
2580
2581             i++;                /* skip unparsed character and loop back */
2582         }
2583
2584         if (started != STARTED_MOVES && started != STARTED_BOARD &&
2585             started != STARTED_HOLDINGS && i > next_out) {
2586             SendToPlayer(&buf[next_out], i - next_out);
2587             next_out = i;
2588         }
2589
2590         leftover_len = buf_len - leftover_start;
2591         /* if buffer ends with something we couldn't parse,
2592            reparse it after appending the next read */
2593
2594     } else if (count == 0) {
2595         RemoveInputSource(isr);
2596         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
2597     } else {
2598         DisplayFatalError(_("Error reading from ICS"), error, 1);
2599     }
2600 }
2601
2602
2603 /* Board style 12 looks like this:
2604
2605    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
2606
2607  * The "<12> " is stripped before it gets to this routine.  The two
2608  * trailing 0's (flip state and clock ticking) are later addition, and
2609  * some chess servers may not have them, or may have only the first.
2610  * Additional trailing fields may be added in the future.
2611  */
2612
2613 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
2614
2615 #define RELATION_OBSERVING_PLAYED    0
2616 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
2617 #define RELATION_PLAYING_MYMOVE      1
2618 #define RELATION_PLAYING_NOTMYMOVE  -1
2619 #define RELATION_EXAMINING           2
2620 #define RELATION_ISOLATED_BOARD     -3
2621 #define RELATION_STARTING_POSITION  -4   /* FICS only */
2622
2623 void
2624 ParseBoard12(string)
2625      char *string;
2626 {
2627     GameMode newGameMode;
2628     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
2629     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
2630     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2631     char to_play, board_chars[72];
2632     char move_str[500], str[500], elapsed_time[500];
2633     char black[32], white[32];
2634     Board board;
2635     int prevMove = currentMove;
2636     int ticking = 2;
2637     ChessMove moveType;
2638     int fromX, fromY, toX, toY;
2639     char promoChar;
2640
2641     fromX = fromY = toX = toY = -1;
2642
2643     newGame = FALSE;
2644
2645     if (appData.debugMode)
2646       fprintf(debugFP, _("Parsing board: %s\n"), string);
2647
2648     move_str[0] = NULLCHAR;
2649     elapsed_time[0] = NULLCHAR;
2650     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2651                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2652                &gamenum, white, black, &relation, &basetime, &increment,
2653                &white_stren, &black_stren, &white_time, &black_time,
2654                &moveNum, str, elapsed_time, move_str, &ics_flip,
2655                &ticking);
2656
2657     if (n < 22) {
2658         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
2659         DisplayError(str, 0);
2660         return;
2661     }
2662
2663     /* Convert the move number to internal form */
2664     moveNum = (moveNum - 1) * 2;
2665     if (to_play == 'B') moveNum++;
2666     if (moveNum >= MAX_MOVES) {
2667       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
2668                         0, 1);
2669       return;
2670     }
2671
2672     switch (relation) {
2673       case RELATION_OBSERVING_PLAYED:
2674       case RELATION_OBSERVING_STATIC:
2675         if (gamenum == -1) {
2676             /* Old ICC buglet */
2677             relation = RELATION_OBSERVING_STATIC;
2678         }
2679         newGameMode = IcsObserving;
2680         break;
2681       case RELATION_PLAYING_MYMOVE:
2682       case RELATION_PLAYING_NOTMYMOVE:
2683         newGameMode =
2684           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2685             IcsPlayingWhite : IcsPlayingBlack;
2686         break;
2687       case RELATION_EXAMINING:
2688         newGameMode = IcsExamining;
2689         break;
2690       case RELATION_ISOLATED_BOARD:
2691       default:
2692         /* Just display this board.  If user was doing something else,
2693            we will forget about it until the next board comes. */
2694         newGameMode = IcsIdle;
2695         break;
2696       case RELATION_STARTING_POSITION:
2697         newGameMode = gameMode;
2698         break;
2699     }
2700
2701     /* Modify behavior for initial board display on move listing
2702        of wild games.
2703        */
2704     switch (ics_getting_history) {
2705       case H_FALSE:
2706       case H_REQUESTED:
2707         break;
2708       case H_GOT_REQ_HEADER:
2709       case H_GOT_UNREQ_HEADER:
2710         /* This is the initial position of the current game */
2711         gamenum = ics_gamenum;
2712         moveNum = 0;            /* old ICS bug workaround */
2713         if (to_play == 'B') {
2714           startedFromSetupPosition = TRUE;
2715           blackPlaysFirst = TRUE;
2716           moveNum = 1;
2717           if (forwardMostMove == 0) forwardMostMove = 1;
2718           if (backwardMostMove == 0) backwardMostMove = 1;
2719           if (currentMove == 0) currentMove = 1;
2720         }
2721         newGameMode = gameMode;
2722         relation = RELATION_STARTING_POSITION; /* ICC needs this */
2723         break;
2724       case H_GOT_UNWANTED_HEADER:
2725         /* This is an initial board that we don't want */
2726         return;
2727       case H_GETTING_MOVES:
2728         /* Should not happen */
2729         DisplayError(_("Error gathering move list: extra board"), 0);
2730         ics_getting_history = H_FALSE;
2731         return;
2732     }
2733
2734     /* Take action if this is the first board of a new game, or of a
2735        different game than is currently being displayed.  */
2736     if (gamenum != ics_gamenum || newGameMode != gameMode ||
2737         relation == RELATION_ISOLATED_BOARD) {
2738
2739         /* Forget the old game and get the history (if any) of the new one */
2740         if (gameMode != BeginningOfGame) {
2741        Reset(FALSE, TRUE);
2742         }
2743         newGame = TRUE;
2744         if (appData.autoRaiseBoard) BoardToTop();
2745         prevMove = -3;
2746         if (gamenum == -1) {
2747             newGameMode = IcsIdle;
2748         } else if (moveNum > 0 && newGameMode != IcsIdle &&
2749                    appData.getMoveList) {
2750             /* Need to get game history */
2751             ics_getting_history = H_REQUESTED;
2752             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2753             SendToICS(str);
2754         }
2755
2756         /* Initially flip the board to have black on the bottom if playing
2757            black or if the ICS flip flag is set, but let the user change
2758            it with the Flip View button. */
2759         flipView = appData.autoFlipView ?
2760           (newGameMode == IcsPlayingBlack) || ics_flip :
2761           appData.flipView;
2762
2763         /* Done with values from previous mode; copy in new ones */
2764         gameMode = newGameMode;
2765         ModeHighlight();
2766         ics_gamenum = gamenum;
2767         if (gamenum == gs_gamenum) {
2768             int klen = strlen(gs_kind);
2769             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2770             sprintf(str, "ICS %s", gs_kind);
2771             gameInfo.event = StrSave(str);
2772         } else {
2773             gameInfo.event = StrSave("ICS game");
2774         }
2775         gameInfo.site = StrSave(appData.icsHost);
2776         gameInfo.date = PGNDate();
2777         gameInfo.round = StrSave("-");
2778         gameInfo.white = StrSave(white);
2779         gameInfo.black = StrSave(black);
2780         timeControl = basetime * 60 * 1000;
2781         timeIncrement = increment * 1000;
2782         movesPerSession = 0;
2783         gameInfo.timeControl = TimeControlTagValue();
2784         gameInfo.variant = StringToVariant(gameInfo.event);
2785
2786         /* Do we have the ratings? */
2787         if (strcmp(player1Name, white) == 0 &&
2788             strcmp(player2Name, black) == 0) {
2789             if (appData.debugMode)
2790               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2791                       player1Rating, player2Rating);
2792             gameInfo.whiteRating = player1Rating;
2793             gameInfo.blackRating = player2Rating;
2794         } else if (strcmp(player2Name, white) == 0 &&
2795                    strcmp(player1Name, black) == 0) {
2796             if (appData.debugMode)
2797               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2798                       player2Rating, player1Rating);
2799             gameInfo.whiteRating = player2Rating;
2800             gameInfo.blackRating = player1Rating;
2801         }
2802         player1Name[0] = player2Name[0] = NULLCHAR;
2803
2804         /* Silence shouts if requested */
2805         if (appData.quietPlay &&
2806             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2807             SendToICS(ics_prefix);
2808             SendToICS("set shout 0\n");
2809         }
2810     }
2811
2812     /* Deal with midgame name changes */
2813     if (!newGame) {
2814         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2815             if (gameInfo.white) free(gameInfo.white);
2816             gameInfo.white = StrSave(white);
2817         }
2818         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2819             if (gameInfo.black) free(gameInfo.black);
2820             gameInfo.black = StrSave(black);
2821         }
2822     }
2823
2824     /* Throw away game result if anything actually changes in examine mode */
2825     if (gameMode == IcsExamining && !newGame) {
2826         gameInfo.result = GameUnfinished;
2827         if (gameInfo.resultDetails != NULL) {
2828             free(gameInfo.resultDetails);
2829             gameInfo.resultDetails = NULL;
2830         }
2831     }
2832
2833     /* In pausing && IcsExamining mode, we ignore boards coming
2834        in if they are in a different variation than we are. */
2835     if (pauseExamInvalid) return;
2836     if (pausing && gameMode == IcsExamining) {
2837         if (moveNum <= pauseExamForwardMostMove) {
2838             pauseExamInvalid = TRUE;
2839             forwardMostMove = pauseExamForwardMostMove;
2840             return;
2841         }
2842     }
2843
2844     /* Parse the board */
2845     for (k = 0; k < 8; k++)
2846       for (j = 0; j < 8; j++)
2847         board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2848     CopyBoard(boards[moveNum], board);
2849     if (moveNum == 0) {
2850         startedFromSetupPosition =
2851           !CompareBoards(board, initialPosition);
2852     }
2853
2854     if (ics_getting_history == H_GOT_REQ_HEADER ||
2855         ics_getting_history == H_GOT_UNREQ_HEADER) {
2856         /* This was an initial position from a move list, not
2857            the current position */
2858         return;
2859     }
2860
2861     /* Update currentMove and known move number limits */
2862     newMove = newGame || moveNum > forwardMostMove;
2863
2864         /* If we found takebacks during icsEngineAnalyze 
2865            try send to engine */
2866         if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
2867                 takeback = forwardMostMove - moveNum;
2868                 for (i = 0; i < takeback; i++) {
2869                     if (appData.debugMode) fprintf(debugFP, "take back move\n");
2870                     SendToProgram("undo\n", &first);
2871                  }
2872         }
2873     if (newGame) {
2874         forwardMostMove = backwardMostMove = currentMove = moveNum;
2875         if (gameMode == IcsExamining && moveNum == 0) {
2876           /* Workaround for ICS limitation: we are not told the wild
2877              type when starting to examine a game.  But if we ask for
2878              the move list, the move list header will tell us */
2879             ics_getting_history = H_REQUESTED;
2880             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2881             SendToICS(str);
2882         }
2883     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2884                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2885         forwardMostMove = moveNum;
2886         if (!pausing || currentMove > forwardMostMove)
2887           currentMove = forwardMostMove;
2888     } else {
2889         /* New part of history that is not contiguous with old part */
2890         if (pausing && gameMode == IcsExamining) {
2891             pauseExamInvalid = TRUE;
2892             forwardMostMove = pauseExamForwardMostMove;
2893             return;
2894         }
2895         forwardMostMove = backwardMostMove = currentMove = moveNum;
2896         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
2897             ics_getting_history = H_REQUESTED;
2898             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2899             SendToICS(str);
2900         }
2901     }
2902
2903     /* Update the clocks */
2904     if (strchr(elapsed_time, '.')) {
2905       /* Time is in ms */
2906       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
2907       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
2908     } else {
2909       /* Time is in seconds */
2910       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
2911       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
2912     }
2913
2914
2915 #if ZIPPY
2916     if (appData.zippyPlay && newGame &&
2917         gameMode != IcsObserving && gameMode != IcsIdle &&
2918         gameMode != IcsExamining)
2919       ZippyFirstBoard(moveNum, basetime, increment);
2920 #endif
2921
2922     /* Put the move on the move list, first converting
2923        to canonical algebraic form. */
2924     if (moveNum > 0) {
2925         if (moveNum <= backwardMostMove) {
2926             /* We don't know what the board looked like before
2927                this move.  Punt. */
2928             strcpy(parseList[moveNum - 1], move_str);
2929             strcat(parseList[moveNum - 1], " ");
2930             strcat(parseList[moveNum - 1], elapsed_time);
2931             moveList[moveNum - 1][0] = NULLCHAR;
2932         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
2933                                 &fromX, &fromY, &toX, &toY, &promoChar)) {
2934             (void) CoordsToAlgebraic(boards[moveNum - 1],
2935                                      PosFlags(moveNum - 1), EP_UNKNOWN,
2936                                      fromY, fromX, toY, toX, promoChar,
2937                                      parseList[moveNum-1]);
2938             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
2939               case MT_NONE:
2940               case MT_STALEMATE:
2941               default:
2942                 break;
2943               case MT_CHECK:
2944                 strcat(parseList[moveNum - 1], "+");
2945                 break;
2946               case MT_CHECKMATE:
2947                 strcat(parseList[moveNum - 1], "#");
2948                 break;
2949             }
2950             strcat(parseList[moveNum - 1], " ");
2951             strcat(parseList[moveNum - 1], elapsed_time);
2952             /* currentMoveString is set as a side-effect of ParseOneMove */
2953             strcpy(moveList[moveNum - 1], currentMoveString);
2954             strcat(moveList[moveNum - 1], "\n");
2955         } else if (strcmp(move_str, "none") == 0) {
2956             /* Again, we don't know what the board looked like;
2957                this is really the start of the game. */
2958             parseList[moveNum - 1][0] = NULLCHAR;
2959             moveList[moveNum - 1][0] = NULLCHAR;
2960             backwardMostMove = moveNum;
2961             startedFromSetupPosition = TRUE;
2962             fromX = fromY = toX = toY = -1;
2963         } else {
2964             /* Move from ICS was illegal!?  Punt. */
2965 #if 0
2966             if (appData.testLegality && appData.debugMode) {
2967                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
2968                 DisplayError(str, 0);
2969             }
2970 #endif
2971             strcpy(parseList[moveNum - 1], move_str);
2972             strcat(parseList[moveNum - 1], " ");
2973             strcat(parseList[moveNum - 1], elapsed_time);
2974             moveList[moveNum - 1][0] = NULLCHAR;
2975             fromX = fromY = toX = toY = -1;
2976         }
2977
2978 #if ZIPPY
2979         /* Send move to chess program (BEFORE animating it). */
2980         if (appData.zippyPlay && !newGame && newMove &&
2981            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
2982
2983             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
2984                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
2985                 if (moveList[moveNum - 1][0] == NULLCHAR) {
2986                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
2987                             move_str);
2988                     DisplayError(str, 0);
2989                 } else {
2990                     if (first.sendTime) {
2991                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
2992                     }
2993                     SendMoveToProgram(moveNum - 1, &first);
2994                     if (firstMove) {
2995                         firstMove = FALSE;
2996                         if (first.useColors) {
2997                           SendToProgram(gameMode == IcsPlayingWhite ?
2998                                         "white\ngo\n" :
2999                                         "black\ngo\n", &first);
3000                         } else {
3001                           SendToProgram("go\n", &first);
3002                         }
3003                         first.maybeThinking = TRUE;
3004                     }
3005                 }
3006             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3007               if (moveList[moveNum - 1][0] == NULLCHAR) {
3008                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3009                 DisplayError(str, 0);
3010               } else {
3011                 SendMoveToProgram(moveNum - 1, &first);
3012               }
3013             }
3014         }
3015 #endif
3016     }
3017
3018     if (moveNum > 0 && !gotPremove) {
3019         /* If move comes from a remote source, animate it.  If it
3020            isn't remote, it will have already been animated. */
3021         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3022             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3023         }
3024         if (!pausing && appData.highlightLastMove) {
3025             SetHighlights(fromX, fromY, toX, toY);
3026         }
3027     }
3028
3029     /* Start the clocks */
3030     whiteFlag = blackFlag = FALSE;
3031     appData.clockMode = !(basetime == 0 && increment == 0);
3032     if (ticking == 0) {
3033       ics_clock_paused = TRUE;
3034       StopClocks();
3035     } else if (ticking == 1) {
3036       ics_clock_paused = FALSE;
3037     }
3038     if (gameMode == IcsIdle ||
3039         relation == RELATION_OBSERVING_STATIC ||
3040         relation == RELATION_EXAMINING ||
3041         ics_clock_paused)
3042       DisplayBothClocks();
3043     else
3044       StartClocks();
3045
3046     /* Display opponents and material strengths */
3047     if (gameInfo.variant != VariantBughouse &&
3048         gameInfo.variant != VariantCrazyhouse) {
3049         if (tinyLayout || smallLayout) {
3050             sprintf(str, "%s(%d) %s(%d) {%d %d}",
3051                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3052                     basetime, increment);
3053         } else {
3054             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3055                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3056                     basetime, increment);
3057         }
3058         DisplayTitle(str);
3059     }
3060
3061
3062     /* Display the board */
3063     if (!pausing) {
3064
3065       if (appData.premove)
3066           if (!gotPremove ||
3067              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3068              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3069               ClearPremoveHighlights();
3070
3071       DrawPosition(FALSE, boards[currentMove]);
3072       DisplayMove(moveNum - 1);
3073       if (appData.ringBellAfterMoves && !ics_user_moved)
3074         RingBell();
3075     }
3076
3077     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3078 }
3079
3080 void
3081 GetMoveListEvent()
3082 {
3083     char buf[MSG_SIZ];
3084     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3085         ics_getting_history = H_REQUESTED;
3086         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3087         SendToICS(buf);
3088     }
3089 }
3090
3091 void
3092 AnalysisPeriodicEvent(force)
3093      int force;
3094 {
3095     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3096          && !force) || !appData.periodicUpdates)
3097       return;
3098
3099     /* Send . command to Crafty to collect stats */
3100     SendToProgram(".\n", &first);
3101
3102     /* Don't send another until we get a response (this makes
3103        us stop sending to old Crafty's which don't understand
3104        the "." command (sending illegal cmds resets node count & time,
3105        which looks bad)) */
3106     programStats.ok_to_send = 0;
3107 }
3108
3109 void
3110 SendMoveToProgram(moveNum, cps)
3111      int moveNum;
3112      ChessProgramState *cps;
3113 {
3114     char buf[MSG_SIZ];
3115     if (cps->useUsermove) {
3116       SendToProgram("usermove ", cps);
3117     }
3118     if (cps->useSAN) {
3119       char *space;
3120       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3121         int len = space - parseList[moveNum];
3122         memcpy(buf, parseList[moveNum], len);
3123         buf[len++] = '\n';
3124         buf[len] = NULLCHAR;
3125       } else {
3126         sprintf(buf, "%s\n", parseList[moveNum]);
3127       }
3128       SendToProgram(buf, cps);
3129     } else {
3130       SendToProgram(moveList[moveNum], cps);
3131     }
3132 }
3133
3134 void
3135 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3136      ChessMove moveType;
3137      int fromX, fromY, toX, toY;
3138 {
3139     char user_move[MSG_SIZ];
3140
3141     switch (moveType) {
3142       default:
3143         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3144                 (int)moveType, fromX, fromY, toX, toY);
3145         DisplayError(user_move + strlen("say "), 0);
3146         break;
3147       case WhiteKingSideCastle:
3148       case BlackKingSideCastle:
3149       case WhiteQueenSideCastleWild:
3150       case BlackQueenSideCastleWild:
3151         sprintf(user_move, "o-o\n");
3152         break;
3153       case WhiteQueenSideCastle:
3154       case BlackQueenSideCastle:
3155       case WhiteKingSideCastleWild:
3156       case BlackKingSideCastleWild:
3157         sprintf(user_move, "o-o-o\n");
3158         break;
3159       case WhitePromotionQueen:
3160       case BlackPromotionQueen:
3161       case WhitePromotionRook:
3162       case BlackPromotionRook:
3163       case WhitePromotionBishop:
3164       case BlackPromotionBishop:
3165       case WhitePromotionKnight:
3166       case BlackPromotionKnight:
3167       case WhitePromotionKing:
3168       case BlackPromotionKing:
3169         sprintf(user_move, "%c%c%c%c=%c\n",
3170                 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3171                 PieceToChar(PromoPiece(moveType)));
3172         break;
3173       case WhiteDrop:
3174       case BlackDrop:
3175         sprintf(user_move, "%c@%c%c\n",
3176                 ToUpper(PieceToChar((ChessSquare) fromX)),
3177                 'a' + toX, '1' + toY);
3178         break;
3179       case NormalMove:
3180       case WhiteCapturesEnPassant:
3181       case BlackCapturesEnPassant:
3182       case IllegalMove:  /* could be a variant we don't quite understand */
3183         sprintf(user_move, "%c%c%c%c\n",
3184                 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3185         break;
3186     }
3187     SendToICS(user_move);
3188 }
3189
3190 void
3191 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3192      int rf, ff, rt, ft;
3193      char promoChar;
3194      char move[7];
3195 {
3196     if (rf == DROP_RANK) {
3197         sprintf(move, "%c@%c%c\n",
3198                 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3199     } else {
3200         if (promoChar == 'x' || promoChar == NULLCHAR) {
3201             sprintf(move, "%c%c%c%c\n",
3202                     'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3203         } else {
3204             sprintf(move, "%c%c%c%c%c\n",
3205                     'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3206         }
3207     }
3208 }
3209
3210 void
3211 ProcessICSInitScript(f)
3212      FILE *f;
3213 {
3214     char buf[MSG_SIZ];
3215
3216     while (fgets(buf, MSG_SIZ, f)) {
3217         SendToICSDelayed(buf,(long)appData.msLoginDelay);
3218     }
3219
3220     fclose(f);
3221 }
3222
3223
3224 /* Parser for moves from gnuchess, ICS, or user typein box */
3225 Boolean
3226 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3227      char *move;
3228      int moveNum;
3229      ChessMove *moveType;
3230      int *fromX, *fromY, *toX, *toY;
3231      char *promoChar;
3232 {
3233     *moveType = yylexstr(moveNum, move);
3234     switch (*moveType) {
3235       case WhitePromotionQueen:
3236       case BlackPromotionQueen:
3237       case WhitePromotionRook:
3238       case BlackPromotionRook:
3239       case WhitePromotionBishop:
3240       case BlackPromotionBishop:
3241       case WhitePromotionKnight:
3242       case BlackPromotionKnight:
3243       case WhitePromotionKing:
3244       case BlackPromotionKing:
3245       case NormalMove:
3246       case WhiteCapturesEnPassant:
3247       case BlackCapturesEnPassant:
3248       case WhiteKingSideCastle:
3249       case WhiteQueenSideCastle:
3250       case BlackKingSideCastle:
3251       case BlackQueenSideCastle:
3252       case WhiteKingSideCastleWild:
3253       case WhiteQueenSideCastleWild:
3254       case BlackKingSideCastleWild:
3255       case BlackQueenSideCastleWild:
3256       case IllegalMove:         /* bug or odd chess variant */
3257         *fromX = currentMoveString[0] - 'a';
3258         *fromY = currentMoveString[1] - '1';
3259         *toX = currentMoveString[2] - 'a';
3260         *toY = currentMoveString[3] - '1';
3261         *promoChar = currentMoveString[4];
3262         if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3263             *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3264             *fromX = *fromY = *toX = *toY = 0;
3265             return FALSE;
3266         }
3267         if (appData.testLegality) {
3268           return (*moveType != IllegalMove);
3269         } else {
3270           return !(fromX == fromY && toX == toY);
3271         }
3272
3273       case WhiteDrop:
3274       case BlackDrop:
3275         *fromX = *moveType == WhiteDrop ?
3276           (int) CharToPiece(ToUpper(currentMoveString[0])) :
3277         (int) CharToPiece(ToLower(currentMoveString[0]));
3278         *fromY = DROP_RANK;
3279         *toX = currentMoveString[2] - 'a';
3280         *toY = currentMoveString[3] - '1';
3281         *promoChar = NULLCHAR;
3282         return TRUE;
3283
3284       case AmbiguousMove:
3285       case ImpossibleMove:
3286       case (ChessMove) 0:       /* end of file */
3287       case ElapsedTime:
3288       case Comment:
3289       case PGNTag:
3290       case NAG:
3291       case WhiteWins:
3292       case BlackWins:
3293       case GameIsDrawn:
3294       default:
3295         /* bug? */
3296         *fromX = *fromY = *toX = *toY = 0;
3297         *promoChar = NULLCHAR;
3298         return FALSE;
3299     }
3300 }
3301
3302
3303 void
3304 InitPosition(redraw)
3305      int redraw;
3306 {
3307     currentMove = forwardMostMove = backwardMostMove = 0;
3308     switch (gameInfo.variant) {
3309     default:
3310       CopyBoard(boards[0], initialPosition);
3311       break;
3312     case VariantTwoKings:
3313       CopyBoard(boards[0], twoKingsPosition);
3314       startedFromSetupPosition = TRUE;
3315       break;
3316     case VariantWildCastle:
3317       CopyBoard(boards[0], initialPosition);
3318       /* !!?shuffle with kings guaranteed to be on d or e file */
3319       break;
3320     case VariantNoCastle:
3321       CopyBoard(boards[0], initialPosition);
3322       /* !!?unconstrained back-rank shuffle */
3323       break;
3324     case VariantFischeRandom:
3325       CopyBoard(boards[0], initialPosition);
3326       /* !!shuffle according to FR rules */
3327       break;
3328     }
3329     if (redraw)
3330       DrawPosition(FALSE, boards[currentMove]);
3331 }
3332
3333 void
3334 SendBoard(cps, moveNum)
3335      ChessProgramState *cps;
3336      int moveNum;
3337 {
3338     char message[MSG_SIZ];
3339
3340     if (cps->useSetboard) {
3341       char* fen = PositionToFEN(moveNum);
3342       sprintf(message, "setboard %s\n", fen);
3343       SendToProgram(message, cps);
3344       free(fen);
3345
3346     } else {
3347       ChessSquare *bp;
3348       int i, j;
3349       /* Kludge to set black to move, avoiding the troublesome and now
3350        * deprecated "black" command.
3351        */
3352       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3353
3354       SendToProgram("edit\n", cps);
3355       SendToProgram("#\n", cps);
3356       for (i = BOARD_SIZE - 1; i >= 0; i--) {
3357         bp = &boards[moveNum][i][0];
3358         for (j = 0; j < BOARD_SIZE; j++, bp++) {
3359           if ((int) *bp < (int) BlackPawn) {
3360             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3361                     'a' + j, '1' + i);
3362             SendToProgram(message, cps);
3363           }
3364         }
3365       }
3366
3367       SendToProgram("c\n", cps);
3368       for (i = BOARD_SIZE - 1; i >= 0; i--) {
3369         bp = &boards[moveNum][i][0];
3370         for (j = 0; j < BOARD_SIZE; j++, bp++) {
3371           if (((int) *bp != (int) EmptySquare)
3372               && ((int) *bp >= (int) BlackPawn)) {
3373             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3374                     'a' + j, '1' + i);
3375             SendToProgram(message, cps);
3376           }
3377         }
3378       }
3379
3380       SendToProgram(".\n", cps);
3381     }
3382 }
3383
3384 int
3385 IsPromotion(fromX, fromY, toX, toY)
3386      int fromX, fromY, toX, toY;
3387 {
3388     return gameMode != EditPosition &&
3389       fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3390         ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3391          (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3392 }
3393
3394
3395 int
3396 PieceForSquare (x, y)
3397      int x;
3398      int y;
3399 {
3400   if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3401      return -1;
3402   else
3403      return boards[currentMove][y][x];
3404 }
3405
3406 int
3407 OKToStartUserMove(x, y)
3408      int x, y;
3409 {
3410     ChessSquare from_piece;
3411     int white_piece;
3412
3413     if (matchMode) return FALSE;
3414     if (gameMode == EditPosition) return TRUE;
3415
3416     if (x >= 0 && y >= 0)
3417       from_piece = boards[currentMove][y][x];
3418     else
3419       from_piece = EmptySquare;
3420
3421     if (from_piece == EmptySquare) return FALSE;
3422
3423     white_piece = (int)from_piece >= (int)WhitePawn &&
3424       (int)from_piece <= (int)WhiteKing;
3425
3426     switch (gameMode) {
3427       case PlayFromGameFile:
3428       case AnalyzeFile:
3429       case TwoMachinesPlay:
3430       case EndOfGame:
3431         return FALSE;
3432
3433       case IcsObserving:
3434       case IcsIdle:
3435         return FALSE;
3436
3437       case MachinePlaysWhite:
3438       case IcsPlayingBlack:
3439         if (appData.zippyPlay) return FALSE;
3440         if (white_piece) {
3441             DisplayMoveError(_("You are playing Black"));
3442             return FALSE;
3443         }
3444         break;
3445
3446       case MachinePlaysBlack:
3447       case IcsPlayingWhite:
3448         if (appData.zippyPlay) return FALSE;
3449         if (!white_piece) {
3450             DisplayMoveError(_("You are playing White"));
3451             return FALSE;
3452         }
3453         break;
3454
3455       case EditGame:
3456         if (!white_piece && WhiteOnMove(currentMove)) {
3457             DisplayMoveError(_("It is White's turn"));
3458             return FALSE;
3459         }
3460         if (white_piece && !WhiteOnMove(currentMove)) {
3461             DisplayMoveError(_("It is Black's turn"));
3462             return FALSE;
3463         }
3464         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3465             /* Editing correspondence game history */
3466             /* Could disallow this or prompt for confirmation */
3467             cmailOldMove = -1;
3468         }
3469         if (currentMove < forwardMostMove) {
3470             /* Discarding moves */
3471             /* Could prompt for confirmation here,
3472                but I don't think that's such a good idea */
3473             forwardMostMove = currentMove;
3474         }
3475         break;
3476
3477       case BeginningOfGame:
3478         if (appData.icsActive) return FALSE;
3479         if (!appData.noChessProgram) {
3480             if (!white_piece) {
3481                 DisplayMoveError(_("You are playing White"));
3482                 return FALSE;
3483             }
3484         }
3485         break;
3486
3487       case Training:
3488         if (!white_piece && WhiteOnMove(currentMove)) {
3489             DisplayMoveError(_("It is White's turn"));
3490             return FALSE;
3491         }
3492         if (white_piece && !WhiteOnMove(currentMove)) {
3493             DisplayMoveError(_("It is Black's turn"));
3494             return FALSE;
3495         }
3496         break;
3497
3498       default:
3499       case IcsExamining:
3500         break;
3501     }
3502     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3503         && gameMode != AnalyzeFile && gameMode != Training) {
3504         DisplayMoveError(_("Displayed position is not current"));
3505         return FALSE;
3506     }
3507     return TRUE;
3508 }
3509
3510 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3511 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3512 int lastLoadGameUseList = FALSE;
3513 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3514 ChessMove lastLoadGameStart = (ChessMove) 0;
3515
3516
3517 void
3518 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3519      int fromX, fromY, toX, toY;
3520      int promoChar;
3521 {
3522     ChessMove moveType;
3523
3524     if (fromX < 0 || fromY < 0) return;
3525     if ((fromX == toX) && (fromY == toY)) {
3526         return;
3527     }
3528
3529     /* Check if the user is playing in turn.  This is complicated because we
3530        let the user "pick up" a piece before it is his turn.  So the piece he
3531        tried to pick up may have been captured by the time he puts it down!
3532        Therefore we use the color the user is supposed to be playing in this
3533        test, not the color of the piece that is currently on the starting
3534        square---except in EditGame mode, where the user is playing both
3535        sides; fortunately there the capture race can't happen.  (It can
3536        now happen in IcsExamining mode, but that's just too bad.  The user
3537        will get a somewhat confusing message in that case.)
3538        */
3539
3540     switch (gameMode) {
3541       case PlayFromGameFile:
3542       case AnalyzeFile:
3543       case TwoMachinesPlay:
3544       case EndOfGame:
3545       case IcsObserving:
3546       case IcsIdle:
3547         /* We switched into a game mode where moves are not accepted,
3548            perhaps while the mouse button was down. */
3549         return;
3550
3551       case MachinePlaysWhite:
3552         /* User is moving for Black */
3553         if (WhiteOnMove(currentMove)) {
3554             DisplayMoveError(_("It is White's turn"));
3555             return;
3556         }
3557         break;
3558
3559       case MachinePlaysBlack:
3560         /* User is moving for White */
3561         if (!WhiteOnMove(currentMove)) {
3562             DisplayMoveError(_("It is Black's turn"));
3563             return;
3564         }
3565         break;
3566
3567       case EditGame:
3568       case IcsExamining:
3569       case BeginningOfGame:
3570       case AnalyzeMode:
3571       case Training:
3572         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3573             (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3574             /* User is moving for Black */
3575             if (WhiteOnMove(currentMove)) {
3576                 DisplayMoveError(_("It is White's turn"));
3577                 return;
3578             }
3579         } else {
3580             /* User is moving for White */
3581             if (!WhiteOnMove(currentMove)) {
3582                 DisplayMoveError(_("It is Black's turn"));
3583                 return;
3584             }
3585         }
3586         break;
3587
3588       case IcsPlayingBlack:
3589         /* User is moving for Black */
3590         if (WhiteOnMove(currentMove)) {
3591             if (!appData.premove) {
3592                 DisplayMoveError(_("It is White's turn"));
3593             } else if (toX >= 0 && toY >= 0) {
3594                 premoveToX = toX;
3595                 premoveToY = toY;
3596                 premoveFromX = fromX;
3597                 premoveFromY = fromY;
3598                 premovePromoChar = promoChar;
3599                 gotPremove = 1;
3600                 if (appData.debugMode)
3601                     fprintf(debugFP, "Got premove: fromX %d,"
3602                             "fromY %d, toX %d, toY %d\n",
3603                             fromX, fromY, toX, toY);
3604             }
3605             return;
3606         }
3607         break;
3608
3609       case IcsPlayingWhite:
3610         /* User is moving for White */
3611         if (!WhiteOnMove(currentMove)) {
3612             if (!appData.premove) {
3613                 DisplayMoveError(_("It is Black's turn"));
3614             } else if (toX >= 0 && toY >= 0) {
3615                 premoveToX = toX;
3616                 premoveToY = toY;
3617                 premoveFromX = fromX;
3618                 premoveFromY = fromY;
3619                 premovePromoChar = promoChar;
3620                 gotPremove = 1;
3621                 if (appData.debugMode)
3622                     fprintf(debugFP, "Got premove: fromX %d,"
3623                             "fromY %d, toX %d, toY %d\n",
3624                             fromX, fromY, toX, toY);
3625             }
3626             return;
3627         }
3628         break;
3629
3630       default:
3631         break;
3632
3633       case EditPosition:
3634         if (toX == -2 || toY == -2) {
3635             boards[0][fromY][fromX] = EmptySquare;
3636             DrawPosition(FALSE, boards[currentMove]);
3637         } else if (toX >= 0 && toY >= 0) {
3638             boards[0][toY][toX] = boards[0][fromY][fromX];
3639             boards[0][fromY][fromX] = EmptySquare;
3640             DrawPosition(FALSE, boards[currentMove]);
3641         }
3642         return;
3643     }
3644
3645     if (toX < 0 || toY < 0) return;
3646     userOfferedDraw = FALSE;
3647
3648     if (appData.testLegality) {
3649         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3650                                 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3651         if (moveType == IllegalMove || moveType == ImpossibleMove) {
3652             DisplayMoveError(_("Illegal move"));
3653             return;
3654         }
3655     } else {
3656         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3657     }
3658
3659     if (gameMode == Training) {
3660       /* compare the move played on the board to the next move in the
3661        * game. If they match, display the move and the opponent's response.
3662        * If they don't match, display an error message.
3663        */
3664       int saveAnimate;
3665       Board testBoard;
3666       CopyBoard(testBoard, boards[currentMove]);
3667       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3668
3669       if (CompareBoards(testBoard, boards[currentMove+1])) {
3670         ForwardInner(currentMove+1);
3671
3672         /* Autoplay the opponent's response.
3673          * if appData.animate was TRUE when Training mode was entered,
3674          * the response will be animated.
3675          */
3676         saveAnimate = appData.animate;
3677         appData.animate = animateTraining;
3678         ForwardInner(currentMove+1);
3679         appData.animate = saveAnimate;
3680
3681         /* check for the end of the game */
3682         if (currentMove >= forwardMostMove) {
3683           gameMode = PlayFromGameFile;
3684           ModeHighlight();
3685           SetTrainingModeOff();
3686           DisplayInformation(_("End of game"));
3687         }
3688       } else {
3689         DisplayError(_("Incorrect move"), 0);
3690       }
3691       return;
3692     }
3693
3694     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
3695 }
3696
3697 /* Common tail of UserMoveEvent and DropMenuEvent */
3698 void
3699 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
3700      ChessMove moveType;
3701      int fromX, fromY, toX, toY;
3702      /*char*/int promoChar;
3703 {
3704   /* Ok, now we know that the move is good, so we can kill
3705      the previous line in Analysis Mode */
3706   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
3707     forwardMostMove = currentMove;
3708   }
3709
3710   /* If we need the chess program but it's dead, restart it */
3711   ResurrectChessProgram();
3712
3713   /* A user move restarts a paused game*/
3714   if (pausing)
3715     PauseEvent();
3716
3717   thinkOutput[0] = NULLCHAR;
3718
3719   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
3720
3721   if (gameMode == BeginningOfGame) {
3722     if (appData.noChessProgram) {
3723       gameMode = EditGame;
3724       SetGameInfo();
3725     } else {
3726       char buf[MSG_SIZ];
3727       gameMode = MachinePlaysBlack;
3728       SetGameInfo();
3729       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
3730       DisplayTitle(buf);
3731       if (first.sendName) {
3732         sprintf(buf, "name %s\n", gameInfo.white);
3733         SendToProgram(buf, &first);
3734       }
3735     }
3736     ModeHighlight();
3737   }
3738
3739   /* Relay move to ICS or chess engine */
3740   if (appData.icsActive) {
3741     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
3742         gameMode == IcsExamining) {
3743       SendMoveToICS(moveType, fromX, fromY, toX, toY);
3744       ics_user_moved = 1;
3745     }
3746   } else {
3747     if (first.sendTime && (gameMode == BeginningOfGame ||
3748                            gameMode == MachinePlaysWhite ||
3749                            gameMode == MachinePlaysBlack)) {
3750       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
3751     }
3752     SendMoveToProgram(forwardMostMove-1, &first);
3753     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
3754       first.maybeThinking = TRUE;
3755     }
3756     if (currentMove == cmailOldMove + 1) {
3757       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
3758     }
3759   }
3760
3761   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3762
3763   switch (gameMode) {
3764   case EditGame:
3765     switch (MateTest(boards[currentMove], PosFlags(currentMove),
3766                      EP_UNKNOWN)) {
3767     case MT_NONE:
3768     case MT_CHECK:
3769       break;
3770     case MT_CHECKMATE:
3771       if (WhiteOnMove(currentMove)) {
3772         GameEnds(BlackWins, "Black mates", GE_PLAYER);
3773       } else {
3774         GameEnds(WhiteWins, "White mates", GE_PLAYER);
3775       }
3776       break;
3777     case MT_STALEMATE:
3778       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
3779       break;
3780     }
3781     break;
3782
3783   case MachinePlaysBlack:
3784   case MachinePlaysWhite:
3785     /* disable certain menu options while machine is thinking */
3786     SetMachineThinkingEnables();
3787     break;
3788
3789   default:
3790     break;
3791   }
3792 }
3793
3794 void
3795 HandleMachineMove(message, cps)
3796      char *message;
3797      ChessProgramState *cps;
3798 {
3799     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
3800     char realname[MSG_SIZ];
3801     int fromX, fromY, toX, toY;
3802     ChessMove moveType;
3803     char promoChar;
3804     char *p;
3805     int machineWhite;
3806
3807     /*
3808      * Kludge to ignore BEL characters
3809      */
3810     while (*message == '\007') message++;
3811
3812     /*
3813      * Look for book output
3814      */
3815     if (cps == &first && bookRequested) {
3816         if (message[0] == '\t' || message[0] == ' ') {
3817             /* Part of the book output is here; append it */
3818             strcat(bookOutput, message);
3819             strcat(bookOutput, "  \n");
3820             return;
3821         } else if (bookOutput[0] != NULLCHAR) {
3822             /* All of book output has arrived; display it */
3823             char *p = bookOutput;
3824             while (*p != NULLCHAR) {
3825                 if (*p == '\t') *p = ' ';
3826                 p++;
3827             }
3828             DisplayInformation(bookOutput);
3829             bookRequested = FALSE;
3830             /* Fall through to parse the current output */
3831         }
3832     }
3833
3834     /*
3835      * Look for machine move.
3836      */
3837     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 &&
3838          strcmp(buf2, "...") == 0) ||
3839         (sscanf(message, "%s %s", buf1, machineMove) == 2 &&
3840          strcmp(buf1, "move") == 0)) {
3841
3842         /* This method is only useful on engines that support ping */
3843         if (cps->lastPing != cps->lastPong) {
3844           if (gameMode == BeginningOfGame) {
3845             /* Extra move from before last new; ignore */
3846             if (appData.debugMode) {
3847                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3848             }
3849           } else {
3850             if (appData.debugMode) {
3851                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3852                         cps->which, gameMode);
3853             }
3854             SendToProgram("undo\n", cps);
3855           }
3856           return;
3857         }
3858
3859         switch (gameMode) {
3860           case BeginningOfGame:
3861             /* Extra move from before last reset; ignore */
3862             if (appData.debugMode) {
3863                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
3864             }
3865             return;
3866
3867           case EndOfGame:
3868           case IcsIdle:
3869           default:
3870             /* Extra move after we tried to stop.  The mode test is
3871                not a reliable way of detecting this problem, but it's
3872                the best we can do on engines that don't support ping.
3873             */
3874             if (appData.debugMode) {
3875                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
3876                         cps->which, gameMode);
3877             }
3878             SendToProgram("undo\n", cps);
3879             return;
3880
3881           case MachinePlaysWhite:
3882           case IcsPlayingWhite:
3883             machineWhite = TRUE;
3884             break;
3885
3886           case MachinePlaysBlack:
3887           case IcsPlayingBlack:
3888             machineWhite = FALSE;
3889             break;
3890
3891           case TwoMachinesPlay:
3892             machineWhite = (cps->twoMachinesColor[0] == 'w');
3893             break;
3894         }
3895         if (WhiteOnMove(forwardMostMove) != machineWhite) {
3896             if (appData.debugMode) {
3897                 fprintf(debugFP,
3898                         "Ignoring move out of turn by %s, gameMode %d"
3899                         ", forwardMost %d\n",
3900                         cps->which, gameMode, forwardMostMove);
3901             }
3902             return;
3903         }
3904
3905         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
3906                               &fromX, &fromY, &toX, &toY, &promoChar)) {
3907             /* Machine move could not be parsed; ignore it. */
3908             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
3909                     machineMove, cps->which);
3910             DisplayError(buf1, 0);
3911             if (gameMode == TwoMachinesPlay) {
3912               GameEnds(machineWhite ? BlackWins : WhiteWins,
3913                        "Forfeit due to illegal move", GE_XBOARD);
3914             }
3915             return;
3916         }
3917
3918         hintRequested = FALSE;
3919         lastHint[0] = NULLCHAR;
3920         bookRequested = FALSE;
3921         /* Program may be pondering now */
3922         cps->maybeThinking = TRUE;
3923         if (cps->sendTime == 2) cps->sendTime = 1;
3924         if (cps->offeredDraw) cps->offeredDraw--;
3925
3926 #if ZIPPY
3927         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
3928             first.initDone) {
3929           SendMoveToICS(moveType, fromX, fromY, toX, toY);
3930           ics_user_moved = 1;
3931         }
3932 #endif
3933         /* currentMoveString is set as a side-effect of ParseOneMove */
3934         strcpy(machineMove, currentMoveString);
3935         strcat(machineMove, "\n");
3936         strcpy(moveList[forwardMostMove], machineMove);
3937
3938         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
3939
3940         if (gameMode == TwoMachinesPlay) {
3941             if (cps->other->sendTime) {
3942                 SendTimeRemaining(cps->other,
3943                                   cps->other->twoMachinesColor[0] == 'w');
3944             }
3945             SendMoveToProgram(forwardMostMove-1, cps->other);
3946             if (firstMove) {
3947                 firstMove = FALSE;
3948                 if (cps->other->useColors) {
3949                   SendToProgram(cps->other->twoMachinesColor, cps->other);
3950                 }
3951                 SendToProgram("go\n", cps->other);
3952             }
3953             cps->other->maybeThinking = TRUE;
3954         }
3955
3956         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
3957         if (!pausing && appData.ringBellAfterMoves) {
3958             RingBell();
3959         }
3960         /*
3961          * Reenable menu items that were disabled while
3962          * machine was thinking
3963          */
3964         if (gameMode != TwoMachinesPlay)
3965             SetUserThinkingEnables();
3966         return;
3967     }
3968
3969     /* Set special modes for chess engines.  Later something general
3970      *  could be added here; for now there is just one kludge feature,
3971      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
3972      *  when "xboard" is given as an interactive command.
3973      */
3974     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
3975         cps->useSigint = FALSE;
3976         cps->useSigterm = FALSE;
3977     }
3978
3979     /*
3980      * Look for communication commands
3981      */
3982     if (!strncmp(message, "telluser ", 9)) {
3983         DisplayNote(message + 9);
3984         return;
3985     }
3986     if (!strncmp(message, "tellusererror ", 14)) {
3987         DisplayError(message + 14, 0);
3988         return;
3989     }
3990     if (!strncmp(message, "tellopponent ", 13)) {
3991       if (appData.icsActive) {
3992         if (loggedOn) {
3993           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
3994           SendToICS(buf1);
3995         }
3996       } else {
3997         DisplayNote(message + 13);
3998       }
3999       return;
4000     }
4001     if (!strncmp(message, "tellothers ", 11)) {
4002       if (appData.icsActive) {
4003         if (loggedOn) {
4004           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
4005           SendToICS(buf1);
4006         }
4007       }
4008       return;
4009     }
4010     if (!strncmp(message, "tellall ", 8)) {
4011       if (appData.icsActive) {
4012         if (loggedOn) {
4013           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
4014           SendToICS(buf1);
4015         }
4016       } else {
4017         DisplayNote(message + 8);
4018       }
4019       return;
4020     }
4021     if (strncmp(message, "warning", 7) == 0) {
4022         /* Undocumented feature, use tellusererror in new code */
4023         DisplayError(message, 0);
4024         return;
4025     }
4026     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
4027         strcpy(realname, cps->tidy);
4028         strcat(realname, " query");
4029         AskQuestion(realname, buf2, buf1, cps->pr);
4030         return;
4031     }
4032     /* Commands from the engine directly to ICS.  We don't allow these to be
4033      *  sent until we are logged on. Crafty kibitzes have been known to
4034      *  interfere with the login process.
4035      */
4036     if (loggedOn) {
4037         if (!strncmp(message, "tellics ", 8)) {
4038             SendToICS(message + 8);
4039             SendToICS("\n");
4040             return;
4041         }
4042         if (!strncmp(message, "tellicsnoalias ", 15)) {
4043             SendToICS(ics_prefix);
4044             SendToICS(message + 15);
4045             SendToICS("\n");
4046             return;
4047         }
4048         /* The following are for backward compatibility only */
4049         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
4050             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
4051             SendToICS(ics_prefix);
4052             SendToICS(message);
4053             SendToICS("\n");
4054             return;
4055         }
4056     }
4057     if (strncmp(message, "feature ", 8) == 0) {
4058       ParseFeatures(message+8, cps);
4059     }
4060     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
4061       return;
4062     }
4063     /*
4064      * If the move is illegal, cancel it and redraw the board.
4065      * Also deal with other error cases.  Matching is rather loose
4066      * here to accommodate engines written before the spec.
4067      */
4068     if (strncmp(message + 1, "llegal move", 11) == 0 ||
4069         strncmp(message, "Error", 5) == 0) {
4070         if (StrStr(message, "name") ||
4071             StrStr(message, "rating") || StrStr(message, "?") ||
4072             StrStr(message, "result") || StrStr(message, "board") ||
4073             StrStr(message, "bk") || StrStr(message, "computer") ||
4074             StrStr(message, "variant") || StrStr(message, "hint") ||
4075             StrStr(message, "random") || StrStr(message, "depth") ||
4076             StrStr(message, "accepted")) {
4077             return;
4078         }
4079         if (StrStr(message, "protover")) {
4080           /* Program is responding to input, so it's apparently done
4081              initializing, and this error message indicates it is
4082              protocol version 1.  So we don't need to wait any longer
4083              for it to initialize and send feature commands. */
4084           FeatureDone(cps, 1);
4085           cps->protocolVersion = 1;
4086           return;
4087         }
4088         cps->maybeThinking = FALSE;
4089
4090         if (StrStr(message, "draw")) {
4091             /* Program doesn't have "draw" command */
4092             cps->sendDrawOffers = 0;
4093             return;
4094         }
4095         if (cps->sendTime != 1 &&
4096             (StrStr(message, "time") || StrStr(message, "otim"))) {
4097           /* Program apparently doesn't have "time" or "otim" command */
4098           cps->sendTime = 0;
4099           return;
4100         }
4101         if (StrStr(message, "analyze")) {
4102             cps->analysisSupport = FALSE;
4103             cps->analyzing = FALSE;
4104             Reset(FALSE, TRUE);
4105             sprintf(buf2, "%s does not support analysis", cps->tidy);
4106             DisplayError(buf2, 0);
4107             return;
4108         }
4109         if (StrStr(message, "(no matching move)st")) {
4110           /* Special kludge for GNU Chess 4 only */
4111           cps->stKludge = TRUE;
4112           SendTimeControl(cps, movesPerSession, timeControl,
4113                           timeIncrement, appData.searchDepth,
4114                           searchTime);
4115           return;
4116         }
4117         if (StrStr(message, "(no matching move)sd")) {
4118           /* Special kludge for GNU Chess 4 only */
4119           cps->sdKludge = TRUE;
4120           SendTimeControl(cps, movesPerSession, timeControl,
4121                           timeIncrement, appData.searchDepth,
4122                           searchTime);
4123           return;
4124         }
4125         if (!StrStr(message, "llegal")) return;
4126         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4127             gameMode == IcsIdle) return;
4128         if (forwardMostMove <= backwardMostMove) return;
4129 #if 0
4130         /* Following removed: it caused a bug where a real illegal move
4131            message in analyze mored would be ignored. */
4132         if (cps == &first && programStats.ok_to_send == 0) {
4133             /* Bogus message from Crafty responding to "."  This filtering
4134                can miss some of the bad messages, but fortunately the bug
4135                is fixed in current Crafty versions, so it doesn't matter. */
4136             return;
4137         }
4138 #endif
4139         if (pausing) PauseEvent();
4140         if (gameMode == PlayFromGameFile) {
4141             /* Stop reading this game file */
4142             gameMode = EditGame;
4143             ModeHighlight();
4144         }
4145         currentMove = --forwardMostMove;
4146         DisplayMove(currentMove-1); /* before DisplayMoveError */
4147         SwitchClocks();
4148         DisplayBothClocks();
4149         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
4150                 parseList[currentMove], cps->which);
4151         DisplayMoveError(buf1);
4152         DrawPosition(FALSE, boards[currentMove]);
4153         return;
4154     }
4155     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
4156         /* Program has a broken "time" command that
4157            outputs a string not ending in newline.
4158            Don't use it. */
4159         cps->sendTime = 0;
4160     }
4161
4162     /*
4163      * If chess program startup fails, exit with an error message.
4164      * Attempts to recover here are futile.
4165      */
4166     if ((StrStr(message, "unknown host") != NULL)
4167         || (StrStr(message, "No remote directory") != NULL)
4168         || (StrStr(message, "not found") != NULL)
4169         || (StrStr(message, "No such file") != NULL)
4170         || (StrStr(message, "can't alloc") != NULL)
4171         || (StrStr(message, "Permission denied") != NULL)) {
4172
4173         cps->maybeThinking = FALSE;
4174         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
4175                 cps->which, cps->program, cps->host, message);
4176         RemoveInputSource(cps->isr);
4177         DisplayFatalError(buf1, 0, 1);
4178         return;
4179     }
4180
4181     /*
4182      * Look for hint output
4183      */
4184     if (sscanf(message, "Hint: %s", buf1) == 1) {
4185         if (cps == &first && hintRequested) {
4186             hintRequested = FALSE;
4187             if (ParseOneMove(buf1, forwardMostMove, &moveType,
4188                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
4189                 (void) CoordsToAlgebraic(boards[forwardMostMove],
4190                                     PosFlags(forwardMostMove), EP_UNKNOWN,
4191                                     fromY, fromX, toY, toX, promoChar, buf1);
4192                 sprintf(buf2, "Hint: %s", buf1);
4193                 DisplayInformation(buf2);
4194             } else {
4195                 /* Hint move could not be parsed!? */
4196                 sprintf(buf2,
4197                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
4198                         buf1, cps->which);
4199                 DisplayError(buf2, 0);
4200             }
4201         } else {
4202             strcpy(lastHint, buf1);
4203         }
4204         return;
4205     }
4206
4207     /*
4208      * Ignore other messages if game is not in progress
4209      */
4210     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
4211         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
4212
4213     /*
4214      * look for win, lose, draw, or draw offer
4215      */
4216     if (strncmp(message, "1-0", 3) == 0) {
4217         char *p, *q, *r = "";
4218         p = strchr(message, '{');
4219         if (p) {
4220             q = strchr(p, '}');
4221             if (q) {
4222                 *q = NULLCHAR;
4223                 r = p + 1;
4224             }
4225         }
4226         GameEnds(WhiteWins, r, GE_ENGINE);
4227         return;
4228     } else if (strncmp(message, "0-1", 3) == 0) {
4229         char *p, *q, *r = "";
4230         p = strchr(message, '{');
4231         if (p) {
4232             q = strchr(p, '}');
4233             if (q) {
4234                 *q = NULLCHAR;
4235                 r = p + 1;
4236             }
4237         }
4238         /* Kludge for Arasan 4.1 bug */
4239         if (strcmp(r, "Black resigns") == 0) {
4240             GameEnds(WhiteWins, r, GE_ENGINE);
4241             return;
4242         }
4243         GameEnds(BlackWins, r, GE_ENGINE);
4244         return;
4245     } else if (strncmp(message, "1/2", 3) == 0) {
4246         char *p, *q, *r = "";
4247         p = strchr(message, '{');
4248         if (p) {
4249             q = strchr(p, '}');
4250             if (q) {
4251                 *q = NULLCHAR;
4252                 r = p + 1;
4253             }
4254         }
4255         GameEnds(GameIsDrawn, r, GE_ENGINE);
4256         return;
4257
4258     } else if (strncmp(message, "White resign", 12) == 0) {
4259         GameEnds(BlackWins, "White resigns", GE_ENGINE);
4260         return;
4261     } else if (strncmp(message, "Black resign", 12) == 0) {
4262         GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4263         return;
4264     } else if (strncmp(message, "White", 5) == 0 &&
4265                message[5] != '(' &&
4266                StrStr(message, "Black") == NULL) {
4267         GameEnds(WhiteWins, "White mates", GE_ENGINE);
4268         return;
4269     } else if (strncmp(message, "Black", 5) == 0 &&
4270                message[5] != '(') {
4271         GameEnds(BlackWins, "Black mates", GE_ENGINE);
4272         return;
4273     } else if (strcmp(message, "resign") == 0 ||
4274                strcmp(message, "computer resigns") == 0) {
4275         switch (gameMode) {
4276           case MachinePlaysBlack:
4277           case IcsPlayingBlack:
4278             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4279             break;
4280           case MachinePlaysWhite:
4281           case IcsPlayingWhite:
4282             GameEnds(BlackWins, "White resigns", GE_ENGINE);
4283             break;
4284           case TwoMachinesPlay:
4285             if (cps->twoMachinesColor[0] == 'w')
4286               GameEnds(BlackWins, "White resigns", GE_ENGINE);
4287             else
4288               GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
4289             break;
4290           default:
4291             /* can't happen */
4292             break;
4293         }
4294         return;
4295     } else if (strncmp(message, "opponent mates", 14) == 0) {
4296         switch (gameMode) {
4297           case MachinePlaysBlack:
4298           case IcsPlayingBlack:
4299             GameEnds(WhiteWins, "White mates", GE_ENGINE);
4300             break;
4301           case MachinePlaysWhite:
4302           case IcsPlayingWhite:
4303             GameEnds(BlackWins, "Black mates", GE_ENGINE);
4304             break;
4305           case TwoMachinesPlay:
4306             if (cps->twoMachinesColor[0] == 'w')
4307               GameEnds(BlackWins, "Black mates", GE_ENGINE);
4308             else
4309               GameEnds(WhiteWins, "White mates", GE_ENGINE);
4310             break;
4311           default:
4312             /* can't happen */
4313             break;
4314         }
4315         return;
4316     } else if (strncmp(message, "computer mates", 14) == 0) {
4317         switch (gameMode) {
4318           case MachinePlaysBlack:
4319           case IcsPlayingBlack:
4320             GameEnds(BlackWins, "Black mates", GE_ENGINE);
4321             break;
4322           case MachinePlaysWhite:
4323           case IcsPlayingWhite:
4324             GameEnds(WhiteWins, "White mates", GE_ENGINE);
4325             break;
4326           case TwoMachinesPlay:
4327             if (cps->twoMachinesColor[0] == 'w')
4328               GameEnds(WhiteWins, "White mates", GE_ENGINE);
4329             else
4330               GameEnds(BlackWins, "Black mates", GE_ENGINE);
4331             break;
4332           default:
4333             /* can't happen */
4334             break;
4335         }
4336         return;
4337     } else if (strncmp(message, "checkmate", 9) == 0) {
4338         if (WhiteOnMove(forwardMostMove)) {
4339             GameEnds(BlackWins, "Black mates", GE_ENGINE);
4340         } else {
4341             GameEnds(WhiteWins, "White mates", GE_ENGINE);
4342         }
4343         return;
4344     } else if (strstr(message, "Draw") != NULL ||
4345                strstr(message, "game is a draw") != NULL) {
4346         GameEnds(GameIsDrawn, "Draw", GE_ENGINE);
4347         return;
4348     } else if (strstr(message, "offer") != NULL &&
4349                strstr(message, "draw") != NULL) {
4350 #if ZIPPY
4351         if (appData.zippyPlay && first.initDone) {
4352             /* Relay offer to ICS */
4353             SendToICS(ics_prefix);
4354             SendToICS("draw\n");
4355         }
4356 #endif
4357         cps->offeredDraw = 2; /* valid until this engine moves twice */
4358         if (gameMode == TwoMachinesPlay) {
4359             if (cps->other->offeredDraw) {
4360                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4361             } else {
4362                 if (cps->other->sendDrawOffers) {
4363                     SendToProgram("draw\n", cps->other);
4364                 }
4365             }
4366         } else if (gameMode == MachinePlaysWhite ||
4367                    gameMode == MachinePlaysBlack) {
4368           if (userOfferedDraw) {
4369             DisplayInformation(_("Machine accepts your draw offer"));
4370             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
4371           } else {
4372             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
4373           }
4374         }
4375     }
4376
4377
4378     /*
4379      * Look for thinking output
4380      */
4381     if (appData.showThinking) {
4382         int plylev, mvleft, mvtot, curscore, time;
4383         char mvname[MOVE_LEN];
4384         u64 nodes;
4385         char plyext;
4386         int ignore = FALSE;
4387         int prefixHint = FALSE;
4388         mvname[0] = NULLCHAR;
4389
4390         switch (gameMode) {
4391           case MachinePlaysBlack:
4392           case IcsPlayingBlack:
4393             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4394             break;
4395           case MachinePlaysWhite:
4396           case IcsPlayingWhite:
4397             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
4398             break;
4399           case AnalyzeMode:
4400           case AnalyzeFile:
4401           /* icsEngineAnalyze */
4402           case IcsObserving:
4403                 if (!appData.icsEngineAnalyze) ignore = TRUE;
4404             break;
4405           case TwoMachinesPlay:
4406             if ((cps->twoMachinesColor[0] == 'w') !=
4407                 WhiteOnMove(forwardMostMove)) {
4408                 ignore = TRUE;
4409             }
4410             break;
4411           default:
4412             ignore = TRUE;
4413             break;
4414         }
4415
4416         if (!ignore) {
4417             buf1[0] = NULLCHAR;
4418             if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n",
4419                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
4420
4421                 if (plyext != ' ' && plyext != '\t') {
4422                     time *= 100;
4423                 }
4424                 programStats.depth = plylev;
4425                 programStats.nodes = nodes;
4426                 programStats.time = time;
4427                 programStats.score = curscore;
4428                 programStats.got_only_move = 0;
4429
4430                 /* Buffer overflow protection */
4431                 if (buf1[0] != NULLCHAR) {
4432                     if (strlen(buf1) >= sizeof(programStats.movelist)
4433                         && appData.debugMode) {
4434                         fprintf(debugFP,
4435                                 "PV is too long; using the first %d bytes.\n",
4436                                 sizeof(programStats.movelist) - 1);
4437                     }
4438                     strncpy(programStats.movelist, buf1,
4439                             sizeof(programStats.movelist));
4440                     programStats.movelist[sizeof(programStats.movelist) - 1]
4441                       = NULLCHAR;
4442                 } else {
4443                     sprintf(programStats.movelist, " no PV\n");
4444                 }
4445
4446                 if (programStats.seen_stat) {
4447                     programStats.ok_to_send = 1;
4448                 }
4449
4450                 if (strchr(programStats.movelist, '(') != NULL) {
4451                     programStats.line_is_book = 1;
4452                     programStats.nr_moves = 0;
4453                     programStats.moves_left = 0;
4454                 } else {
4455                     programStats.line_is_book = 0;
4456                 }
4457
4458                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4459                         plylev,
4460                         (gameMode == TwoMachinesPlay ?
4461                          ToUpper(cps->twoMachinesColor[0]) : ' '),
4462                         ((double) curscore) / 100.0,
4463                         prefixHint ? lastHint : "",
4464                         prefixHint ? " " : "", buf1);
4465
4466                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
4467                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4468                     DisplayMove(currentMove - 1);
4469                     DisplayAnalysis();
4470                 }
4471                 return;
4472
4473             } else if ((p=StrStr(message, "(only move)")) != NULL) {
4474                 /* crafty (9.25+) says "(only move) <move>"
4475                  * if there is only 1 legal move
4476                  */
4477                 sscanf(p, "(only move) %s", buf1);
4478                 sprintf(thinkOutput, "%s (only move)", buf1);
4479                 sprintf(programStats.movelist, "%s (only move)", buf1);
4480                 programStats.depth = 1;
4481                 programStats.nr_moves = 1;
4482                 programStats.moves_left = 1;
4483                 programStats.nodes = 1;
4484                 programStats.time = 1;
4485                 programStats.got_only_move = 1;
4486
4487                 /* Not really, but we also use this member to
4488                    mean "line isn't going to change" (Crafty
4489                    isn't searching, so stats won't change) */
4490                 programStats.line_is_book = 1;
4491
4492                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4493                     gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4494                     DisplayMove(currentMove - 1);
4495                     DisplayAnalysis();
4496                 }
4497                 return;
4498             } else if (sscanf(message,"stat01: %d" u64Display "%d %d %d %s",
4499                               &time, &nodes, &plylev, &mvleft,
4500                               &mvtot, mvname) >= 5) {
4501                 /* The stat01: line is from Crafty (9.29+) in response
4502                    to the "." command */
4503                 programStats.seen_stat = 1;
4504                 cps->maybeThinking = TRUE;
4505
4506                 if (programStats.got_only_move || !appData.periodicUpdates)
4507                   return;
4508
4509                 programStats.depth = plylev;
4510                 programStats.time = time;
4511                 programStats.nodes = nodes;
4512                 programStats.moves_left = mvleft;
4513                 programStats.nr_moves = mvtot;
4514                 strcpy(programStats.move_name, mvname);
4515                 programStats.ok_to_send = 1;
4516                 DisplayAnalysis();
4517                 return;
4518
4519             } else if (strncmp(message,"++",2) == 0) {
4520                 /* Crafty 9.29+ outputs this */
4521                 programStats.got_fail = 2;
4522                 return;
4523
4524             } else if (strncmp(message,"--",2) == 0) {
4525                 /* Crafty 9.29+ outputs this */
4526                 programStats.got_fail = 1;
4527                 return;
4528
4529             } else if (thinkOutput[0] != NULLCHAR &&
4530                        strncmp(message, "    ", 4) == 0) {
4531                 p = message;
4532                 while (*p && *p == ' ') p++;
4533                 strcat(thinkOutput, " ");
4534                 strcat(thinkOutput, p);
4535                 strcat(programStats.movelist, " ");
4536                 strcat(programStats.movelist, p);
4537                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4538                     gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
4539                     DisplayMove(currentMove - 1);
4540                     DisplayAnalysis();
4541                 }
4542                 return;
4543             }
4544         }
4545     }
4546 }
4547
4548
4549 /* Parse a game score from the character string "game", and
4550    record it as the history of the current game.  The game
4551    score is NOT assumed to start from the standard position.
4552    The display is not updated in any way.
4553    */
4554 void
4555 ParseGameHistory(game)
4556      char *game;
4557 {
4558     ChessMove moveType;
4559     int fromX, fromY, toX, toY, boardIndex;
4560     char promoChar;
4561     char *p, *q;
4562     char buf[MSG_SIZ];
4563
4564     if (appData.debugMode)
4565       fprintf(debugFP, "Parsing game history: %s\n", game);
4566
4567     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4568     gameInfo.site = StrSave(appData.icsHost);
4569     gameInfo.date = PGNDate();
4570     gameInfo.round = StrSave("-");
4571
4572     /* Parse out names of players */
4573     while (*game == ' ') game++;
4574     p = buf;
4575     while (*game != ' ') *p++ = *game++;
4576     *p = NULLCHAR;
4577     gameInfo.white = StrSave(buf);
4578     while (*game == ' ') game++;
4579     p = buf;
4580     while (*game != ' ' && *game != '\n') *p++ = *game++;
4581     *p = NULLCHAR;
4582     gameInfo.black = StrSave(buf);
4583
4584     /* Parse moves */
4585     boardIndex = blackPlaysFirst ? 1 : 0;
4586     yynewstr(game);
4587     for (;;) {
4588         yyboardindex = boardIndex;
4589         moveType = (ChessMove) yylex();
4590         switch (moveType) {
4591           case WhitePromotionQueen:
4592           case BlackPromotionQueen:
4593           case WhitePromotionRook:
4594           case BlackPromotionRook:
4595           case WhitePromotionBishop:
4596           case BlackPromotionBishop:
4597           case WhitePromotionKnight:
4598           case BlackPromotionKnight:
4599           case WhitePromotionKing:
4600           case BlackPromotionKing:
4601           case NormalMove:
4602           case WhiteCapturesEnPassant:
4603           case BlackCapturesEnPassant:
4604           case WhiteKingSideCastle:
4605           case WhiteQueenSideCastle:
4606           case BlackKingSideCastle:
4607           case BlackQueenSideCastle:
4608           case WhiteKingSideCastleWild:
4609           case WhiteQueenSideCastleWild:
4610           case BlackKingSideCastleWild:
4611           case BlackQueenSideCastleWild:
4612           case IllegalMove:             /* maybe suicide chess, etc. */
4613             fromX = currentMoveString[0] - 'a';
4614             fromY = currentMoveString[1] - '1';
4615             toX = currentMoveString[2] - 'a';
4616             toY = currentMoveString[3] - '1';
4617             promoChar = currentMoveString[4];
4618             break;
4619           case WhiteDrop:
4620           case BlackDrop:
4621             fromX = moveType == WhiteDrop ?
4622               (int) CharToPiece(ToUpper(currentMoveString[0])) :
4623             (int) CharToPiece(ToLower(currentMoveString[0]));
4624             fromY = DROP_RANK;
4625             toX = currentMoveString[2] - 'a';
4626             toY = currentMoveString[3] - '1';
4627             promoChar = NULLCHAR;
4628             break;
4629           case AmbiguousMove:
4630             /* bug? */
4631             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
4632             DisplayError(buf, 0);
4633             return;
4634           case ImpossibleMove:
4635             /* bug? */
4636             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
4637             DisplayError(buf, 0);
4638             return;
4639           case (ChessMove) 0:   /* end of file */
4640             if (boardIndex < backwardMostMove) {
4641                 /* Oops, gap.  How did that happen? */
4642                 DisplayError(_("Gap in move list"), 0);
4643                 return;
4644             }
4645             backwardMostMove =  blackPlaysFirst ? 1 : 0;
4646             if (boardIndex > forwardMostMove) {
4647                 forwardMostMove = boardIndex;
4648             }
4649             return;
4650           case ElapsedTime:
4651             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4652                 strcat(parseList[boardIndex-1], " ");
4653                 strcat(parseList[boardIndex-1], yy_text);
4654             }
4655             continue;
4656           case Comment:
4657           case PGNTag:
4658           case NAG:
4659           default:
4660             /* ignore */
4661             continue;
4662           case WhiteWins:
4663           case BlackWins:
4664           case GameIsDrawn:
4665           case GameUnfinished:
4666             if (gameMode == IcsExamining) {
4667                 if (boardIndex < backwardMostMove) {
4668                     /* Oops, gap.  How did that happen? */
4669                     return;
4670                 }
4671                 backwardMostMove = blackPlaysFirst ? 1 : 0;
4672                 return;
4673             }
4674             gameInfo.result = moveType;
4675             p = strchr(yy_text, '{');
4676             if (p == NULL) p = strchr(yy_text, '(');
4677             if (p == NULL) {
4678                 p = yy_text;
4679                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4680             } else {
4681                 q = strchr(p, *p == '{' ? '}' : ')');
4682                 if (q != NULL) *q = NULLCHAR;
4683                 p++;
4684             }
4685             gameInfo.resultDetails = StrSave(p);
4686             continue;
4687         }
4688         if (boardIndex >= forwardMostMove &&
4689             !(gameMode == IcsObserving && ics_gamenum == -1)) {
4690             backwardMostMove = blackPlaysFirst ? 1 : 0;
4691             return;
4692         }
4693         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4694                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4695                                  parseList[boardIndex]);
4696         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4697         /* currentMoveString is set as a side-effect of yylex */
4698         strcpy(moveList[boardIndex], currentMoveString);
4699         strcat(moveList[boardIndex], "\n");
4700         boardIndex++;
4701         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4702         switch (MateTest(boards[boardIndex],
4703                          PosFlags(boardIndex), EP_UNKNOWN)) {
4704           case MT_NONE:
4705           case MT_STALEMATE:
4706           default:
4707             break;
4708           case MT_CHECK:
4709             strcat(parseList[boardIndex - 1], "+");
4710             break;
4711           case MT_CHECKMATE:
4712             strcat(parseList[boardIndex - 1], "#");
4713             break;
4714         }
4715     }
4716 }
4717
4718
4719 /* Apply a move to the given board  */
4720 void
4721 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4722      int fromX, fromY, toX, toY;
4723      int promoChar;
4724      Board board;
4725 {
4726     ChessSquare captured = board[toY][toX];
4727     if (fromY == DROP_RANK) {
4728         /* must be first */
4729         board[toY][toX] = (ChessSquare) fromX;
4730     } else if (fromX == toX && fromY == toY) {
4731         return;
4732     } else if (fromY == 0 && fromX == 4
4733         && board[fromY][fromX] == WhiteKing
4734         && toY == 0 && toX == 6) {
4735         board[fromY][fromX] = EmptySquare;
4736         board[toY][toX] = WhiteKing;
4737         board[fromY][7] = EmptySquare;
4738         board[toY][5] = WhiteRook;
4739     } else if (fromY == 0 && fromX == 4
4740                && board[fromY][fromX] == WhiteKing
4741                && toY == 0 && toX == 2) {
4742         board[fromY][fromX] = EmptySquare;
4743         board[toY][toX] = WhiteKing;
4744         board[fromY][0] = EmptySquare;
4745         board[toY][3] = WhiteRook;
4746     } else if (fromY == 0 && fromX == 3
4747                && board[fromY][fromX] == WhiteKing
4748                && toY == 0 && toX == 5) {
4749         board[fromY][fromX] = EmptySquare;
4750         board[toY][toX] = WhiteKing;
4751         board[fromY][7] = EmptySquare;
4752         board[toY][4] = WhiteRook;
4753     } else if (fromY == 0 && fromX == 3
4754                && board[fromY][fromX] == WhiteKing
4755                && toY == 0 && toX == 1) {
4756         board[fromY][fromX] = EmptySquare;
4757         board[toY][toX] = WhiteKing;
4758         board[fromY][0] = EmptySquare;
4759         board[toY][2] = WhiteRook;
4760     } else if (board[fromY][fromX] == WhitePawn
4761                && toY == 7) {
4762         /* white pawn promotion */
4763         board[7][toX] = CharToPiece(ToUpper(promoChar));
4764         if (board[7][toX] == EmptySquare) {
4765             board[7][toX] = WhiteQueen;
4766         }
4767         board[fromY][fromX] = EmptySquare;
4768     } else if ((fromY == 4)
4769                && (toX != fromX)
4770                && (board[fromY][fromX] == WhitePawn)
4771                && (board[toY][toX] == EmptySquare)) {
4772         board[fromY][fromX] = EmptySquare;
4773         board[toY][toX] = WhitePawn;
4774         captured = board[toY - 1][toX];
4775         board[toY - 1][toX] = EmptySquare;
4776     } else if (fromY == 7 && fromX == 4
4777                && board[fromY][fromX] == BlackKing
4778                && toY == 7 && toX == 6) {
4779         board[fromY][fromX] = EmptySquare;
4780         board[toY][toX] = BlackKing;
4781         board[fromY][7] = EmptySquare;
4782         board[toY][5] = BlackRook;
4783     } else if (fromY == 7 && fromX == 4
4784                && board[fromY][fromX] == BlackKing
4785                && toY == 7 && toX == 2) {
4786         board[fromY][fromX] = EmptySquare;
4787         board[toY][toX] = BlackKing;
4788         board[fromY][0] = EmptySquare;
4789         board[toY][3] = BlackRook;
4790     } else if (fromY == 7 && fromX == 3
4791                && board[fromY][fromX] == BlackKing
4792                && toY == 7 && toX == 5) {
4793         board[fromY][fromX] = EmptySquare;
4794         board[toY][toX] = BlackKing;
4795         board[fromY][7] = EmptySquare;
4796         board[toY][4] = BlackRook;
4797     } else if (fromY == 7 && fromX == 3
4798                && board[fromY][fromX] == BlackKing
4799                && toY == 7 && toX == 1) {
4800         board[fromY][fromX] = EmptySquare;
4801         board[toY][toX] = BlackKing;
4802         board[fromY][0] = EmptySquare;
4803         board[toY][2] = BlackRook;
4804     } else if (board[fromY][fromX] == BlackPawn
4805                && toY == 0) {
4806         /* black pawn promotion */
4807         board[0][toX] = CharToPiece(ToLower(promoChar));
4808         if (board[0][toX] == EmptySquare) {
4809             board[0][toX] = BlackQueen;
4810         }
4811         board[fromY][fromX] = EmptySquare;
4812     } else if ((fromY == 3)
4813                && (toX != fromX)
4814                && (board[fromY][fromX] == BlackPawn)
4815                && (board[toY][toX] == EmptySquare)) {
4816         board[fromY][fromX] = EmptySquare;
4817         board[toY][toX] = BlackPawn;
4818         captured = board[toY + 1][toX];
4819         board[toY + 1][toX] = EmptySquare;
4820     } else {
4821         board[toY][toX] = board[fromY][fromX];
4822         board[fromY][fromX] = EmptySquare;
4823     }
4824     if (gameInfo.variant == VariantCrazyhouse) {
4825 #if 0
4826       /* !!A lot more code needs to be written to support holdings */
4827       if (fromY == DROP_RANK) {
4828         /* Delete from holdings */
4829         if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4830       }
4831       if (captured != EmptySquare) {
4832         /* Add to holdings */
4833         if (captured < BlackPawn) {
4834           holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4835         } else {
4836           holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4837         }
4838       }
4839 #endif
4840     } else if (gameInfo.variant == VariantAtomic) {
4841       if (captured != EmptySquare) {
4842         int y, x;
4843         for (y = toY-1; y <= toY+1; y++) {
4844           for (x = toX-1; x <= toX+1; x++) {
4845             if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4846                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4847               board[y][x] = EmptySquare;
4848             }
4849           }
4850         }
4851         board[toY][toX] = EmptySquare;
4852       }
4853     }
4854 }
4855
4856 /* Updates forwardMostMove */
4857 void
4858 MakeMove(fromX, fromY, toX, toY, promoChar)
4859      int fromX, fromY, toX, toY;
4860      int promoChar;
4861 {
4862     forwardMostMove++;
4863     if (forwardMostMove >= MAX_MOVES) {
4864       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4865                         0, 1);
4866       return;
4867     }
4868     SwitchClocks();
4869     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4870     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4871     if (commentList[forwardMostMove] != NULL) {
4872         free(commentList[forwardMostMove]);
4873         commentList[forwardMostMove] = NULL;
4874     }
4875     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4876     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4877     gameInfo.result = GameUnfinished;
4878     if (gameInfo.resultDetails != NULL) {
4879         free(gameInfo.resultDetails);
4880         gameInfo.resultDetails = NULL;
4881     }
4882     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4883                               moveList[forwardMostMove - 1]);
4884     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4885                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4886                              fromY, fromX, toY, toX, promoChar,
4887                              parseList[forwardMostMove - 1]);
4888     switch (MateTest(boards[forwardMostMove],
4889                      PosFlags(forwardMostMove), EP_UNKNOWN)){
4890       case MT_NONE:
4891       case MT_STALEMATE:
4892       default:
4893         break;
4894       case MT_CHECK:
4895         strcat(parseList[forwardMostMove - 1], "+");
4896         break;
4897       case MT_CHECKMATE:
4898         strcat(parseList[forwardMostMove - 1], "#");
4899         break;
4900     }
4901 }
4902
4903 /* Updates currentMove if not pausing */
4904 void
4905 ShowMove(fromX, fromY, toX, toY)
4906 {
4907     int instant = (gameMode == PlayFromGameFile) ?
4908         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4909     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4910         if (!instant) {
4911             if (forwardMostMove == currentMove + 1) {
4912                 AnimateMove(boards[forwardMostMove - 1],
4913                             fromX, fromY, toX, toY);
4914             }
4915             if (appData.highlightLastMove) {
4916                 SetHighlights(fromX, fromY, toX, toY);
4917             }
4918         }
4919         currentMove = forwardMostMove;
4920     }
4921
4922     if (instant) return;
4923     DisplayMove(currentMove - 1);
4924     DrawPosition(FALSE, boards[currentMove]);
4925     DisplayBothClocks();
4926     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4927 }
4928
4929
4930 void
4931 InitChessProgram(cps)
4932      ChessProgramState *cps;
4933 {
4934     char buf[MSG_SIZ];
4935     if (appData.noChessProgram) return;
4936     hintRequested = FALSE;
4937     bookRequested = FALSE;
4938     SendToProgram(cps->initString, cps);
4939     if (gameInfo.variant != VariantNormal &&
4940         gameInfo.variant != VariantLoadable) {
4941       char *v = VariantName(gameInfo.variant);
4942       if (StrStr(cps->variants, v) == NULL) {
4943         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
4944         DisplayFatalError(buf, 0, 1);
4945         return;
4946       }
4947       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4948       SendToProgram(buf, cps);
4949     }
4950     if (cps->sendICS) {
4951       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4952       SendToProgram(buf, cps);
4953     }
4954     cps->maybeThinking = FALSE;
4955     cps->offeredDraw = 0;
4956     if (!appData.icsActive) {
4957         SendTimeControl(cps, movesPerSession, timeControl,
4958                         timeIncrement, appData.searchDepth,
4959                         searchTime);
4960     }
4961     if (appData.showThinking) {
4962         SendToProgram("post\n", cps);
4963     }
4964     SendToProgram("hard\n", cps);
4965     if (!appData.ponderNextMove) {
4966         /* Warning: "easy" is a toggle in GNU Chess, so don't send
4967            it without being sure what state we are in first.  "hard"
4968            is not a toggle, so that one is OK.
4969          */
4970         SendToProgram("easy\n", cps);
4971     }
4972     if (cps->usePing) {
4973       sprintf(buf, "ping %d\n", ++cps->lastPing);
4974       SendToProgram(buf, cps);
4975     }
4976     cps->initDone = TRUE;
4977 }
4978
4979
4980 void
4981 StartChessProgram(cps)
4982      ChessProgramState *cps;
4983 {
4984     char buf[MSG_SIZ];
4985     int err;
4986
4987     if (appData.noChessProgram) return;
4988     cps->initDone = FALSE;
4989
4990     if (strcmp(cps->host, "localhost") == 0) {
4991         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4992     } else if (*appData.remoteShell == NULLCHAR) {
4993         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4994     } else {
4995         if (*appData.remoteUser == NULLCHAR) {
4996             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4997                     cps->program);
4998         } else {
4999             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
5000                     cps->host, appData.remoteUser, cps->program);
5001         }
5002         err = StartChildProcess(buf, "", &cps->pr);
5003     }
5004
5005     if (err != 0) {
5006         sprintf(buf, "Startup failure on '%s'", cps->program);
5007         DisplayFatalError(buf, err, 1);
5008         cps->pr = NoProc;
5009         cps->isr = NULL;
5010         return;
5011     }
5012
5013     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
5014     if (cps->protocolVersion > 1) {
5015       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
5016       SendToProgram(buf, cps);
5017     } else {
5018       SendToProgram("xboard\n", cps);
5019     }
5020 }
5021
5022
5023 void
5024 TwoMachinesEventIfReady P((void))
5025 {
5026   if (first.lastPing != first.lastPong) {
5027     DisplayMessage("", "Waiting for first chess program");
5028     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5029     return;
5030   }
5031   if (second.lastPing != second.lastPong) {
5032     DisplayMessage("", "Waiting for second chess program");
5033     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
5034     return;
5035   }
5036   ThawUI();
5037   TwoMachinesEvent();
5038 }
5039
5040 void
5041 NextMatchGame P((void))
5042 {
5043     Reset(FALSE, TRUE);
5044     if (*appData.loadGameFile != NULLCHAR) {
5045         LoadGameFromFile(appData.loadGameFile,
5046                          appData.loadGameIndex,
5047                          appData.loadGameFile, FALSE);
5048     } else if (*appData.loadPositionFile != NULLCHAR) {
5049         LoadPositionFromFile(appData.loadPositionFile,
5050                              appData.loadPositionIndex,
5051                              appData.loadPositionFile);
5052     }
5053     TwoMachinesEventIfReady();
5054 }
5055
5056 void
5057 GameEnds(result, resultDetails, whosays)
5058      ChessMove result;
5059      char *resultDetails;
5060      int whosays;
5061 {
5062     GameMode nextGameMode;
5063     int isIcsGame;
5064
5065     if (appData.debugMode) {
5066       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
5067               result, resultDetails ? resultDetails : "(null)", whosays);
5068     }
5069
5070     if (appData.icsActive && whosays == GE_ENGINE) {
5071         /* If we are playing on ICS, the server decides when the
5072            game is over, but the engine can offer to draw, claim
5073            a draw, or resign.
5074          */
5075 #if ZIPPY
5076         if (appData.zippyPlay && first.initDone) {
5077             if (result == GameIsDrawn) {
5078                 /* In case draw still needs to be claimed */
5079                 SendToICS(ics_prefix);
5080                 SendToICS("draw\n");
5081             } else if (StrCaseStr(resultDetails, "resign")) {
5082                 SendToICS(ics_prefix);
5083                 SendToICS("resign\n");
5084             }
5085         }
5086 #endif
5087         return;
5088     }
5089
5090     /* If we're loading the game from a file, stop */
5091     if (whosays == GE_FILE) {
5092       (void) StopLoadGameTimer();
5093       gameFileFP = NULL;
5094     }
5095
5096     /* Cancel draw offers */
5097    first.offeredDraw = second.offeredDraw = 0;
5098
5099     /* If this is an ICS game, only ICS can really say it's done;
5100        if not, anyone can. */
5101     isIcsGame = (gameMode == IcsPlayingWhite ||
5102                  gameMode == IcsPlayingBlack ||
5103                  gameMode == IcsObserving    ||
5104                  gameMode == IcsExamining);
5105
5106     if (!isIcsGame || whosays == GE_ICS) {
5107         /* OK -- not an ICS game, or ICS said it was done */
5108         StopClocks();
5109         if (!isIcsGame && !appData.noChessProgram)
5110           SetUserThinkingEnables();
5111
5112         if (resultDetails != NULL) {
5113             gameInfo.result = result;
5114             gameInfo.resultDetails = StrSave(resultDetails);
5115
5116             /* Tell program how game ended in case it is learning */
5117             if (gameMode == MachinePlaysWhite ||
5118                 gameMode == MachinePlaysBlack ||
5119                 gameMode == TwoMachinesPlay ||
5120                 gameMode == IcsPlayingWhite ||
5121                 gameMode == IcsPlayingBlack ||
5122                 gameMode == BeginningOfGame) {
5123                 char buf[MSG_SIZ];
5124                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5125                         resultDetails);
5126                 if (first.pr != NoProc) {
5127                     SendToProgram(buf, &first);
5128                 }
5129                 if (second.pr != NoProc &&
5130                     gameMode == TwoMachinesPlay) {
5131                     SendToProgram(buf, &second);
5132                 }
5133             }
5134
5135             /* display last move only if game was not loaded from file */
5136             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5137                 DisplayMove(currentMove - 1);
5138
5139             if (forwardMostMove != 0) {
5140                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5141                     if (*appData.saveGameFile != NULLCHAR) {
5142                         SaveGameToFile(appData.saveGameFile, TRUE);
5143                     } else if (appData.autoSaveGames) {
5144                         AutoSaveGame();
5145                     }
5146                     if (*appData.savePositionFile != NULLCHAR) {
5147                         SavePositionToFile(appData.savePositionFile);
5148                     }
5149                 }
5150             }
5151         }
5152
5153         if (appData.icsActive) {
5154             if (appData.quietPlay &&
5155                 (gameMode == IcsPlayingWhite ||
5156                  gameMode == IcsPlayingBlack)) {
5157                 SendToICS(ics_prefix);
5158                 SendToICS("set shout 1\n");
5159             }
5160             nextGameMode = IcsIdle;
5161             ics_user_moved = FALSE;
5162             /* clean up premove.  It's ugly when the game has ended and the
5163              * premove highlights are still on the board.
5164              */
5165             if (gotPremove) {
5166               gotPremove = FALSE;
5167               ClearPremoveHighlights();
5168               DrawPosition(FALSE, boards[currentMove]);
5169             }
5170             if (whosays == GE_ICS) {
5171                 switch (result) {
5172                 case WhiteWins:
5173                     if (gameMode == IcsPlayingWhite)
5174                         PlayIcsWinSound();
5175                     else if(gameMode == IcsPlayingBlack)
5176                         PlayIcsLossSound();
5177                     break;
5178                 case BlackWins:
5179                     if (gameMode == IcsPlayingBlack)
5180                         PlayIcsWinSound();
5181                     else if(gameMode == IcsPlayingWhite)
5182                         PlayIcsLossSound();
5183                     break;
5184                 case GameIsDrawn:
5185                     PlayIcsDrawSound();
5186                     break;
5187                 default:
5188                     PlayIcsUnfinishedSound();
5189                 }
5190             }
5191         } else if (gameMode == EditGame ||
5192                    gameMode == PlayFromGameFile ||
5193                    gameMode == AnalyzeMode ||
5194                    gameMode == AnalyzeFile) {
5195             nextGameMode = gameMode;
5196         } else {
5197             nextGameMode = EndOfGame;
5198         }
5199         pausing = FALSE;
5200         ModeHighlight();
5201     } else {
5202         nextGameMode = gameMode;
5203     }
5204
5205     if (appData.noChessProgram) {
5206         gameMode = nextGameMode;
5207         ModeHighlight();
5208         return;
5209     }
5210
5211     if (first.reuse) {
5212         /* Put first chess program into idle state */
5213         if (first.pr != NoProc &&
5214             (gameMode == MachinePlaysWhite ||
5215              gameMode == MachinePlaysBlack ||
5216              gameMode == TwoMachinesPlay ||
5217              gameMode == IcsPlayingWhite ||
5218              gameMode == IcsPlayingBlack ||
5219              gameMode == BeginningOfGame)) {
5220             SendToProgram("force\n", &first);
5221             if (first.usePing) {
5222               char buf[MSG_SIZ];
5223               sprintf(buf, "ping %d\n", ++first.lastPing);
5224               SendToProgram(buf, &first);
5225             }
5226         }
5227     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5228         /* Kill off first chess program */
5229         if (first.isr != NULL)
5230           RemoveInputSource(first.isr);
5231         first.isr = NULL;
5232
5233         if (first.pr != NoProc) {
5234             ExitAnalyzeMode();
5235             SendToProgram("quit\n", &first);
5236             DestroyChildProcess(first.pr, first.useSigterm);
5237         }
5238         first.pr = NoProc;
5239     }
5240     if (second.reuse) {
5241         /* Put second chess program into idle state */
5242         if (second.pr != NoProc &&
5243             gameMode == TwoMachinesPlay) {
5244             SendToProgram("force\n", &second);
5245             if (second.usePing) {
5246               char buf[MSG_SIZ];
5247               sprintf(buf, "ping %d\n", ++second.lastPing);
5248               SendToProgram(buf, &second);
5249             }
5250         }
5251     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5252         /* Kill off second chess program */
5253         if (second.isr != NULL)
5254           RemoveInputSource(second.isr);
5255         second.isr = NULL;
5256
5257         if (second.pr != NoProc) {
5258             SendToProgram("quit\n", &second);
5259             DestroyChildProcess(second.pr, second.useSigterm);
5260         }
5261         second.pr = NoProc;
5262     }
5263
5264     if (matchMode && gameMode == TwoMachinesPlay) {
5265         switch (result) {
5266         case WhiteWins:
5267           if (first.twoMachinesColor[0] == 'w') {
5268             first.matchWins++;
5269           } else {
5270             second.matchWins++;
5271           }
5272           break;
5273         case BlackWins:
5274           if (first.twoMachinesColor[0] == 'b') {
5275             first.matchWins++;
5276           } else {
5277             second.matchWins++;
5278           }
5279           break;
5280         default:
5281           break;
5282         }
5283         if (matchGame < appData.matchGames) {
5284             char *tmp;
5285             tmp = first.twoMachinesColor;
5286             first.twoMachinesColor = second.twoMachinesColor;
5287             second.twoMachinesColor = tmp;
5288             gameMode = nextGameMode;
5289             matchGame++;
5290             ScheduleDelayedEvent(NextMatchGame, 10000);
5291             return;
5292         } else {
5293             char buf[MSG_SIZ];
5294             gameMode = nextGameMode;
5295             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
5296                     first.tidy, second.tidy,
5297                     first.matchWins, second.matchWins,
5298                     appData.matchGames - (first.matchWins + second.matchWins));
5299             DisplayFatalError(buf, 0, 0);
5300         }
5301     }
5302     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5303         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5304       ExitAnalyzeMode();
5305     gameMode = nextGameMode;
5306     ModeHighlight();
5307 }
5308
5309 /* Assumes program was just initialized (initString sent).
5310    Leaves program in force mode. */
5311 void
5312 FeedMovesToProgram(cps, upto)
5313      ChessProgramState *cps;
5314      int upto;
5315 {
5316     int i;
5317
5318     if (appData.debugMode)
5319       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5320               startedFromSetupPosition ? "position and " : "",
5321               backwardMostMove, upto, cps->which);
5322     SendToProgram("force\n", cps);
5323     if (startedFromSetupPosition) {
5324         SendBoard(cps, backwardMostMove);
5325     }
5326     for (i = backwardMostMove; i < upto; i++) {
5327         SendMoveToProgram(i, cps);
5328     }
5329 }
5330
5331
5332 void
5333 ResurrectChessProgram()
5334 {
5335      /* The chess program may have exited.
5336         If so, restart it and feed it all the moves made so far. */
5337
5338     if (appData.noChessProgram || first.pr != NoProc) return;
5339
5340     StartChessProgram(&first);
5341     InitChessProgram(&first);
5342     FeedMovesToProgram(&first, currentMove);
5343
5344     if (!first.sendTime) {
5345         /* can't tell gnuchess what its clock should read,
5346            so we bow to its notion. */
5347         ResetClocks();
5348         timeRemaining[0][currentMove] = whiteTimeRemaining;
5349         timeRemaining[1][currentMove] = blackTimeRemaining;
5350     }
5351
5352     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
5353                  appData.icsEngineAnalyze) && first.analysisSupport) {
5354       SendToProgram("analyze\n", &first);
5355       first.analyzing = TRUE;
5356     }
5357 }
5358
5359 /*
5360  * Button procedures
5361  */
5362 void
5363 Reset(redraw, init)
5364      int redraw, init;
5365 {
5366     int i;
5367
5368     if (appData.debugMode) {
5369         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5370                 redraw, init, gameMode);
5371     }
5372
5373     pausing = pauseExamInvalid = FALSE;
5374     startedFromSetupPosition = blackPlaysFirst = FALSE;
5375     firstMove = TRUE;
5376     whiteFlag = blackFlag = FALSE;
5377     userOfferedDraw = FALSE;
5378     hintRequested = bookRequested = FALSE;
5379     first.maybeThinking = FALSE;
5380     second.maybeThinking = FALSE;
5381     thinkOutput[0] = NULLCHAR;
5382     lastHint[0] = NULLCHAR;
5383     ClearGameInfo(&gameInfo);
5384     gameInfo.variant = StringToVariant(appData.variant);
5385     ics_user_moved = ics_clock_paused = FALSE;
5386     ics_getting_history = H_FALSE;
5387     ics_gamenum = -1;
5388     white_holding[0] = black_holding[0] = NULLCHAR;
5389     ClearProgramStats();
5390
5391     ResetFrontEnd();
5392     ClearHighlights();
5393     flipView = appData.flipView;
5394     ClearPremoveHighlights();
5395     gotPremove = FALSE;
5396     alarmSounded = FALSE;
5397
5398     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5399     ExitAnalyzeMode();
5400     gameMode = BeginningOfGame;
5401     ModeHighlight();
5402     InitPosition(redraw);
5403     for (i = 0; i < MAX_MOVES; i++) {
5404         if (commentList[i] != NULL) {
5405             free(commentList[i]);
5406             commentList[i] = NULL;
5407         }
5408     }
5409     ResetClocks();
5410     timeRemaining[0][0] = whiteTimeRemaining;
5411     timeRemaining[1][0] = blackTimeRemaining;
5412     if (first.pr == NULL) {
5413         StartChessProgram(&first);
5414     }
5415     if (init) InitChessProgram(&first);
5416     DisplayTitle("");
5417     DisplayMessage("", "");
5418     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5419 }
5420
5421 void
5422 AutoPlayGameLoop()
5423 {
5424     for (;;) {
5425         if (!AutoPlayOneMove())
5426           return;
5427         if (matchMode || appData.timeDelay == 0)
5428           continue;
5429         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5430           return;
5431         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5432         break;
5433     }
5434 }
5435
5436
5437 int
5438 AutoPlayOneMove()
5439 {
5440     int fromX, fromY, toX, toY;
5441
5442     if (appData.debugMode) {
5443       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5444     }
5445
5446     if (gameMode != PlayFromGameFile)
5447       return FALSE;
5448
5449     if (currentMove >= forwardMostMove) {
5450       gameMode = EditGame;
5451       ModeHighlight();
5452       return FALSE;
5453     }
5454
5455     toX = moveList[currentMove][2] - 'a';
5456     toY = moveList[currentMove][3] - '1';
5457
5458     if (moveList[currentMove][1] == '@') {
5459         if (appData.highlightLastMove) {
5460             SetHighlights(-1, -1, toX, toY);
5461         }
5462     } else {
5463         fromX = moveList[currentMove][0] - 'a';
5464         fromY = moveList[currentMove][1] - '1';
5465         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5466
5467         if (appData.highlightLastMove) {
5468             SetHighlights(fromX, fromY, toX, toY);
5469         }
5470     }
5471     DisplayMove(currentMove);
5472     SendMoveToProgram(currentMove++, &first);
5473     DisplayBothClocks();
5474     DrawPosition(FALSE, boards[currentMove]);
5475     if (commentList[currentMove] != NULL) {
5476         DisplayComment(currentMove - 1, commentList[currentMove]);
5477     }
5478     return TRUE;
5479 }
5480
5481
5482 int
5483 LoadGameOneMove(readAhead)
5484      ChessMove readAhead;
5485 {
5486     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5487     char promoChar = NULLCHAR;
5488     ChessMove moveType;
5489     char move[MSG_SIZ];
5490     char *p, *q;
5491
5492     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
5493         gameMode != AnalyzeMode && gameMode != Training) {
5494         gameFileFP = NULL;
5495         return FALSE;
5496     }
5497
5498     yyboardindex = forwardMostMove;
5499     if (readAhead != (ChessMove)0) {
5500       moveType = readAhead;
5501     } else {
5502       if (gameFileFP == NULL)
5503           return FALSE;
5504       moveType = (ChessMove) yylex();
5505     }
5506
5507     done = FALSE;
5508     switch (moveType) {
5509       case Comment:
5510         if (appData.debugMode)
5511           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5512         p = yy_text;
5513         if (*p == '{' || *p == '[' || *p == '(') {
5514             p[strlen(p) - 1] = NULLCHAR;
5515             p++;
5516         }
5517
5518         /* append the comment but don't display it */
5519         while (*p == '\n') p++;
5520         AppendComment(currentMove, p);
5521         return TRUE;
5522
5523       case WhiteCapturesEnPassant:
5524       case BlackCapturesEnPassant:
5525       case WhitePromotionQueen:
5526       case BlackPromotionQueen:
5527       case WhitePromotionRook:
5528       case BlackPromotionRook:
5529       case WhitePromotionBishop:
5530       case BlackPromotionBishop:
5531       case WhitePromotionKnight:
5532       case BlackPromotionKnight:
5533       case WhitePromotionKing:
5534       case BlackPromotionKing:
5535       case NormalMove:
5536       case WhiteKingSideCastle:
5537       case WhiteQueenSideCastle:
5538       case BlackKingSideCastle:
5539       case BlackQueenSideCastle:
5540       case WhiteKingSideCastleWild:
5541       case WhiteQueenSideCastleWild:
5542       case BlackKingSideCastleWild:
5543       case BlackQueenSideCastleWild:
5544         if (appData.debugMode)
5545           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5546         fromX = currentMoveString[0] - 'a';
5547         fromY = currentMoveString[1] - '1';
5548         toX = currentMoveString[2] - 'a';
5549         toY = currentMoveString[3] - '1';
5550         promoChar = currentMoveString[4];
5551         break;
5552
5553       case WhiteDrop:
5554       case BlackDrop:
5555         if (appData.debugMode)
5556           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5557         fromX = moveType == WhiteDrop ?
5558           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5559         (int) CharToPiece(ToLower(currentMoveString[0]));
5560         fromY = DROP_RANK;
5561         toX = currentMoveString[2] - 'a';
5562         toY = currentMoveString[3] - '1';
5563         break;
5564
5565       case WhiteWins:
5566       case BlackWins:
5567       case GameIsDrawn:
5568       case GameUnfinished:
5569         if (appData.debugMode)
5570           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5571         p = strchr(yy_text, '{');
5572         if (p == NULL) p = strchr(yy_text, '(');
5573         if (p == NULL) {
5574             p = yy_text;
5575             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5576         } else {
5577             q = strchr(p, *p == '{' ? '}' : ')');
5578             if (q != NULL) *q = NULLCHAR;
5579             p++;
5580         }
5581         GameEnds(moveType, p, GE_FILE);
5582         done = TRUE;
5583         if (cmailMsgLoaded) {
5584             ClearHighlights();
5585             flipView = WhiteOnMove(currentMove);
5586             if (moveType == GameUnfinished) flipView = !flipView;
5587             if (appData.debugMode)
5588               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5589         }
5590         break;
5591
5592       case (ChessMove) 0:       /* end of file */
5593         if (appData.debugMode)
5594           fprintf(debugFP, "Parser hit end of file\n");
5595         switch (MateTest(boards[currentMove], PosFlags(currentMove),
5596                          EP_UNKNOWN)) {
5597           case MT_NONE:
5598           case MT_CHECK:
5599             break;
5600           case MT_CHECKMATE:
5601             if (WhiteOnMove(currentMove)) {
5602                 GameEnds(BlackWins, "Black mates", GE_FILE);
5603             } else {
5604                 GameEnds(WhiteWins, "White mates", GE_FILE);
5605             }
5606             break;
5607           case MT_STALEMATE:
5608             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5609             break;
5610         }
5611         done = TRUE;
5612         break;
5613
5614       case MoveNumberOne:
5615         if (lastLoadGameStart == GNUChessGame) {
5616             /* GNUChessGames have numbers, but they aren't move numbers */
5617             if (appData.debugMode)
5618               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5619                       yy_text, (int) moveType);
5620             return LoadGameOneMove((ChessMove)0); /* tail recursion */
5621         }
5622         /* else fall thru */
5623
5624       case XBoardGame:
5625       case GNUChessGame:
5626       case PGNTag:
5627         /* Reached start of next game in file */
5628         if (appData.debugMode)
5629           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5630         switch (MateTest(boards[currentMove], PosFlags(currentMove),
5631                          EP_UNKNOWN)) {
5632           case MT_NONE:
5633           case MT_CHECK:
5634             break;
5635           case MT_CHECKMATE:
5636             if (WhiteOnMove(currentMove)) {
5637                 GameEnds(BlackWins, "Black mates", GE_FILE);
5638             } else {
5639                 GameEnds(WhiteWins, "White mates", GE_FILE);
5640             }
5641             break;
5642           case MT_STALEMATE:
5643             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5644             break;
5645         }
5646         done = TRUE;
5647         break;
5648
5649       case PositionDiagram:     /* should not happen; ignore */
5650       case ElapsedTime:         /* ignore */
5651       case NAG:                 /* ignore */
5652         if (appData.debugMode)
5653           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5654                   yy_text, (int) moveType);
5655         return LoadGameOneMove((ChessMove)0); /* tail recursion */
5656
5657       case IllegalMove:
5658         if (appData.testLegality) {
5659             if (appData.debugMode)
5660               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5661             sprintf(move, _("Illegal move: %d.%s%s"),
5662                     (forwardMostMove / 2) + 1,
5663                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5664             DisplayError(move, 0);
5665             done = TRUE;
5666         } else {
5667             if (appData.debugMode)
5668               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5669                       yy_text, currentMoveString);
5670             fromX = currentMoveString[0] - 'a';
5671             fromY = currentMoveString[1] - '1';
5672             toX = currentMoveString[2] - 'a';
5673             toY = currentMoveString[3] - '1';
5674             promoChar = currentMoveString[4];
5675         }
5676         break;
5677
5678       case AmbiguousMove:
5679         if (appData.debugMode)
5680           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5681         sprintf(move, _("Ambiguous move: %d.%s%s"),
5682                 (forwardMostMove / 2) + 1,
5683                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5684         DisplayError(move, 0);
5685         done = TRUE;
5686         break;
5687
5688       default:
5689       case ImpossibleMove:
5690         if (appData.debugMode)
5691           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5692         sprintf(move, _("Illegal move: %d.%s%s"),
5693                 (forwardMostMove / 2) + 1,
5694                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5695         DisplayError(move, 0);
5696         done = TRUE;
5697         break;
5698     }
5699
5700     if (done) {
5701         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5702             DrawPosition(FALSE, boards[currentMove]);
5703             DisplayBothClocks();
5704             if (!appData.matchMode && commentList[currentMove] != NULL)
5705               DisplayComment(currentMove - 1, commentList[currentMove]);
5706         }
5707         (void) StopLoadGameTimer();
5708         gameFileFP = NULL;
5709         cmailOldMove = forwardMostMove;
5710         return FALSE;
5711     } else {
5712         /* currentMoveString is set as a side-effect of yylex */
5713         strcat(currentMoveString, "\n");
5714         strcpy(moveList[forwardMostMove], currentMoveString);
5715
5716         thinkOutput[0] = NULLCHAR;
5717         MakeMove(fromX, fromY, toX, toY, promoChar);
5718         currentMove = forwardMostMove;
5719         return TRUE;
5720     }
5721 }
5722
5723 /* Load the nth game from the given file */
5724 int
5725 LoadGameFromFile(filename, n, title, useList)
5726      char *filename;
5727      int n;
5728      char *title;
5729      /*Boolean*/ int useList;
5730 {
5731     FILE *f;
5732     char buf[MSG_SIZ];
5733
5734     if (strcmp(filename, "-") == 0) {
5735         f = stdin;
5736         title = "stdin";
5737     } else {
5738         f = fopen(filename, "rb");
5739         if (f == NULL) {
5740             sprintf(buf, _("Can't open \"%s\""), filename);
5741             DisplayError(buf, errno);
5742             return FALSE;
5743         }
5744     }
5745     if (fseek(f, 0, 0) == -1) {
5746         /* f is not seekable; probably a pipe */
5747         useList = FALSE;
5748     }
5749     if (useList && n == 0) {
5750         int error = GameListBuild(f);
5751         if (error) {
5752             DisplayError(_("Cannot build game list"), error);
5753         } else if (!ListEmpty(&gameList) &&
5754                    ((ListGame *) gameList.tailPred)->number > 1) {
5755             GameListPopUp(f, title);
5756             return TRUE;
5757         }
5758         GameListDestroy();
5759         n = 1;
5760     }
5761     if (n == 0) n = 1;
5762     return LoadGame(f, n, title, FALSE);
5763 }
5764
5765
5766 void
5767 MakeRegisteredMove()
5768 {
5769     int fromX, fromY, toX, toY;
5770     char promoChar;
5771     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5772         switch (cmailMoveType[lastLoadGameNumber - 1]) {
5773           case CMAIL_MOVE:
5774           case CMAIL_DRAW:
5775             if (appData.debugMode)
5776               fprintf(debugFP, "Restoring %s for game %d\n",
5777                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5778
5779             thinkOutput[0] = NULLCHAR;
5780             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5781             fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5782             fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5783             toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5784             toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5785             promoChar = cmailMove[lastLoadGameNumber - 1][4];
5786             MakeMove(fromX, fromY, toX, toY, promoChar);
5787             ShowMove(fromX, fromY, toX, toY);
5788
5789             switch (MateTest(boards[currentMove], PosFlags(currentMove),
5790                              EP_UNKNOWN)) {
5791               case MT_NONE:
5792               case MT_CHECK:
5793                 break;
5794
5795               case MT_CHECKMATE:
5796                 if (WhiteOnMove(currentMove)) {
5797                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
5798                 } else {
5799                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
5800                 }
5801                 break;
5802
5803               case MT_STALEMATE:
5804                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5805                 break;
5806             }
5807
5808             break;
5809
5810           case CMAIL_RESIGN:
5811             if (WhiteOnMove(currentMove)) {
5812                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5813             } else {
5814                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5815             }
5816             break;
5817
5818           case CMAIL_ACCEPT:
5819             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5820             break;
5821
5822           default:
5823             break;
5824         }
5825     }
5826
5827     return;
5828 }
5829
5830 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5831 int
5832 CmailLoadGame(f, gameNumber, title, useList)
5833      FILE *f;
5834      int gameNumber;
5835      char *title;
5836      int useList;
5837 {
5838     int retVal;
5839
5840     if (gameNumber > nCmailGames) {
5841         DisplayError(_("No more games in this message"), 0);
5842         return FALSE;
5843     }
5844     if (f == lastLoadGameFP) {
5845         int offset = gameNumber - lastLoadGameNumber;
5846         if (offset == 0) {
5847             cmailMsg[0] = NULLCHAR;
5848             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5849                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5850                 nCmailMovesRegistered--;
5851             }
5852             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5853             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5854                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5855             }
5856         } else {
5857             if (! RegisterMove()) return FALSE;
5858         }
5859     }
5860
5861     retVal = LoadGame(f, gameNumber, title, useList);
5862
5863     /* Make move registered during previous look at this game, if any */
5864     MakeRegisteredMove();
5865
5866     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5867         commentList[currentMove]
5868           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5869         DisplayComment(currentMove - 1, commentList[currentMove]);
5870     }
5871
5872     return retVal;
5873 }
5874
5875 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5876 int
5877 ReloadGame(offset)
5878      int offset;
5879 {
5880     int gameNumber = lastLoadGameNumber + offset;
5881     if (lastLoadGameFP == NULL) {
5882         DisplayError(_("No game has been loaded yet"), 0);
5883         return FALSE;
5884     }
5885     if (gameNumber <= 0) {
5886         DisplayError(_("Can't back up any further"), 0);
5887         return FALSE;
5888     }
5889     if (cmailMsgLoaded) {
5890         return CmailLoadGame(lastLoadGameFP, gameNumber,
5891                              lastLoadGameTitle, lastLoadGameUseList);
5892     } else {
5893         return LoadGame(lastLoadGameFP, gameNumber,
5894                         lastLoadGameTitle, lastLoadGameUseList);
5895     }
5896 }
5897
5898
5899
5900 /* Load the nth game from open file f */
5901 int
5902 LoadGame(f, gameNumber, title, useList)
5903      FILE *f;
5904      int gameNumber;
5905      char *title;
5906      int useList;
5907 {
5908     ChessMove cm;
5909     char buf[MSG_SIZ];
5910     int gn = gameNumber;
5911     ListGame *lg = NULL;
5912     int numPGNTags = 0;
5913     int err;
5914     GameMode oldGameMode;
5915
5916     if (appData.debugMode)
5917         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5918
5919     if (gameMode == Training )
5920         SetTrainingModeOff();
5921
5922     oldGameMode = gameMode;
5923     if (gameMode != BeginningOfGame) {
5924       Reset(FALSE, TRUE);
5925     }
5926
5927     gameFileFP = f;
5928     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5929         fclose(lastLoadGameFP);
5930     }
5931
5932     if (useList) {
5933         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5934
5935         if (lg) {
5936             fseek(f, lg->offset, 0);
5937             GameListHighlight(gameNumber);
5938             gn = 1;
5939         }
5940         else {
5941             DisplayError(_("Game number out of range"), 0);
5942             return FALSE;
5943         }
5944     } else {
5945         GameListDestroy();
5946         if (fseek(f, 0, 0) == -1) {
5947             if (f == lastLoadGameFP ?
5948                 gameNumber == lastLoadGameNumber + 1 :
5949                 gameNumber == 1) {
5950                 gn = 1;
5951             } else {
5952                 DisplayError(_("Can't seek on game file"), 0);
5953                 return FALSE;
5954             }
5955         }
5956     }
5957     lastLoadGameFP = f;
5958     lastLoadGameNumber = gameNumber;
5959     strcpy(lastLoadGameTitle, title);
5960     lastLoadGameUseList = useList;
5961
5962     yynewfile(f);
5963
5964
5965     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5966         sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5967                 lg->gameInfo.black);
5968             DisplayTitle(buf);
5969     } else if (*title != NULLCHAR) {
5970         if (gameNumber > 1) {
5971             sprintf(buf, "%s %d", title, gameNumber);
5972             DisplayTitle(buf);
5973         } else {
5974             DisplayTitle(title);
5975         }
5976     }
5977
5978     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5979         gameMode = PlayFromGameFile;
5980         ModeHighlight();
5981     }
5982
5983     currentMove = forwardMostMove = backwardMostMove = 0;
5984     CopyBoard(boards[0], initialPosition);
5985     StopClocks();
5986
5987     /*
5988      * Skip the first gn-1 games in the file.
5989      * Also skip over anything that precedes an identifiable
5990      * start of game marker, to avoid being confused by
5991      * garbage at the start of the file.  Currently
5992      * recognized start of game markers are the move number "1",
5993      * the pattern "gnuchess .* game", the pattern
5994      * "^[#;%] [^ ]* game file", and a PGN tag block.
5995      * A game that starts with one of the latter two patterns
5996      * will also have a move number 1, possibly
5997      * following a position diagram.
5998      * 5-4-02: Let's try being more lenient and allowing a game to
5999      * start with an unnumbered move.  Does that break anything?
6000      */
6001     cm = lastLoadGameStart = (ChessMove) 0;
6002     while (gn > 0) {
6003         yyboardindex = forwardMostMove;
6004         cm = (ChessMove) yylex();
6005         switch (cm) {
6006           case (ChessMove) 0:
6007             if (cmailMsgLoaded) {
6008                 nCmailGames = CMAIL_MAX_GAMES - gn;
6009             } else {
6010                 Reset(TRUE, TRUE);
6011                 DisplayError(_("Game not found in file"), 0);
6012             }
6013             return FALSE;
6014
6015           case GNUChessGame:
6016           case XBoardGame:
6017             gn--;
6018             lastLoadGameStart = cm;
6019             break;
6020
6021           case MoveNumberOne:
6022             switch (lastLoadGameStart) {
6023               case GNUChessGame:
6024               case XBoardGame:
6025               case PGNTag:
6026                 break;
6027               case MoveNumberOne:
6028               case (ChessMove) 0:
6029                 gn--;           /* count this game */
6030                 lastLoadGameStart = cm;
6031                 break;
6032               default:
6033                 /* impossible */
6034                 break;
6035             }
6036             break;
6037
6038           case PGNTag:
6039             switch (lastLoadGameStart) {
6040               case GNUChessGame:
6041               case PGNTag:
6042               case MoveNumberOne:
6043               case (ChessMove) 0:
6044                 gn--;           /* count this game */
6045                 lastLoadGameStart = cm;
6046                 break;
6047               case XBoardGame:
6048                 lastLoadGameStart = cm; /* game counted already */
6049                 break;
6050               default:
6051                 /* impossible */
6052                 break;
6053             }
6054             if (gn > 0) {
6055                 do {
6056                     yyboardindex = forwardMostMove;
6057                     cm = (ChessMove) yylex();
6058                 } while (cm == PGNTag || cm == Comment);
6059             }
6060             break;
6061
6062           case WhiteWins:
6063           case BlackWins:
6064           case GameIsDrawn:
6065             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
6066                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
6067                     != CMAIL_OLD_RESULT) {
6068                     nCmailResults ++ ;
6069                     cmailResult[  CMAIL_MAX_GAMES
6070                                 - gn - 1] = CMAIL_OLD_RESULT;
6071                 }
6072             }
6073             break;
6074
6075           case NormalMove:
6076             /* Only a NormalMove can be at the start of a game
6077              * without a position diagram. */
6078             if (lastLoadGameStart == (ChessMove) 0) {
6079               gn--;
6080               lastLoadGameStart = MoveNumberOne;
6081             }
6082             break;
6083
6084           default:
6085             break;
6086         }
6087     }
6088
6089     if (appData.debugMode)
6090       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6091
6092     if (cm == XBoardGame) {
6093         /* Skip any header junk before position diagram and/or move 1 */
6094         for (;;) {
6095             yyboardindex = forwardMostMove;
6096             cm = (ChessMove) yylex();
6097
6098             if (cm == (ChessMove) 0 ||
6099                 cm == GNUChessGame || cm == XBoardGame) {
6100                 /* Empty game; pretend end-of-file and handle later */
6101                 cm = (ChessMove) 0;
6102                 break;
6103             }
6104
6105             if (cm == MoveNumberOne || cm == PositionDiagram ||
6106                 cm == PGNTag || cm == Comment)
6107               break;
6108         }
6109     } else if (cm == GNUChessGame) {
6110         if (gameInfo.event != NULL) {
6111             free(gameInfo.event);
6112         }
6113         gameInfo.event = StrSave(yy_text);
6114     }
6115
6116     startedFromSetupPosition = FALSE;
6117     while (cm == PGNTag) {
6118         if (appData.debugMode)
6119           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6120         err = ParsePGNTag(yy_text, &gameInfo);
6121         if (!err) numPGNTags++;
6122
6123         if (gameInfo.fen != NULL) {
6124           Board initial_position;
6125           startedFromSetupPosition = TRUE;
6126           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6127             Reset(TRUE, TRUE);
6128             DisplayError(_("Bad FEN position in file"), 0);
6129             return FALSE;
6130           }
6131           CopyBoard(boards[0], initial_position);
6132           if (blackPlaysFirst) {
6133             currentMove = forwardMostMove = backwardMostMove = 1;
6134             CopyBoard(boards[1], initial_position);
6135             strcpy(moveList[0], "");
6136             strcpy(parseList[0], "");
6137             timeRemaining[0][1] = whiteTimeRemaining;
6138             timeRemaining[1][1] = blackTimeRemaining;
6139             if (commentList[0] != NULL) {
6140               commentList[1] = commentList[0];
6141               commentList[0] = NULL;
6142             }
6143           } else {
6144             currentMove = forwardMostMove = backwardMostMove = 0;
6145           }
6146           yyboardindex = forwardMostMove;
6147           free(gameInfo.fen);
6148           gameInfo.fen = NULL;
6149         }
6150
6151         yyboardindex = forwardMostMove;
6152         cm = (ChessMove) yylex();
6153
6154         /* Handle comments interspersed among the tags */
6155         while (cm == Comment) {
6156             char *p;
6157             if (appData.debugMode)
6158               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6159             p = yy_text;
6160             if (*p == '{' || *p == '[' || *p == '(') {
6161                 p[strlen(p) - 1] = NULLCHAR;
6162                 p++;
6163             }
6164             while (*p == '\n') p++;
6165             AppendComment(currentMove, p);
6166             yyboardindex = forwardMostMove;
6167             cm = (ChessMove) yylex();
6168         }
6169     }
6170
6171     /* don't rely on existence of Event tag since if game was
6172      * pasted from clipboard the Event tag may not exist
6173      */
6174     if (numPGNTags > 0){
6175         char *tags;
6176         if (gameInfo.variant == VariantNormal) {
6177           gameInfo.variant = StringToVariant(gameInfo.event);
6178         }
6179         if (!matchMode) {
6180           tags = PGNTags(&gameInfo);
6181           TagsPopUp(tags, CmailMsg());
6182           free(tags);
6183         }
6184     } else {
6185         /* Make something up, but don't display it now */
6186         SetGameInfo();
6187         TagsPopDown();
6188     }
6189
6190     if (cm == PositionDiagram) {
6191         int i, j;
6192         char *p;
6193         Board initial_position;
6194
6195         if (appData.debugMode)
6196           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6197
6198         if (!startedFromSetupPosition) {
6199             p = yy_text;
6200             for (i = BOARD_SIZE - 1; i >= 0; i--)
6201               for (j = 0; j < BOARD_SIZE; p++)
6202                 switch (*p) {
6203                   case '[':
6204                   case '-':
6205                   case ' ':
6206                   case '\t':
6207                   case '\n':
6208                   case '\r':
6209                     break;
6210                   default:
6211                     initial_position[i][j++] = CharToPiece(*p);
6212                     break;
6213                 }
6214             while (*p == ' ' || *p == '\t' ||
6215                    *p == '\n' || *p == '\r') p++;
6216
6217             if (strncmp(p, "black", strlen("black"))==0)
6218               blackPlaysFirst = TRUE;
6219             else
6220               blackPlaysFirst = FALSE;
6221             startedFromSetupPosition = TRUE;
6222
6223             CopyBoard(boards[0], initial_position);
6224             if (blackPlaysFirst) {
6225                 currentMove = forwardMostMove = backwardMostMove = 1;
6226                 CopyBoard(boards[1], initial_position);
6227                 strcpy(moveList[0], "");
6228                 strcpy(parseList[0], "");
6229                 timeRemaining[0][1] = whiteTimeRemaining;
6230                 timeRemaining[1][1] = blackTimeRemaining;
6231                 if (commentList[0] != NULL) {
6232                     commentList[1] = commentList[0];
6233                     commentList[0] = NULL;
6234                 }
6235             } else {
6236                 currentMove = forwardMostMove = backwardMostMove = 0;
6237             }
6238         }
6239         yyboardindex = forwardMostMove;
6240         cm = (ChessMove) yylex();
6241     }
6242
6243     if (first.pr == NoProc) {
6244         StartChessProgram(&first);
6245     }
6246     InitChessProgram(&first);
6247     SendToProgram("force\n", &first);
6248     if (startedFromSetupPosition) {
6249         SendBoard(&first, forwardMostMove);
6250         DisplayBothClocks();
6251     }
6252
6253     while (cm == Comment) {
6254         char *p;
6255         if (appData.debugMode)
6256           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6257         p = yy_text;
6258         if (*p == '{' || *p == '[' || *p == '(') {
6259             p[strlen(p) - 1] = NULLCHAR;
6260             p++;
6261         }
6262         while (*p == '\n') p++;
6263         AppendComment(currentMove, p);
6264         yyboardindex = forwardMostMove;
6265         cm = (ChessMove) yylex();
6266     }
6267
6268     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6269         cm == WhiteWins || cm == BlackWins ||
6270         cm == GameIsDrawn || cm == GameUnfinished) {
6271         DisplayMessage("", _("No moves in game"));
6272         if (cmailMsgLoaded) {
6273             if (appData.debugMode)
6274               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6275             ClearHighlights();
6276             flipView = FALSE;
6277         }
6278         DrawPosition(FALSE, boards[currentMove]);
6279         DisplayBothClocks();
6280         gameMode = EditGame;
6281         ModeHighlight();
6282         gameFileFP = NULL;
6283         cmailOldMove = 0;
6284         return TRUE;
6285     }
6286
6287     if (commentList[currentMove] != NULL) {
6288       if (!matchMode && (pausing || appData.timeDelay != 0)) {
6289         DisplayComment(currentMove - 1, commentList[currentMove]);
6290       }
6291     }
6292     if (!matchMode && appData.timeDelay != 0)
6293       DrawPosition(FALSE, boards[currentMove]);
6294
6295     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6296       programStats.ok_to_send = 1;
6297     }
6298
6299     /* if the first token after the PGN tags is a move
6300      * and not move number 1, retrieve it from the parser
6301      */
6302     if (cm != MoveNumberOne)
6303         LoadGameOneMove(cm);
6304
6305     /* load the remaining moves from the file */
6306     while (LoadGameOneMove((ChessMove)0)) {
6307       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6308       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6309     }
6310
6311     /* rewind to the start of the game */
6312     currentMove = backwardMostMove;
6313
6314     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6315
6316     if (oldGameMode == AnalyzeFile ||
6317         oldGameMode == AnalyzeMode) {
6318       AnalyzeFileEvent();
6319     }
6320
6321     if (matchMode || appData.timeDelay == 0) {
6322       ToEndEvent();
6323       gameMode = EditGame;
6324       ModeHighlight();
6325     } else if (appData.timeDelay > 0) {
6326       AutoPlayGameLoop();
6327     }
6328
6329     if (appData.debugMode)
6330         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6331     return TRUE;
6332 }
6333
6334 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6335 int
6336 ReloadPosition(offset)
6337      int offset;
6338 {
6339     int positionNumber = lastLoadPositionNumber + offset;
6340     if (lastLoadPositionFP == NULL) {
6341         DisplayError(_("No position has been loaded yet"), 0);
6342         return FALSE;
6343     }
6344     if (positionNumber <= 0) {
6345         DisplayError(_("Can't back up any further"), 0);
6346         return FALSE;
6347     }
6348     return LoadPosition(lastLoadPositionFP, positionNumber,
6349                         lastLoadPositionTitle);
6350 }
6351
6352 /* Load the nth position from the given file */
6353 int
6354 LoadPositionFromFile(filename, n, title)
6355      char *filename;
6356      int n;
6357      char *title;
6358 {
6359     FILE *f;
6360     char buf[MSG_SIZ];
6361
6362     if (strcmp(filename, "-") == 0) {
6363         return LoadPosition(stdin, n, "stdin");
6364     } else {
6365         f = fopen(filename, "rb");
6366         if (f == NULL) {
6367             sprintf(buf, _("Can't open \"%s\""), filename);
6368             DisplayError(buf, errno);
6369             return FALSE;
6370         } else {
6371             return LoadPosition(f, n, title);
6372         }
6373     }
6374 }
6375
6376 /* Load the nth position from the given open file, and close it */
6377 int
6378 LoadPosition(f, positionNumber, title)
6379      FILE *f;
6380      int positionNumber;
6381      char *title;
6382 {
6383     char *p, line[MSG_SIZ];
6384     Board initial_position;
6385     int i, j, fenMode, pn;
6386
6387     if (gameMode == Training )
6388         SetTrainingModeOff();
6389
6390     if (gameMode != BeginningOfGame) {
6391         Reset(FALSE, TRUE);
6392     }
6393     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6394         fclose(lastLoadPositionFP);
6395     }
6396     if (positionNumber == 0) positionNumber = 1;
6397     lastLoadPositionFP = f;
6398     lastLoadPositionNumber = positionNumber;
6399     strcpy(lastLoadPositionTitle, title);
6400     if (first.pr == NoProc) {
6401       StartChessProgram(&first);
6402       InitChessProgram(&first);
6403     }
6404     pn = positionNumber;
6405     if (positionNumber < 0) {
6406         /* Negative position number means to seek to that byte offset */
6407         if (fseek(f, -positionNumber, 0) == -1) {
6408             DisplayError(_("Can't seek on position file"), 0);
6409             return FALSE;
6410         };
6411         pn = 1;
6412     } else {
6413         if (fseek(f, 0, 0) == -1) {
6414             if (f == lastLoadPositionFP ?
6415                 positionNumber == lastLoadPositionNumber + 1 :
6416                 positionNumber == 1) {
6417                 pn = 1;
6418             } else {
6419                 DisplayError(_("Can't seek on position file"), 0);
6420                 return FALSE;
6421             }
6422         }
6423     }
6424     /* See if this file is FEN or old-style xboard */
6425     if (fgets(line, MSG_SIZ, f) == NULL) {
6426         DisplayError(_("Position not found in file"), 0);
6427         return FALSE;
6428     }
6429     switch (line[0]) {
6430       case '#':  case 'x':
6431       default:
6432         fenMode = FALSE;
6433         break;
6434       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
6435       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
6436       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
6437       case '7':  case '8':
6438         fenMode = TRUE;
6439         break;
6440     }
6441
6442     if (pn >= 2) {
6443         if (fenMode || line[0] == '#') pn--;
6444         while (pn > 0) {
6445             /* skip postions before number pn */
6446             if (fgets(line, MSG_SIZ, f) == NULL) {
6447                 Reset(TRUE, TRUE);
6448                 DisplayError(_("Position not found in file"), 0);
6449                 return FALSE;
6450             }
6451             if (fenMode || line[0] == '#') pn--;
6452         }
6453     }
6454
6455     if (fenMode) {
6456         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6457             DisplayError(_("Bad FEN position in file"), 0);
6458             return FALSE;
6459         }
6460     } else {
6461         (void) fgets(line, MSG_SIZ, f);
6462         (void) fgets(line, MSG_SIZ, f);
6463
6464         for (i = BOARD_SIZE - 1; i >= 0; i--) {
6465             (void) fgets(line, MSG_SIZ, f);
6466             for (p = line, j = 0; j < BOARD_SIZE; p++) {
6467                 if (*p == ' ')
6468                   continue;
6469                 initial_position[i][j++] = CharToPiece(*p);
6470             }
6471         }
6472
6473         blackPlaysFirst = FALSE;
6474         if (!feof(f)) {
6475             (void) fgets(line, MSG_SIZ, f);
6476             if (strncmp(line, "black", strlen("black"))==0)
6477               blackPlaysFirst = TRUE;
6478         }
6479     }
6480     startedFromSetupPosition = TRUE;
6481
6482     SendToProgram("force\n", &first);
6483     CopyBoard(boards[0], initial_position);
6484     if (blackPlaysFirst) {
6485         currentMove = forwardMostMove = backwardMostMove = 1;
6486         strcpy(moveList[0], "");
6487         strcpy(parseList[0], "");
6488         CopyBoard(boards[1], initial_position);
6489         DisplayMessage("", _("Black to play"));
6490     } else {
6491         currentMove = forwardMostMove = backwardMostMove = 0;
6492         DisplayMessage("", _("White to play"));
6493     }
6494     SendBoard(&first, forwardMostMove);
6495
6496     if (positionNumber > 1) {
6497         sprintf(line, "%s %d", title, positionNumber);
6498         DisplayTitle(line);
6499     } else {
6500         DisplayTitle(title);
6501     }
6502     gameMode = EditGame;
6503     ModeHighlight();
6504     ResetClocks();
6505     timeRemaining[0][1] = whiteTimeRemaining;
6506     timeRemaining[1][1] = blackTimeRemaining;
6507     DrawPosition(FALSE, boards[currentMove]);
6508
6509     return TRUE;
6510 }
6511
6512
6513 void
6514 CopyPlayerNameIntoFileName(dest, src)
6515      char **dest, *src;
6516 {
6517     while (*src != NULLCHAR && *src != ',') {
6518         if (*src == ' ') {
6519             *(*dest)++ = '_';
6520             src++;
6521         } else {
6522             *(*dest)++ = *src++;
6523         }
6524     }
6525 }
6526
6527 char *DefaultFileName(ext)
6528      char *ext;
6529 {
6530     static char def[MSG_SIZ];
6531     char *p;
6532
6533     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6534         p = def;
6535         CopyPlayerNameIntoFileName(&p, gameInfo.white);
6536         *p++ = '-';
6537         CopyPlayerNameIntoFileName(&p, gameInfo.black);
6538         *p++ = '.';
6539         strcpy(p, ext);
6540     } else {
6541         def[0] = NULLCHAR;
6542     }
6543     return def;
6544 }
6545
6546 /* Save the current game to the given file */
6547 int
6548 SaveGameToFile(filename, append)
6549      char *filename;
6550      int append;
6551 {
6552     FILE *f;
6553     char buf[MSG_SIZ];
6554
6555     if (strcmp(filename, "-") == 0) {
6556         return SaveGame(stdout, 0, NULL);
6557     } else {
6558         f = fopen(filename, append ? "a" : "w");
6559         if (f == NULL) {
6560             sprintf(buf, _("Can't open \"%s\""), filename);
6561             DisplayError(buf, errno);
6562             return FALSE;
6563         } else {
6564             return SaveGame(f, 0, NULL);
6565         }
6566     }
6567 }
6568
6569 char *
6570 SavePart(str)
6571      char *str;
6572 {
6573     static char buf[MSG_SIZ];
6574     char *p;
6575
6576     p = strchr(str, ' ');
6577     if (p == NULL) return str;
6578     strncpy(buf, str, p - str);
6579     buf[p - str] = NULLCHAR;
6580     return buf;
6581 }
6582
6583 #define PGN_MAX_LINE 75
6584
6585 /* Save game in PGN style and close the file */
6586 int
6587 SaveGamePGN(f)
6588      FILE *f;
6589 {
6590     int i, offset, linelen, newblock;
6591     time_t tm;
6592     char *movetext;
6593     char numtext[32];
6594     int movelen, numlen, blank;
6595
6596     tm = time((time_t *) NULL);
6597
6598     PrintPGNTags(f, &gameInfo);
6599
6600     if (backwardMostMove > 0 || startedFromSetupPosition) {
6601         char *fen = PositionToFEN(backwardMostMove);
6602         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6603         fprintf(f, "\n{--------------\n");
6604         PrintPosition(f, backwardMostMove);
6605         fprintf(f, "--------------}\n");
6606         free(fen);
6607     } else {
6608         fprintf(f, "\n");
6609     }
6610
6611     i = backwardMostMove;
6612     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6613     linelen = 0;
6614     newblock = TRUE;
6615
6616     while (i < forwardMostMove) {
6617         /* Print comments preceding this move */
6618         if (commentList[i] != NULL) {
6619             if (linelen > 0) fprintf(f, "\n");
6620             fprintf(f, "{\n%s}\n", commentList[i]);
6621             linelen = 0;
6622             newblock = TRUE;
6623         }
6624
6625         /* Format move number */
6626         if ((i % 2) == 0) {
6627             sprintf(numtext, "%d.", (i - offset)/2 + 1);
6628         } else {
6629             if (newblock) {
6630                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6631             } else {
6632                 numtext[0] = NULLCHAR;
6633             }
6634         }
6635         numlen = strlen(numtext);
6636         newblock = FALSE;
6637
6638         /* Print move number */
6639         blank = linelen > 0 && numlen > 0;
6640         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6641             fprintf(f, "\n");
6642             linelen = 0;
6643             blank = 0;
6644         }
6645         if (blank) {
6646             fprintf(f, " ");
6647             linelen++;
6648         }
6649         fprintf(f, numtext);
6650         linelen += numlen;
6651
6652         /* Get move */
6653         movetext = SavePart(parseList[i]);
6654         movelen = strlen(movetext);
6655
6656         /* Print move */
6657         blank = linelen > 0 && movelen > 0;
6658         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6659             fprintf(f, "\n");
6660             linelen = 0;
6661             blank = 0;
6662         }
6663         if (blank) {
6664             fprintf(f, " ");
6665             linelen++;
6666         }
6667         fprintf(f, movetext);
6668         linelen += movelen;
6669
6670         i++;
6671     }
6672
6673     /* Start a new line */
6674     if (linelen > 0) fprintf(f, "\n");
6675
6676     /* Print comments after last move */
6677     if (commentList[i] != NULL) {
6678         fprintf(f, "{\n%s}\n", commentList[i]);
6679     }
6680
6681     /* Print result */
6682     if (gameInfo.resultDetails != NULL &&
6683         gameInfo.resultDetails[0] != NULLCHAR) {
6684         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6685                 PGNResult(gameInfo.result));
6686     } else {
6687         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6688     }
6689
6690     fclose(f);
6691     return TRUE;
6692 }
6693
6694 /* Save game in old style and close the file */
6695 int
6696 SaveGameOldStyle(f)
6697      FILE *f;
6698 {
6699     int i, offset;
6700     time_t tm;
6701
6702     tm = time((time_t *) NULL);
6703
6704     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6705     PrintOpponents(f);
6706
6707     if (backwardMostMove > 0 || startedFromSetupPosition) {
6708         fprintf(f, "\n[--------------\n");
6709         PrintPosition(f, backwardMostMove);
6710         fprintf(f, "--------------]\n");
6711     } else {
6712         fprintf(f, "\n");
6713     }
6714
6715     i = backwardMostMove;
6716     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6717
6718     while (i < forwardMostMove) {
6719         if (commentList[i] != NULL) {
6720             fprintf(f, "[%s]\n", commentList[i]);
6721         }
6722
6723         if ((i % 2) == 1) {
6724             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
6725             i++;
6726         } else {
6727             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
6728             i++;
6729             if (commentList[i] != NULL) {
6730                 fprintf(f, "\n");
6731                 continue;
6732             }
6733             if (i >= forwardMostMove) {
6734                 fprintf(f, "\n");
6735                 break;
6736             }
6737             fprintf(f, "%s\n", parseList[i]);
6738             i++;
6739         }
6740     }
6741
6742     if (commentList[i] != NULL) {
6743         fprintf(f, "[%s]\n", commentList[i]);
6744     }
6745
6746     /* This isn't really the old style, but it's close enough */
6747     if (gameInfo.resultDetails != NULL &&
6748         gameInfo.resultDetails[0] != NULLCHAR) {
6749         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6750                 gameInfo.resultDetails);
6751     } else {
6752         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6753     }
6754
6755     fclose(f);
6756     return TRUE;
6757 }
6758
6759 /* Save the current game to open file f and close the file */
6760 int
6761 SaveGame(f, dummy, dummy2)
6762      FILE *f;
6763      int dummy;
6764      char *dummy2;
6765 {
6766     if (gameMode == EditPosition) EditPositionDone();
6767     if (appData.oldSaveStyle)
6768       return SaveGameOldStyle(f);
6769     else
6770       return SaveGamePGN(f);
6771 }
6772
6773 /* Save the current position to the given file */
6774 int
6775 SavePositionToFile(filename)
6776      char *filename;
6777 {
6778     FILE *f;
6779     char buf[MSG_SIZ];
6780
6781     if (strcmp(filename, "-") == 0) {
6782         return SavePosition(stdout, 0, NULL);
6783     } else {
6784         f = fopen(filename, "a");
6785         if (f == NULL) {
6786             sprintf(buf, _("Can't open \"%s\""), filename);
6787             DisplayError(buf, errno);
6788             return FALSE;
6789         } else {
6790             SavePosition(f, 0, NULL);
6791             return TRUE;
6792         }
6793     }
6794 }
6795
6796 /* Save the current position to the given open file and close the file */
6797 int
6798 SavePosition(f, dummy, dummy2)
6799      FILE *f;
6800      int dummy;
6801      char *dummy2;
6802 {
6803     time_t tm;
6804     char *fen;
6805
6806     if (appData.oldSaveStyle) {
6807         tm = time((time_t *) NULL);
6808
6809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6810         PrintOpponents(f);
6811         fprintf(f, "[--------------\n");
6812         PrintPosition(f, currentMove);
6813         fprintf(f, "--------------]\n");
6814     } else {
6815         fen = PositionToFEN(currentMove);
6816         fprintf(f, "%s\n", fen);
6817         free(fen);
6818     }
6819     fclose(f);
6820     return TRUE;
6821 }
6822
6823 void
6824 ReloadCmailMsgEvent(unregister)
6825      int unregister;
6826 {
6827 #if !WIN32
6828     static char *inFilename = NULL;
6829     static char *outFilename;
6830     int i;
6831     struct stat inbuf, outbuf;
6832     int status;
6833
6834     /* Any registered moves are unregistered if unregister is set, */
6835     /* i.e. invoked by the signal handler */
6836     if (unregister) {
6837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6838             cmailMoveRegistered[i] = FALSE;
6839             if (cmailCommentList[i] != NULL) {
6840                 free(cmailCommentList[i]);
6841                 cmailCommentList[i] = NULL;
6842             }
6843         }
6844         nCmailMovesRegistered = 0;
6845     }
6846
6847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6848         cmailResult[i] = CMAIL_NOT_RESULT;
6849     }
6850     nCmailResults = 0;
6851
6852     if (inFilename == NULL) {
6853         /* Because the filenames are static they only get malloced once  */
6854         /* and they never get freed                                      */
6855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6857
6858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6859         sprintf(outFilename, "%s.out", appData.cmailGameName);
6860     }
6861
6862     status = stat(outFilename, &outbuf);
6863     if (status < 0) {
6864         cmailMailedMove = FALSE;
6865     } else {
6866         status = stat(inFilename, &inbuf);
6867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6868     }
6869
6870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6871        counts the games, notes how each one terminated, etc.
6872
6873        It would be nice to remove this kludge and instead gather all
6874        the information while building the game list.  (And to keep it
6875        in the game list nodes instead of having a bunch of fixed-size
6876        parallel arrays.)  Note this will require getting each game's
6877        termination from the PGN tags, as the game list builder does
6878        not process the game moves.  --mann
6879        */
6880     cmailMsgLoaded = TRUE;
6881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6882
6883     /* Load first game in the file or popup game menu */
6884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6885
6886 #endif /* !WIN32 */
6887     return;
6888 }
6889
6890 int
6891 RegisterMove()
6892 {
6893     FILE *f;
6894     char string[MSG_SIZ];
6895
6896     if (   cmailMailedMove
6897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6898         return TRUE;            /* Allow free viewing  */
6899     }
6900
6901     /* Unregister move to ensure that we don't leave RegisterMove        */
6902     /* with the move registered when the conditions for registering no   */
6903     /* longer hold                                                       */
6904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6906         nCmailMovesRegistered --;
6907
6908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
6909           {
6910               free(cmailCommentList[lastLoadGameNumber - 1]);
6911               cmailCommentList[lastLoadGameNumber - 1] = NULL;
6912           }
6913     }
6914
6915     if (cmailOldMove == -1) {
6916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
6917         return FALSE;
6918     }
6919
6920     if (currentMove > cmailOldMove + 1) {
6921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
6922         return FALSE;
6923     }
6924
6925     if (currentMove < cmailOldMove) {
6926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
6927         return FALSE;
6928     }
6929
6930     if (forwardMostMove > currentMove) {
6931         /* Silently truncate extra moves */
6932         TruncateGame();
6933     }
6934
6935     if (   (currentMove == cmailOldMove + 1)
6936         || (   (currentMove == cmailOldMove)
6937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6939         if (gameInfo.result != GameUnfinished) {
6940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6941         }
6942
6943         if (commentList[currentMove] != NULL) {
6944             cmailCommentList[lastLoadGameNumber - 1]
6945               = StrSave(commentList[currentMove]);
6946         }
6947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6948
6949         if (appData.debugMode)
6950           fprintf(debugFP, "Saving %s for game %d\n",
6951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6952
6953         sprintf(string,
6954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6955
6956         f = fopen(string, "w");
6957         if (appData.oldSaveStyle) {
6958             SaveGameOldStyle(f); /* also closes the file */
6959
6960             sprintf(string, "%s.pos.out", appData.cmailGameName);
6961             f = fopen(string, "w");
6962             SavePosition(f, 0, NULL); /* also closes the file */
6963         } else {
6964             fprintf(f, "{--------------\n");
6965             PrintPosition(f, currentMove);
6966             fprintf(f, "--------------}\n\n");
6967
6968             SaveGame(f, 0, NULL); /* also closes the file*/
6969         }
6970
6971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6972         nCmailMovesRegistered ++;
6973     } else if (nCmailGames == 1) {
6974         DisplayError(_("You have not made a move yet"), 0);
6975         return FALSE;
6976     }
6977
6978     return TRUE;
6979 }
6980
6981 void
6982 MailMoveEvent()
6983 {
6984 #if !WIN32
6985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6986     FILE *commandOutput;
6987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
6989     int nBuffers;
6990     int i;
6991     int archived;
6992     char *arcDir;
6993
6994     if (! cmailMsgLoaded) {
6995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
6996         return;
6997     }
6998
6999     if (nCmailGames == nCmailResults) {
7000         DisplayError(_("No unfinished games"), 0);
7001         return;
7002     }
7003
7004 #if CMAIL_PROHIBIT_REMAIL
7005     if (cmailMailedMove) {
7006         sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
7007         DisplayError(msg, 0);
7008         return;
7009     }
7010 #endif
7011
7012     if (! (cmailMailedMove || RegisterMove())) return;
7013
7014     if (   cmailMailedMove
7015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
7016         sprintf(string, partCommandString,
7017                 appData.debugMode ? " -v" : "", appData.cmailGameName);
7018         commandOutput = popen(string, "r");
7019
7020         if (commandOutput == NULL) {
7021             DisplayError(_("Failed to invoke cmail"), 0);
7022         } else {
7023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
7024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
7025             }
7026             if (nBuffers > 1) {
7027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
7028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
7029                 nBytes = MSG_SIZ - 1;
7030             } else {
7031                 (void) memcpy(msg, buffer, nBytes);
7032             }
7033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
7034
7035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
7036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
7037
7038                 archived = TRUE;
7039                 for (i = 0; i < nCmailGames; i ++) {
7040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
7041                         archived = FALSE;
7042                     }
7043                 }
7044                 if (   archived
7045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
7046                         != NULL)) {
7047                     sprintf(buffer, "%s/%s.%s.archive",
7048                             arcDir,
7049                             appData.cmailGameName,
7050                             gameInfo.date);
7051                     LoadGameFromFile(buffer, 1, buffer, FALSE);
7052                     cmailMsgLoaded = FALSE;
7053                 }
7054             }
7055
7056             DisplayInformation(msg);
7057             pclose(commandOutput);
7058         }
7059     } else {
7060         if ((*cmailMsg) != '\0') {
7061             DisplayInformation(cmailMsg);
7062         }
7063     }
7064
7065     return;
7066 #endif /* !WIN32 */
7067 }
7068
7069 char *
7070 CmailMsg()
7071 {
7072 #if WIN32
7073     return NULL;
7074 #else
7075     int  prependComma = 0;
7076     char number[5];
7077     char string[MSG_SIZ];       /* Space for game-list */
7078     int  i;
7079
7080     if (!cmailMsgLoaded) return "";
7081
7082     if (cmailMailedMove) {
7083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
7084     } else {
7085         /* Create a list of games left */
7086         sprintf(string, "[");
7087         for (i = 0; i < nCmailGames; i ++) {
7088             if (! (   cmailMoveRegistered[i]
7089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7090                 if (prependComma) {
7091                     sprintf(number, ",%d", i + 1);
7092                 } else {
7093                     sprintf(number, "%d", i + 1);
7094                     prependComma = 1;
7095                 }
7096
7097                 strcat(string, number);
7098             }
7099         }
7100         strcat(string, "]");
7101
7102         if (nCmailMovesRegistered + nCmailResults == 0) {
7103             switch (nCmailGames) {
7104               case 1:
7105                 sprintf(cmailMsg,
7106                         _("Still need to make move for game\n"));
7107                 break;
7108
7109               case 2:
7110                 sprintf(cmailMsg,
7111                         _("Still need to make moves for both games\n"));
7112                 break;
7113
7114               default:
7115                 sprintf(cmailMsg,
7116                         _("Still need to make moves for all %d games\n"),
7117                         nCmailGames);
7118                 break;
7119             }
7120         } else {
7121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7122               case 1:
7123                 sprintf(cmailMsg,
7124                         _("Still need to make a move for game %s\n"),
7125                         string);
7126                 break;
7127
7128               case 0:
7129                 if (nCmailResults == nCmailGames) {
7130                     sprintf(cmailMsg, _("No unfinished games\n"));
7131                 } else {
7132                     sprintf(cmailMsg, _("Ready to send mail\n"));
7133                 }
7134                 break;
7135
7136               default:
7137                 sprintf(cmailMsg,
7138                         _("Still need to make moves for games %s\n"),
7139                         string);
7140             }
7141         }
7142     }
7143     return cmailMsg;
7144 #endif /* WIN32 */
7145 }
7146
7147 void
7148 ResetGameEvent()
7149 {
7150     if (gameMode == Training)
7151       SetTrainingModeOff();
7152
7153     Reset(TRUE, TRUE);
7154     cmailMsgLoaded = FALSE;
7155     if (appData.icsActive) {
7156       SendToICS(ics_prefix);
7157       SendToICS("refresh\n");
7158     }
7159 }
7160
7161 static int exiting = 0;
7162
7163 void
7164 ExitEvent(status)
7165      int status;
7166 {
7167     exiting++;
7168     if (exiting > 2) {
7169       /* Give up on clean exit */
7170       exit(status);
7171     }
7172     if (exiting > 1) {
7173       /* Keep trying for clean exit */
7174       return;
7175     }
7176
7177     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7178
7179     if (telnetISR != NULL) {
7180       RemoveInputSource(telnetISR);
7181     }
7182     if (icsPR != NoProc) {
7183       DestroyChildProcess(icsPR, TRUE);
7184     }
7185     /* Save game if resource set and not already saved by GameEnds() */
7186     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7187       if (*appData.saveGameFile != NULLCHAR) {
7188         SaveGameToFile(appData.saveGameFile, TRUE);
7189       } else if (appData.autoSaveGames) {
7190         AutoSaveGame();
7191       }
7192       if (*appData.savePositionFile != NULLCHAR) {
7193         SavePositionToFile(appData.savePositionFile);
7194       }
7195     }
7196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7197
7198     /* Kill off chess programs */
7199     if (first.pr != NoProc) {
7200         ExitAnalyzeMode();
7201         SendToProgram("quit\n", &first);
7202         DestroyChildProcess(first.pr, first.useSigterm);
7203     }
7204     if (second.pr != NoProc) {
7205         SendToProgram("quit\n", &second);
7206         DestroyChildProcess(second.pr, second.useSigterm);
7207     }
7208     if (first.isr != NULL) {
7209         RemoveInputSource(first.isr);
7210     }
7211     if (second.isr != NULL) {
7212         RemoveInputSource(second.isr);
7213     }
7214
7215     ShutDownFrontEnd();
7216     exit(status);
7217 }
7218
7219 void
7220 PauseEvent()
7221 {
7222     if (appData.debugMode)
7223         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7224     if (pausing) {
7225         pausing = FALSE;
7226         ModeHighlight();
7227         if (gameMode == MachinePlaysWhite ||
7228             gameMode == MachinePlaysBlack) {
7229             StartClocks();
7230         } else {
7231             DisplayBothClocks();
7232         }
7233         if (gameMode == PlayFromGameFile) {
7234             if (appData.timeDelay >= 0)
7235                 AutoPlayGameLoop();
7236         } else if (gameMode == IcsExamining && pauseExamInvalid) {
7237             Reset(FALSE, TRUE);
7238             SendToICS(ics_prefix);
7239             SendToICS("refresh\n");
7240         } else if (currentMove < forwardMostMove) {
7241             ForwardInner(forwardMostMove);
7242         }
7243         pauseExamInvalid = FALSE;
7244     } else {
7245         switch (gameMode) {
7246           default:
7247             return;
7248           case IcsExamining:
7249             pauseExamForwardMostMove = forwardMostMove;
7250             pauseExamInvalid = FALSE;
7251             /* fall through */
7252           case IcsObserving:
7253           case IcsPlayingWhite:
7254           case IcsPlayingBlack:
7255             pausing = TRUE;
7256             ModeHighlight();
7257             return;
7258           case PlayFromGameFile:
7259             (void) StopLoadGameTimer();
7260             pausing = TRUE;
7261             ModeHighlight();
7262             break;
7263           case BeginningOfGame:
7264             if (appData.icsActive) return;
7265             /* else fall through */
7266           case MachinePlaysWhite:
7267           case MachinePlaysBlack:
7268           case TwoMachinesPlay:
7269             if (forwardMostMove == 0)
7270               return;           /* don't pause if no one has moved */
7271             if ((gameMode == MachinePlaysWhite &&
7272                  !WhiteOnMove(forwardMostMove)) ||
7273                 (gameMode == MachinePlaysBlack &&
7274                  WhiteOnMove(forwardMostMove))) {
7275                 StopClocks();
7276             }
7277             pausing = TRUE;
7278             ModeHighlight();
7279             break;
7280         }
7281     }
7282 }
7283
7284 void
7285 EditCommentEvent()
7286 {
7287     char title[MSG_SIZ];
7288
7289     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7290         strcpy(title, _("Edit comment"));
7291     } else {
7292         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
7293                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7294                 parseList[currentMove - 1]);
7295     }
7296
7297     EditCommentPopUp(currentMove, title, commentList[currentMove]);
7298 }
7299
7300
7301 void
7302 EditTagsEvent()
7303 {
7304     char *tags = PGNTags(&gameInfo);
7305     EditTagsPopUp(tags);
7306     free(tags);
7307 }
7308
7309 void
7310 AnalyzeModeEvent()
7311 {
7312     if (appData.noChessProgram || gameMode == AnalyzeMode)
7313       return;
7314
7315     if (gameMode != AnalyzeFile) {
7316                 if (!appData.icsEngineAnalyze) {
7317                         EditGameEvent();
7318                 if (gameMode != EditGame) return;
7319                 }
7320         ResurrectChessProgram();
7321         SendToProgram("analyze\n", &first);
7322         first.analyzing = TRUE;
7323         /*first.maybeThinking = TRUE;*/
7324         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7325         AnalysisPopUp(_("Analysis"),
7326                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7327     }
7328     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
7329     pausing = FALSE;
7330     ModeHighlight();
7331     SetGameInfo();
7332
7333     StartAnalysisClock();
7334     GetTimeMark(&lastNodeCountTime);
7335     lastNodeCount = 0;
7336 }
7337
7338 void
7339 AnalyzeFileEvent()
7340 {
7341     if (appData.noChessProgram || gameMode == AnalyzeFile)
7342       return;
7343
7344     if (gameMode != AnalyzeMode) {
7345         EditGameEvent();
7346         if (gameMode != EditGame) return;
7347         ResurrectChessProgram();
7348         SendToProgram("analyze\n", &first);
7349         first.analyzing = TRUE;
7350         /*first.maybeThinking = TRUE;*/
7351         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7352         AnalysisPopUp(_("Analysis"),
7353                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
7354     }
7355     gameMode = AnalyzeFile;
7356     pausing = FALSE;
7357     ModeHighlight();
7358     SetGameInfo();
7359
7360     StartAnalysisClock();
7361     GetTimeMark(&lastNodeCountTime);
7362     lastNodeCount = 0;
7363 }
7364
7365 void
7366 MachineWhiteEvent()
7367 {
7368     char buf[MSG_SIZ];
7369
7370     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7371       return;
7372
7373
7374     if (gameMode == PlayFromGameFile ||
7375         gameMode == TwoMachinesPlay  ||
7376         gameMode == Training         ||
7377         gameMode == AnalyzeMode      ||
7378         gameMode == EndOfGame)
7379         EditGameEvent();
7380
7381     if (gameMode == EditPosition)
7382         EditPositionDone();
7383
7384     if (!WhiteOnMove(currentMove)) {
7385         DisplayError(_("It is not White's turn"), 0);
7386         return;
7387     }
7388
7389     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7390       ExitAnalyzeMode();
7391
7392     if (gameMode == EditGame || gameMode == AnalyzeMode ||
7393         gameMode == AnalyzeFile)
7394         TruncateGame();
7395
7396     ResurrectChessProgram();    /* in case it isn't running */
7397     gameMode = MachinePlaysWhite;
7398     pausing = FALSE;
7399     ModeHighlight();
7400     SetGameInfo();
7401     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7402     DisplayTitle(buf);
7403     if (first.sendName) {
7404       sprintf(buf, "name %s\n", gameInfo.black);
7405       SendToProgram(buf, &first);
7406     }
7407     if (first.sendTime) {
7408       if (first.useColors) {
7409         SendToProgram("black\n", &first); /*gnu kludge*/
7410       }
7411       SendTimeRemaining(&first, TRUE);
7412     }
7413     if (first.useColors) {
7414       SendToProgram("white\ngo\n", &first);
7415     } else {
7416       SendToProgram("go\n", &first);
7417     }
7418     SetMachineThinkingEnables();
7419     first.maybeThinking = TRUE;
7420     StartClocks();
7421
7422     if (appData.autoFlipView && !flipView) {
7423       flipView = !flipView;
7424       DrawPosition(FALSE, NULL);
7425     }
7426 }
7427
7428 void
7429 MachineBlackEvent()
7430 {
7431     char buf[MSG_SIZ];
7432
7433     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7434         return;
7435
7436
7437     if (gameMode == PlayFromGameFile ||
7438         gameMode == TwoMachinesPlay  ||
7439         gameMode == Training         ||
7440         gameMode == AnalyzeMode      ||
7441         gameMode == EndOfGame)
7442         EditGameEvent();
7443
7444     if (gameMode == EditPosition)
7445         EditPositionDone();
7446
7447     if (WhiteOnMove(currentMove)) {
7448         DisplayError(_("It is not Black's turn"), 0);
7449         return;
7450     }
7451
7452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7453       ExitAnalyzeMode();
7454
7455     if (gameMode == EditGame || gameMode == AnalyzeMode ||
7456         gameMode == AnalyzeFile)
7457         TruncateGame();
7458
7459     ResurrectChessProgram();    /* in case it isn't running */
7460     gameMode = MachinePlaysBlack;
7461     pausing = FALSE;
7462     ModeHighlight();
7463     SetGameInfo();
7464     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7465     DisplayTitle(buf);
7466     if (first.sendName) {
7467       sprintf(buf, "name %s\n", gameInfo.white);
7468       SendToProgram(buf, &first);
7469     }
7470     if (first.sendTime) {
7471       if (first.useColors) {
7472         SendToProgram("white\n", &first); /*gnu kludge*/
7473       }
7474       SendTimeRemaining(&first, FALSE);
7475     }
7476     if (first.useColors) {
7477       SendToProgram("black\ngo\n", &first);
7478     } else {
7479       SendToProgram("go\n", &first);
7480     }
7481     SetMachineThinkingEnables();
7482     first.maybeThinking = TRUE;
7483     StartClocks();
7484
7485     if (appData.autoFlipView && flipView) {
7486       flipView = !flipView;
7487       DrawPosition(FALSE, NULL);
7488     }
7489 }
7490
7491
7492 void
7493 DisplayTwoMachinesTitle()
7494 {
7495     char buf[MSG_SIZ];
7496     if (appData.matchGames > 0) {
7497         if (first.twoMachinesColor[0] == 'w') {
7498             sprintf(buf, "%s vs. %s (%d-%d-%d)",
7499                     gameInfo.white, gameInfo.black,
7500                     first.matchWins, second.matchWins,
7501                     matchGame - 1 - (first.matchWins + second.matchWins));
7502         } else {
7503             sprintf(buf, "%s vs. %s (%d-%d-%d)",
7504                     gameInfo.white, gameInfo.black,
7505                     second.matchWins, first.matchWins,
7506                     matchGame - 1 - (first.matchWins + second.matchWins));
7507         }
7508     } else {
7509         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7510     }
7511     DisplayTitle(buf);
7512 }
7513
7514 void
7515 TwoMachinesEvent P((void))
7516 {
7517     int i;
7518     char buf[MSG_SIZ];
7519     ChessProgramState *onmove;
7520
7521     if (appData.noChessProgram) return;
7522
7523     switch (gameMode) {
7524       case TwoMachinesPlay:
7525         return;
7526       case MachinePlaysWhite:
7527       case MachinePlaysBlack:
7528         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7529             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
7530             return;
7531         }
7532         /* fall through */
7533       case BeginningOfGame:
7534       case PlayFromGameFile:
7535       case EndOfGame:
7536         EditGameEvent();
7537         if (gameMode != EditGame) return;
7538         break;
7539       case EditPosition:
7540         EditPositionDone();
7541         break;
7542       case AnalyzeMode:
7543       case AnalyzeFile:
7544         ExitAnalyzeMode();
7545         break;
7546       case EditGame:
7547       default:
7548         break;
7549     }
7550
7551     forwardMostMove = currentMove;
7552     ResurrectChessProgram();    /* in case first program isn't running */
7553
7554     if (second.pr == NULL) {
7555         StartChessProgram(&second);
7556         if (second.protocolVersion == 1) {
7557           TwoMachinesEventIfReady();
7558         } else {
7559           /* kludge: allow timeout for initial "feature" command */
7560           FreezeUI();
7561           DisplayMessage("", _("Starting second chess program"));
7562           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7563         }
7564         return;
7565     }
7566     DisplayMessage("", "");
7567     InitChessProgram(&second);
7568     SendToProgram("force\n", &second);
7569     if (startedFromSetupPosition) {
7570         SendBoard(&second, backwardMostMove);
7571     }
7572     for (i = backwardMostMove; i < forwardMostMove; i++) {
7573         SendMoveToProgram(i, &second);
7574     }
7575
7576     gameMode = TwoMachinesPlay;
7577     pausing = FALSE;
7578     ModeHighlight();
7579     SetGameInfo();
7580     DisplayTwoMachinesTitle();
7581     firstMove = TRUE;
7582     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7583         onmove = &first;
7584     } else {
7585         onmove = &second;
7586     }
7587
7588     SendToProgram(first.computerString, &first);
7589     if (first.sendName) {
7590       sprintf(buf, "name %s\n", second.tidy);
7591       SendToProgram(buf, &first);
7592     }
7593     SendToProgram(second.computerString, &second);
7594     if (second.sendName) {
7595       sprintf(buf, "name %s\n", first.tidy);
7596       SendToProgram(buf, &second);
7597     }
7598
7599     if (!first.sendTime || !second.sendTime) {
7600         ResetClocks();
7601         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7602         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7603     }
7604     if (onmove->sendTime) {
7605       if (onmove->useColors) {
7606         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7607       }
7608       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7609     }
7610     if (onmove->useColors) {
7611       SendToProgram(onmove->twoMachinesColor, onmove);
7612     }
7613     SendToProgram("go\n", onmove);
7614     onmove->maybeThinking = TRUE;
7615     SetMachineThinkingEnables();
7616
7617     StartClocks();
7618 }
7619
7620 void
7621 TrainingEvent()
7622 {
7623     if (gameMode == Training) {
7624       SetTrainingModeOff();
7625       gameMode = PlayFromGameFile;
7626       DisplayMessage("", _("Training mode off"));
7627     } else {
7628       gameMode = Training;
7629       animateTraining = appData.animate;
7630
7631       /* make sure we are not already at the end of the game */
7632       if (currentMove < forwardMostMove) {
7633         SetTrainingModeOn();
7634         DisplayMessage("", _("Training mode on"));
7635       } else {
7636         gameMode = PlayFromGameFile;
7637         DisplayError(_("Already at end of game"), 0);
7638       }
7639     }
7640     ModeHighlight();
7641 }
7642
7643 void
7644 IcsClientEvent()
7645 {
7646     if (!appData.icsActive) return;
7647     switch (gameMode) {
7648       case IcsPlayingWhite:
7649       case IcsPlayingBlack:
7650       case IcsObserving:
7651       case IcsIdle:
7652       case BeginningOfGame:
7653       case IcsExamining:
7654         return;
7655
7656       case EditGame:
7657         break;
7658
7659       case EditPosition:
7660         EditPositionDone();
7661         break;
7662
7663       case AnalyzeMode:
7664       case AnalyzeFile:
7665         ExitAnalyzeMode();
7666         break;
7667
7668       default:
7669         EditGameEvent();
7670         break;
7671     }
7672
7673     gameMode = IcsIdle;
7674     ModeHighlight();
7675     return;
7676 }
7677
7678
7679 void
7680 EditGameEvent()
7681 {
7682     int i;
7683
7684     switch (gameMode) {
7685       case Training:
7686         SetTrainingModeOff();
7687         break;
7688       case MachinePlaysWhite:
7689       case MachinePlaysBlack:
7690       case BeginningOfGame:
7691         SendToProgram("force\n", &first);
7692         SetUserThinkingEnables();
7693         break;
7694       case PlayFromGameFile:
7695         (void) StopLoadGameTimer();
7696         if (gameFileFP != NULL) {
7697             gameFileFP = NULL;
7698         }
7699         break;
7700       case EditPosition:
7701         EditPositionDone();
7702         break;
7703       case AnalyzeMode:
7704       case AnalyzeFile:
7705         ExitAnalyzeMode();
7706         SendToProgram("force\n", &first);
7707         break;
7708       case TwoMachinesPlay:
7709         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7710         ResurrectChessProgram();
7711         SetUserThinkingEnables();
7712         break;
7713       case EndOfGame:
7714         ResurrectChessProgram();
7715         break;
7716       case IcsPlayingBlack:
7717       case IcsPlayingWhite:
7718         DisplayError(_("Warning: You are still playing a game"), 0);
7719         break;
7720       case IcsObserving:
7721         DisplayError(_("Warning: You are still observing a game"), 0);
7722         break;
7723       case IcsExamining:
7724         DisplayError(_("Warning: You are still examining a game"), 0);
7725         break;
7726       case IcsIdle:
7727         break;
7728       case EditGame:
7729       default:
7730         return;
7731     }
7732
7733     pausing = FALSE;
7734     StopClocks();
7735     first.offeredDraw = second.offeredDraw = 0;
7736
7737     if (gameMode == PlayFromGameFile) {
7738         whiteTimeRemaining = timeRemaining[0][currentMove];
7739         blackTimeRemaining = timeRemaining[1][currentMove];
7740         DisplayTitle("");
7741     }
7742
7743     if (gameMode == MachinePlaysWhite ||
7744         gameMode == MachinePlaysBlack ||
7745         gameMode == TwoMachinesPlay ||
7746         gameMode == EndOfGame) {
7747         i = forwardMostMove;
7748         while (i > currentMove) {
7749             SendToProgram("undo\n", &first);
7750             i--;
7751         }
7752         whiteTimeRemaining = timeRemaining[0][currentMove];
7753         blackTimeRemaining = timeRemaining[1][currentMove];
7754         DisplayBothClocks();
7755         if (whiteFlag || blackFlag) {
7756             whiteFlag = blackFlag = 0;
7757         }
7758         DisplayTitle("");
7759     }
7760
7761     gameMode = EditGame;
7762     ModeHighlight();
7763     SetGameInfo();
7764 }
7765
7766
7767 void
7768 EditPositionEvent()
7769 {
7770     if (gameMode == EditPosition) {
7771         EditGameEvent();
7772         return;
7773     }
7774
7775     EditGameEvent();
7776     if (gameMode != EditGame) return;
7777
7778     gameMode = EditPosition;
7779     ModeHighlight();
7780     SetGameInfo();
7781     if (currentMove > 0)
7782       CopyBoard(boards[0], boards[currentMove]);
7783
7784     blackPlaysFirst = !WhiteOnMove(currentMove);
7785     ResetClocks();
7786     currentMove = forwardMostMove = backwardMostMove = 0;
7787     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7788     DisplayMove(-1);
7789 }
7790
7791 void
7792 ExitAnalyzeMode()
7793 {
7794         /* icsEngineAnalyze - possible call from other functions */
7795         if (appData.icsEngineAnalyze) {
7796             appData.icsEngineAnalyze = FALSE;
7797                 DisplayMessage("","Close ICS engine analyze...");
7798         }
7799     if (first.analysisSupport && first.analyzing) {
7800       SendToProgram("exit\n", &first);
7801       first.analyzing = FALSE;
7802     }
7803     AnalysisPopDown();
7804     thinkOutput[0] = NULLCHAR;
7805 }
7806
7807 void
7808 EditPositionDone()
7809 {
7810     startedFromSetupPosition = TRUE;
7811     InitChessProgram(&first);
7812     SendToProgram("force\n", &first);
7813     if (blackPlaysFirst) {
7814         strcpy(moveList[0], "");
7815         strcpy(parseList[0], "");
7816         currentMove = forwardMostMove = backwardMostMove = 1;
7817         CopyBoard(boards[1], boards[0]);
7818     } else {
7819         currentMove = forwardMostMove = backwardMostMove = 0;
7820     }
7821     SendBoard(&first, forwardMostMove);
7822     DisplayTitle("");
7823     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7824     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7825     gameMode = EditGame;
7826     ModeHighlight();
7827     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7828 }
7829
7830 /* Pause for `ms' milliseconds */
7831 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7832 void
7833 TimeDelay(ms)
7834      long ms;
7835 {
7836     TimeMark m1, m2;
7837
7838     GetTimeMark(&m1);
7839     do {
7840         GetTimeMark(&m2);
7841     } while (SubtractTimeMarks(&m2, &m1) < ms);
7842 }
7843
7844 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7845 void
7846 SendMultiLineToICS(buf)
7847      char *buf;
7848 {
7849     char temp[MSG_SIZ+1], *p;
7850     int len;
7851
7852     len = strlen(buf);
7853     if (len > MSG_SIZ)
7854       len = MSG_SIZ;
7855
7856     strncpy(temp, buf, len);
7857     temp[len] = 0;
7858
7859     p = temp;
7860     while (*p) {
7861         if (*p == '\n' || *p == '\r')
7862           *p = ' ';
7863         ++p;
7864     }
7865
7866     strcat(temp, "\n");
7867     SendToICS(temp);
7868     SendToPlayer(temp, strlen(temp));
7869 }
7870
7871 void
7872 SetWhiteToPlayEvent()
7873 {
7874     if (gameMode == EditPosition) {
7875         blackPlaysFirst = FALSE;
7876         DisplayBothClocks();    /* works because currentMove is 0 */
7877     } else if (gameMode == IcsExamining) {
7878         SendToICS(ics_prefix);
7879         SendToICS("tomove white\n");
7880     }
7881 }
7882
7883 void
7884 SetBlackToPlayEvent()
7885 {
7886     if (gameMode == EditPosition) {
7887         blackPlaysFirst = TRUE;
7888         currentMove = 1;        /* kludge */
7889         DisplayBothClocks();
7890         currentMove = 0;
7891     } else if (gameMode == IcsExamining) {
7892         SendToICS(ics_prefix);
7893         SendToICS("tomove black\n");
7894     }
7895 }
7896
7897 void
7898 EditPositionMenuEvent(selection, x, y)
7899      ChessSquare selection;
7900      int x, y;
7901 {
7902     char buf[MSG_SIZ];
7903
7904     if (gameMode != EditPosition && gameMode != IcsExamining) return;
7905
7906     switch (selection) {
7907       case ClearBoard:
7908         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7909             SendToICS(ics_prefix);
7910             SendToICS("bsetup clear\n");
7911         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7912             SendToICS(ics_prefix);
7913             SendToICS("clearboard\n");
7914         } else {
7915             for (x = 0; x < BOARD_SIZE; x++) {
7916                 for (y = 0; y < BOARD_SIZE; y++) {
7917                     if (gameMode == IcsExamining) {
7918                         if (boards[currentMove][y][x] != EmptySquare) {
7919                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
7920                                     'a' + x, '1' + y);
7921                             SendToICS(buf);
7922                         }
7923                     } else {
7924                         boards[0][y][x] = EmptySquare;
7925                     }
7926                 }
7927             }
7928         }
7929         if (gameMode == EditPosition) {
7930             DrawPosition(FALSE, boards[0]);
7931         }
7932         break;
7933
7934       case WhitePlay:
7935         SetWhiteToPlayEvent();
7936         break;
7937
7938       case BlackPlay:
7939         SetBlackToPlayEvent();
7940         break;
7941
7942       case EmptySquare:
7943         if (gameMode == IcsExamining) {
7944             sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7945             SendToICS(buf);
7946         } else {
7947             boards[0][y][x] = EmptySquare;
7948             DrawPosition(FALSE, boards[0]);
7949         }
7950         break;
7951
7952       default:
7953         if (gameMode == IcsExamining) {
7954             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7955                     PieceToChar(selection), 'a' + x, '1' + y);
7956             SendToICS(buf);
7957         } else {
7958             boards[0][y][x] = selection;
7959             DrawPosition(FALSE, boards[0]);
7960         }
7961         break;
7962     }
7963 }
7964
7965
7966 void
7967 DropMenuEvent(selection, x, y)
7968      ChessSquare selection;
7969      int x, y;
7970 {
7971     ChessMove moveType;
7972
7973     switch (gameMode) {
7974       case IcsPlayingWhite:
7975       case MachinePlaysBlack:
7976         if (!WhiteOnMove(currentMove)) {
7977             DisplayMoveError(_("It is Black's turn"));
7978             return;
7979         }
7980         moveType = WhiteDrop;
7981         break;
7982       case IcsPlayingBlack:
7983       case MachinePlaysWhite:
7984         if (WhiteOnMove(currentMove)) {
7985             DisplayMoveError(_("It is White's turn"));
7986             return;
7987         }
7988         moveType = BlackDrop;
7989         break;
7990       case EditGame:
7991         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7992         break;
7993       default:
7994         return;
7995     }
7996
7997     if (moveType == BlackDrop && selection < BlackPawn) {
7998       selection = (ChessSquare) ((int) selection
7999                                  + (int) BlackPawn - (int) WhitePawn);
8000     }
8001     if (boards[currentMove][y][x] != EmptySquare) {
8002         DisplayMoveError(_("That square is occupied"));
8003         return;
8004     }
8005
8006     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
8007 }
8008
8009 void
8010 AcceptEvent()
8011 {
8012     /* Accept a pending offer of any kind from opponent */
8013
8014     if (appData.icsActive) {
8015         SendToICS(ics_prefix);
8016         SendToICS("accept\n");
8017     } else if (cmailMsgLoaded) {
8018         if (currentMove == cmailOldMove &&
8019             commentList[cmailOldMove] != NULL &&
8020             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8021                    "Black offers a draw" : "White offers a draw")) {
8022             TruncateGame();
8023             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8024             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8025         } else {
8026             DisplayError(_("There is no pending offer on this move"), 0);
8027             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8028         }
8029     } else {
8030         /* Not used for offers from chess program */
8031     }
8032 }
8033
8034 void
8035 DeclineEvent()
8036 {
8037     /* Decline a pending offer of any kind from opponent */
8038
8039     if (appData.icsActive) {
8040         SendToICS(ics_prefix);
8041         SendToICS("decline\n");
8042     } else if (cmailMsgLoaded) {
8043         if (currentMove == cmailOldMove &&
8044             commentList[cmailOldMove] != NULL &&
8045             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8046                    "Black offers a draw" : "White offers a draw")) {
8047 #ifdef NOTDEF
8048             AppendComment(cmailOldMove, "Draw declined");
8049             DisplayComment(cmailOldMove - 1, "Draw declined");
8050 #endif /*NOTDEF*/
8051         } else {
8052             DisplayError(_("There is no pending offer on this move"), 0);
8053         }
8054     } else {
8055         /* Not used for offers from chess program */
8056     }
8057 }
8058
8059 void
8060 RematchEvent()
8061 {
8062     /* Issue ICS rematch command */
8063     if (appData.icsActive) {
8064         SendToICS(ics_prefix);
8065         SendToICS("rematch\n");
8066     }
8067 }
8068
8069 void
8070 CallFlagEvent()
8071 {
8072     /* Call your opponent's flag (claim a win on time) */
8073     if (appData.icsActive) {
8074         SendToICS(ics_prefix);
8075         SendToICS("flag\n");
8076     } else {
8077         switch (gameMode) {
8078           default:
8079             return;
8080           case MachinePlaysWhite:
8081             if (whiteFlag) {
8082                 if (blackFlag)
8083                   GameEnds(GameIsDrawn, "Both players ran out of time",
8084                            GE_PLAYER);
8085                 else
8086                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8087             } else {
8088                 DisplayError(_("Your opponent is not out of time"), 0);
8089             }
8090             break;
8091           case MachinePlaysBlack:
8092             if (blackFlag) {
8093                 if (whiteFlag)
8094                   GameEnds(GameIsDrawn, "Both players ran out of time",
8095                            GE_PLAYER);
8096                 else
8097                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8098             } else {
8099                 DisplayError(_("Your opponent is not out of time"), 0);
8100             }
8101             break;
8102         }
8103     }
8104 }
8105
8106 void
8107 DrawEvent()
8108 {
8109     /* Offer draw or accept pending draw offer from opponent */
8110
8111     if (appData.icsActive) {
8112         /* Note: tournament rules require draw offers to be
8113            made after you make your move but before you punch
8114            your clock.  Currently ICS doesn't let you do that;
8115            instead, you immediately punch your clock after making
8116            a move, but you can offer a draw at any time. */
8117
8118         SendToICS(ics_prefix);
8119         SendToICS("draw\n");
8120     } else if (cmailMsgLoaded) {
8121         if (currentMove == cmailOldMove &&
8122             commentList[cmailOldMove] != NULL &&
8123             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8124                    "Black offers a draw" : "White offers a draw")) {
8125             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8127         } else if (currentMove == cmailOldMove + 1) {
8128             char *offer = WhiteOnMove(cmailOldMove) ?
8129               "White offers a draw" : "Black offers a draw";
8130             AppendComment(currentMove, offer);
8131             DisplayComment(currentMove - 1, offer);
8132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8133         } else {
8134             DisplayError(_("You must make your move before offering a draw"), 0);
8135             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8136         }
8137     } else if (first.offeredDraw) {
8138         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8139     } else {
8140         if (first.sendDrawOffers) {
8141             SendToProgram("draw\n", &first);
8142             userOfferedDraw = TRUE;
8143         }
8144     }
8145 }
8146
8147 void
8148 AdjournEvent()
8149 {
8150     /* Offer Adjourn or accept pending Adjourn offer from opponent */
8151
8152     if (appData.icsActive) {
8153         SendToICS(ics_prefix);
8154         SendToICS("adjourn\n");
8155     } else {
8156         /* Currently GNU Chess doesn't offer or accept Adjourns */
8157     }
8158 }
8159
8160
8161 void
8162 AbortEvent()
8163 {
8164     /* Offer Abort or accept pending Abort offer from opponent */
8165
8166     if (appData.icsActive) {
8167         SendToICS(ics_prefix);
8168         SendToICS("abort\n");
8169     } else {
8170         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8171     }
8172 }
8173
8174 void
8175 ResignEvent()
8176 {
8177     /* Resign.  You can do this even if it's not your turn. */
8178
8179     if (appData.icsActive) {
8180         SendToICS(ics_prefix);
8181         SendToICS("resign\n");
8182     } else {
8183         switch (gameMode) {
8184           case MachinePlaysWhite:
8185             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8186             break;
8187           case MachinePlaysBlack:
8188             GameEnds(BlackWins, "White resigns", GE_PLAYER);
8189             break;
8190           case EditGame:
8191             if (cmailMsgLoaded) {
8192                 TruncateGame();
8193                 if (WhiteOnMove(cmailOldMove)) {
8194                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
8195                 } else {
8196                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8197                 }
8198                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8199             }
8200             break;
8201           default:
8202             break;
8203         }
8204     }
8205 }
8206
8207
8208 void
8209 StopObservingEvent()
8210 {
8211     /* Stop observing current games */
8212     SendToICS(ics_prefix);
8213     SendToICS("unobserve\n");
8214 }
8215
8216 void
8217 StopExaminingEvent()
8218 {
8219     /* Stop observing current game */
8220     SendToICS(ics_prefix);
8221     SendToICS("unexamine\n");
8222 }
8223
8224 void
8225 ForwardInner(target)
8226      int target;
8227 {
8228     int limit;
8229
8230     if (appData.debugMode)
8231         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8232                 target, currentMove, forwardMostMove);
8233
8234     if (gameMode == EditPosition)
8235       return;
8236
8237     if (gameMode == PlayFromGameFile && !pausing)
8238       PauseEvent();
8239
8240     if (gameMode == IcsExamining && pausing)
8241       limit = pauseExamForwardMostMove;
8242     else
8243       limit = forwardMostMove;
8244
8245     if (target > limit) target = limit;
8246
8247     if (target > 0 && moveList[target - 1][0]) {
8248         int fromX, fromY, toX, toY;
8249         toX = moveList[target - 1][2] - 'a';
8250         toY = moveList[target - 1][3] - '1';
8251         if (moveList[target - 1][1] == '@') {
8252             if (appData.highlightLastMove) {
8253                 SetHighlights(-1, -1, toX, toY);
8254             }
8255         } else {
8256             fromX = moveList[target - 1][0] - 'a';
8257             fromY = moveList[target - 1][1] - '1';
8258             if (target == currentMove + 1) {
8259                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8260             }
8261             if (appData.highlightLastMove) {
8262                 SetHighlights(fromX, fromY, toX, toY);
8263             }
8264         }
8265     }
8266     if (gameMode == EditGame || gameMode == AnalyzeMode ||
8267         gameMode == Training || gameMode == PlayFromGameFile ||
8268         gameMode == AnalyzeFile) {
8269         while (currentMove < target) {
8270             SendMoveToProgram(currentMove++, &first);
8271         }
8272     } else {
8273         currentMove = target;
8274     }
8275
8276     if (gameMode == EditGame || gameMode == EndOfGame) {
8277         whiteTimeRemaining = timeRemaining[0][currentMove];
8278         blackTimeRemaining = timeRemaining[1][currentMove];
8279     }
8280     DisplayBothClocks();
8281     DisplayMove(currentMove - 1);
8282     DrawPosition(FALSE, boards[currentMove]);
8283     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8284     if (commentList[currentMove] && !matchMode && gameMode != Training) {
8285         DisplayComment(currentMove - 1, commentList[currentMove]);
8286     }
8287 }
8288
8289
8290 void
8291 ForwardEvent()
8292 {
8293     if (gameMode == IcsExamining && !pausing) {
8294         SendToICS(ics_prefix);
8295         SendToICS("forward\n");
8296     } else {
8297         ForwardInner(currentMove + 1);
8298     }
8299 }
8300
8301 void
8302 ToEndEvent()
8303 {
8304     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8305         /* to optimze, we temporarily turn off analysis mode while we feed
8306          * the remaining moves to the engine. Otherwise we get analysis output
8307          * after each move.
8308          */
8309         if (first.analysisSupport) {
8310           SendToProgram("exit\nforce\n", &first);
8311           first.analyzing = FALSE;
8312         }
8313     }
8314
8315     if (gameMode == IcsExamining && !pausing) {
8316         SendToICS(ics_prefix);
8317         SendToICS("forward 999999\n");
8318     } else {
8319         ForwardInner(forwardMostMove);
8320     }
8321
8322     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8323         /* we have fed all the moves, so reactivate analysis mode */
8324         SendToProgram("analyze\n", &first);
8325         first.analyzing = TRUE;
8326         /*first.maybeThinking = TRUE;*/
8327         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8328     }
8329 }
8330
8331 void
8332 BackwardInner(target)
8333      int target;
8334 {
8335     if (appData.debugMode)
8336         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8337                 target, currentMove, forwardMostMove);
8338
8339     if (gameMode == EditPosition) return;
8340     if (currentMove <= backwardMostMove) {
8341         ClearHighlights();
8342         DrawPosition(FALSE, boards[currentMove]);
8343         return;
8344     }
8345     if (gameMode == PlayFromGameFile && !pausing)
8346       PauseEvent();
8347
8348     if (moveList[target][0]) {
8349         int fromX, fromY, toX, toY;
8350         toX = moveList[target][2] - 'a';
8351         toY = moveList[target][3] - '1';
8352         if (moveList[target][1] == '@') {
8353             if (appData.highlightLastMove) {
8354                 SetHighlights(-1, -1, toX, toY);
8355             }
8356         } else {
8357             fromX = moveList[target][0] - 'a';
8358             fromY = moveList[target][1] - '1';
8359             if (target == currentMove - 1) {
8360                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8361             }
8362             if (appData.highlightLastMove) {
8363                 SetHighlights(fromX, fromY, toX, toY);
8364             }
8365         }
8366     }
8367     if (gameMode == EditGame || gameMode==AnalyzeMode ||
8368         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8369         while (currentMove > target) {
8370             SendToProgram("undo\n", &first);
8371             currentMove--;
8372         }
8373     } else {
8374         currentMove = target;
8375     }
8376
8377     if (gameMode == EditGame || gameMode == EndOfGame) {
8378         whiteTimeRemaining = timeRemaining[0][currentMove];
8379         blackTimeRemaining = timeRemaining[1][currentMove];
8380     }
8381     DisplayBothClocks();
8382     DisplayMove(currentMove - 1);
8383     DrawPosition(FALSE, boards[currentMove]);
8384     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8385     if (commentList[currentMove] != NULL) {
8386         DisplayComment(currentMove - 1, commentList[currentMove]);
8387     }
8388 }
8389
8390 void
8391 BackwardEvent()
8392 {
8393     if (gameMode == IcsExamining && !pausing) {
8394         SendToICS(ics_prefix);
8395         SendToICS("backward\n");
8396     } else {
8397         BackwardInner(currentMove - 1);
8398     }
8399 }
8400
8401 void
8402 ToStartEvent()
8403 {
8404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8405         /* to optimze, we temporarily turn off analysis mode while we undo
8406          * all the moves. Otherwise we get analysis output after each undo.
8407          */
8408         if (first.analysisSupport) {
8409           SendToProgram("exit\nforce\n", &first);
8410           first.analyzing = FALSE;
8411         }
8412     }
8413
8414     if (gameMode == IcsExamining && !pausing) {
8415         SendToICS(ics_prefix);
8416         SendToICS("backward 999999\n");
8417     } else {
8418         BackwardInner(backwardMostMove);
8419     }
8420
8421     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8422         /* we have fed all the moves, so reactivate analysis mode */
8423         SendToProgram("analyze\n", &first);
8424         first.analyzing = TRUE;
8425         /*first.maybeThinking = TRUE;*/
8426         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8427     }
8428 }
8429
8430 void
8431 ToNrEvent(int to)
8432 {
8433   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8434   if (to >= forwardMostMove) to = forwardMostMove;
8435   if (to <= backwardMostMove) to = backwardMostMove;
8436   if (to < currentMove) {
8437     BackwardInner(to);
8438   } else {
8439     ForwardInner(to);
8440   }
8441 }
8442
8443 void
8444 RevertEvent()
8445 {
8446     if (gameMode != IcsExamining) {
8447         DisplayError(_("You are not examining a game"), 0);
8448         return;
8449     }
8450     if (pausing) {
8451         DisplayError(_("You can't revert while pausing"), 0);
8452         return;
8453     }
8454     SendToICS(ics_prefix);
8455     SendToICS("revert\n");
8456 }
8457
8458 void
8459 RetractMoveEvent()
8460 {
8461     switch (gameMode) {
8462       case MachinePlaysWhite:
8463       case MachinePlaysBlack:
8464         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8465             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
8466             return;
8467         }
8468         if (forwardMostMove < 2) return;
8469         currentMove = forwardMostMove = forwardMostMove - 2;
8470         whiteTimeRemaining = timeRemaining[0][currentMove];
8471         blackTimeRemaining = timeRemaining[1][currentMove];
8472         DisplayBothClocks();
8473         DisplayMove(currentMove - 1);
8474         ClearHighlights();/*!! could figure this out*/
8475         DrawPosition(FALSE, boards[currentMove]);
8476         SendToProgram("remove\n", &first);
8477         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8478         break;
8479
8480       case BeginningOfGame:
8481       default:
8482         break;
8483
8484       case IcsPlayingWhite:
8485       case IcsPlayingBlack:
8486         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8487             SendToICS(ics_prefix);
8488             SendToICS("takeback 2\n");
8489         } else {
8490             SendToICS(ics_prefix);
8491             SendToICS("takeback 1\n");
8492         }
8493         break;
8494     }
8495 }
8496
8497 void
8498 MoveNowEvent()
8499 {
8500     ChessProgramState *cps;
8501
8502     switch (gameMode) {
8503       case MachinePlaysWhite:
8504         if (!WhiteOnMove(forwardMostMove)) {
8505             DisplayError(_("It is your turn"), 0);
8506             return;
8507         }
8508         cps = &first;
8509         break;
8510       case MachinePlaysBlack:
8511         if (WhiteOnMove(forwardMostMove)) {
8512             DisplayError(_("It is your turn"), 0);
8513             return;
8514         }
8515         cps = &first;
8516         break;
8517       case TwoMachinesPlay:
8518         if (WhiteOnMove(forwardMostMove) ==
8519             (first.twoMachinesColor[0] == 'w')) {
8520             cps = &first;
8521         } else {
8522             cps = &second;
8523         }
8524         break;
8525       case BeginningOfGame:
8526       default:
8527         return;
8528     }
8529     SendToProgram("?\n", cps);
8530 }
8531
8532 void
8533 TruncateGameEvent()
8534 {
8535     EditGameEvent();
8536     if (gameMode != EditGame) return;
8537     TruncateGame();
8538 }
8539
8540 void
8541 TruncateGame()
8542 {
8543     if (forwardMostMove > currentMove) {
8544         if (gameInfo.resultDetails != NULL) {
8545             free(gameInfo.resultDetails);
8546             gameInfo.resultDetails = NULL;
8547             gameInfo.result = GameUnfinished;
8548         }
8549         forwardMostMove = currentMove;
8550         HistorySet(parseList, backwardMostMove, forwardMostMove,
8551                    currentMove-1);
8552     }
8553 }
8554
8555 void
8556 HintEvent()
8557 {
8558     if (appData.noChessProgram) return;
8559     switch (gameMode) {
8560       case MachinePlaysWhite:
8561         if (WhiteOnMove(forwardMostMove)) {
8562             DisplayError(_("Wait until your turn"), 0);
8563             return;
8564         }
8565         break;
8566       case BeginningOfGame:
8567       case MachinePlaysBlack:
8568         if (!WhiteOnMove(forwardMostMove)) {
8569             DisplayError(_("Wait until your turn"), 0);
8570             return;
8571         }
8572         break;
8573       default:
8574         DisplayError(_("No hint available"), 0);
8575         return;
8576     }
8577     SendToProgram("hint\n", &first);
8578     hintRequested = TRUE;
8579 }
8580
8581 void
8582 BookEvent()
8583 {
8584     if (appData.noChessProgram) return;
8585     switch (gameMode) {
8586       case MachinePlaysWhite:
8587         if (WhiteOnMove(forwardMostMove)) {
8588             DisplayError(_("Wait until your turn"), 0);
8589             return;
8590         }
8591         break;
8592       case BeginningOfGame:
8593       case MachinePlaysBlack:
8594         if (!WhiteOnMove(forwardMostMove)) {
8595             DisplayError(_("Wait until your turn"), 0);
8596             return;
8597         }
8598         break;
8599       case EditPosition:
8600         EditPositionDone();
8601         break;
8602       case TwoMachinesPlay:
8603         return;
8604       default:
8605         break;
8606     }
8607     SendToProgram("bk\n", &first);
8608     bookOutput[0] = NULLCHAR;
8609     bookRequested = TRUE;
8610 }
8611
8612 void
8613 AboutGameEvent()
8614 {
8615     char *tags = PGNTags(&gameInfo);
8616     TagsPopUp(tags, CmailMsg());
8617     free(tags);
8618 }
8619
8620 /* end button procedures */
8621
8622 void
8623 PrintPosition(fp, move)
8624      FILE *fp;
8625      int move;
8626 {
8627     int i, j;
8628
8629     for (i = BOARD_SIZE - 1; i >= 0; i--) {
8630         for (j = 0; j < BOARD_SIZE; j++) {
8631             char c = PieceToChar(boards[move][i][j]);
8632             fputc(c == 'x' ? '.' : c, fp);
8633             fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8634         }
8635     }
8636     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8637       fprintf(fp, "white to play\n");
8638     else
8639       fprintf(fp, "black to play\n");
8640 }
8641
8642 void
8643 PrintOpponents(fp)
8644      FILE *fp;
8645 {
8646     if (gameInfo.white != NULL) {
8647         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8648     } else {
8649         fprintf(fp, "\n");
8650     }
8651 }
8652
8653 /* Find last component of program's own name, using some heuristics */
8654 void
8655 TidyProgramName(prog, host, buf)
8656      char *prog, *host, buf[MSG_SIZ];
8657 {
8658     char *p, *q;
8659     int local = (strcmp(host, "localhost") == 0);
8660     while (!local && (p = strchr(prog, ';')) != NULL) {
8661         p++;
8662         while (*p == ' ') p++;
8663         prog = p;
8664     }
8665     if (*prog == '"' || *prog == '\'') {
8666         q = strchr(prog + 1, *prog);
8667     } else {
8668         q = strchr(prog, ' ');
8669     }
8670     if (q == NULL) q = prog + strlen(prog);
8671     p = q;
8672     while (p >= prog && *p != '/' && *p != '\\') p--;
8673     p++;
8674     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8675     memcpy(buf, p, q - p);
8676     buf[q - p] = NULLCHAR;
8677     if (!local) {
8678         strcat(buf, "@");
8679         strcat(buf, host);
8680     }
8681 }
8682
8683 char *
8684 TimeControlTagValue()
8685 {
8686     char buf[MSG_SIZ];
8687     if (!appData.clockMode) {
8688         strcpy(buf, "-");
8689     } else if (movesPerSession > 0) {
8690         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8691     } else if (timeIncrement == 0) {
8692         sprintf(buf, "%ld", timeControl/1000);
8693     } else {
8694         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8695     }
8696     return StrSave(buf);
8697 }
8698
8699 void
8700 SetGameInfo()
8701 {
8702     /* This routine is used only for certain modes */
8703     VariantClass v = gameInfo.variant;
8704     ClearGameInfo(&gameInfo);
8705     gameInfo.variant = v;
8706
8707     switch (gameMode) {
8708       case MachinePlaysWhite:
8709         gameInfo.event = StrSave("Computer chess game");
8710         gameInfo.site = StrSave(HostName());
8711         gameInfo.date = PGNDate();
8712         gameInfo.round = StrSave("-");
8713         gameInfo.white = StrSave(first.tidy);
8714         gameInfo.black = StrSave(UserName());
8715         gameInfo.timeControl = TimeControlTagValue();
8716         break;
8717
8718       case MachinePlaysBlack:
8719         gameInfo.event = StrSave("Computer chess game");
8720         gameInfo.site = StrSave(HostName());
8721         gameInfo.date = PGNDate();
8722         gameInfo.round = StrSave("-");
8723         gameInfo.white = StrSave(UserName());
8724         gameInfo.black = StrSave(first.tidy);
8725         gameInfo.timeControl = TimeControlTagValue();
8726         break;
8727
8728       case TwoMachinesPlay:
8729         gameInfo.event = StrSave("Computer chess game");
8730         gameInfo.site = StrSave(HostName());
8731         gameInfo.date = PGNDate();
8732         if (matchGame > 0) {
8733             char buf[MSG_SIZ];
8734             sprintf(buf, "%d", matchGame);
8735             gameInfo.round = StrSave(buf);
8736         } else {
8737             gameInfo.round = StrSave("-");
8738         }
8739         if (first.twoMachinesColor[0] == 'w') {
8740             gameInfo.white = StrSave(first.tidy);
8741             gameInfo.black = StrSave(second.tidy);
8742         } else {
8743             gameInfo.white = StrSave(second.tidy);
8744             gameInfo.black = StrSave(first.tidy);
8745         }
8746         gameInfo.timeControl = TimeControlTagValue();
8747         break;
8748
8749       case EditGame:
8750         gameInfo.event = StrSave("Edited game");
8751         gameInfo.site = StrSave(HostName());
8752         gameInfo.date = PGNDate();
8753         gameInfo.round = StrSave("-");
8754         gameInfo.white = StrSave("-");
8755         gameInfo.black = StrSave("-");
8756         break;
8757
8758       case EditPosition:
8759         gameInfo.event = StrSave("Edited position");
8760         gameInfo.site = StrSave(HostName());
8761         gameInfo.date = PGNDate();
8762         gameInfo.round = StrSave("-");
8763         gameInfo.white = StrSave("-");
8764         gameInfo.black = StrSave("-");
8765         break;
8766
8767       case IcsPlayingWhite:
8768       case IcsPlayingBlack:
8769       case IcsObserving:
8770       case IcsExamining:
8771         break;
8772
8773       case PlayFromGameFile:
8774         gameInfo.event = StrSave("Game from non-PGN file");
8775         gameInfo.site = StrSave(HostName());
8776         gameInfo.date = PGNDate();
8777         gameInfo.round = StrSave("-");
8778         gameInfo.white = StrSave("?");
8779         gameInfo.black = StrSave("?");
8780         break;
8781
8782       default:
8783         break;
8784     }
8785 }
8786
8787 void
8788 ReplaceComment(index, text)
8789      int index;
8790      char *text;
8791 {
8792     int len;
8793
8794     while (*text == '\n') text++;
8795     len = strlen(text);
8796     while (len > 0 && text[len - 1] == '\n') len--;
8797
8798     if (commentList[index] != NULL)
8799       free(commentList[index]);
8800
8801     if (len == 0) {
8802         commentList[index] = NULL;
8803         return;
8804     }
8805     commentList[index] = (char *) malloc(len + 2);
8806     strncpy(commentList[index], text, len);
8807     commentList[index][len] = '\n';
8808     commentList[index][len + 1] = NULLCHAR;
8809 }
8810
8811 void
8812 CrushCRs(text)
8813      char *text;
8814 {
8815   char *p = text;
8816   char *q = text;
8817   char ch;
8818
8819   do {
8820     ch = *p++;
8821     if (ch == '\r') continue;
8822     *q++ = ch;
8823   } while (ch != '\0');
8824 }
8825
8826 void
8827 AppendComment(index, text)
8828      int index;
8829      char *text;
8830 {
8831     int oldlen, len;
8832     char *old;
8833
8834     CrushCRs(text);
8835     while (*text == '\n') text++;
8836     len = strlen(text);
8837     while (len > 0 && text[len - 1] == '\n') len--;
8838
8839     if (len == 0) return;
8840
8841     if (commentList[index] != NULL) {
8842         old = commentList[index];
8843         oldlen = strlen(old);
8844         commentList[index] = (char *) malloc(oldlen + len + 2);
8845         strcpy(commentList[index], old);
8846         free(old);
8847         strncpy(&commentList[index][oldlen], text, len);
8848         commentList[index][oldlen + len] = '\n';
8849         commentList[index][oldlen + len + 1] = NULLCHAR;
8850     } else {
8851         commentList[index] = (char *) malloc(len + 2);
8852         strncpy(commentList[index], text, len);
8853         commentList[index][len] = '\n';
8854         commentList[index][len + 1] = NULLCHAR;
8855     }
8856 }
8857
8858 void
8859 SendToProgram(message, cps)
8860      char *message;
8861      ChessProgramState *cps;
8862 {
8863     int count, outCount, error;
8864     char buf[MSG_SIZ];
8865
8866     if (cps->pr == NULL) return;
8867     Attention(cps);
8868
8869     if (appData.debugMode) {
8870         TimeMark now;
8871         GetTimeMark(&now);
8872         fprintf(debugFP, "%ld >%-6s: %s",
8873                 SubtractTimeMarks(&now, &programStartTime),
8874                 cps->which, message);
8875     }
8876
8877     count = strlen(message);
8878     outCount = OutputToProcess(cps->pr, message, count, &error);
8879     if (outCount < count && !exiting) {
8880         sprintf(buf, _("Error writing to %s chess program"), cps->which);
8881         DisplayFatalError(buf, error, 1);
8882     }
8883 }
8884
8885 void
8886 ReceiveFromProgram(isr, closure, message, count, error)
8887      InputSourceRef isr;
8888      VOIDSTAR closure;
8889      char *message;
8890      int count;
8891      int error;
8892 {
8893     char *end_str;
8894     char buf[MSG_SIZ];
8895     ChessProgramState *cps = (ChessProgramState *)closure;
8896
8897     if (isr != cps->isr) return; /* Killed intentionally */
8898     if (count <= 0) {
8899         if (count == 0) {
8900             sprintf(buf,
8901                     _("Error: %s chess program (%s) exited unexpectedly"),
8902                     cps->which, cps->program);
8903             RemoveInputSource(cps->isr);
8904             DisplayFatalError(buf, 0, 1);
8905         } else {
8906             sprintf(buf,
8907                     _("Error reading from %s chess program (%s)"),
8908                     cps->which, cps->program);
8909             RemoveInputSource(cps->isr);
8910             DisplayFatalError(buf, error, 1);
8911         }
8912         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8913         return;
8914     }
8915
8916     if ((end_str = strchr(message, '\r')) != NULL)
8917       *end_str = NULLCHAR;
8918     if ((end_str = strchr(message, '\n')) != NULL)
8919       *end_str = NULLCHAR;
8920
8921     if (appData.debugMode) {
8922         TimeMark now;
8923         GetTimeMark(&now);
8924         fprintf(debugFP, "%ld <%-6s: %s\n",
8925                 SubtractTimeMarks(&now, &programStartTime),
8926                 cps->which, message);
8927     }
8928         /* if icsEngineAnalyze is active we block all
8929            whisper and kibitz output, because nobody want 
8930            see this 
8931          */
8932         if (appData.icsEngineAnalyze) {
8933                 if (strstr(message, "whisper") != NULL ||
8934                     strstr(message, "kibitz") != NULL || 
8935                         strstr(message, "tellics") != NULL) return;
8936                 HandleMachineMove(message, cps);
8937         } else {
8938                 HandleMachineMove(message, cps);
8939         }
8940 }
8941
8942
8943 void
8944 SendTimeControl(cps, mps, tc, inc, sd, st)
8945      ChessProgramState *cps;
8946      int mps, inc, sd, st;
8947      long tc;
8948 {
8949     char buf[MSG_SIZ];
8950     int seconds = (tc / 1000) % 60;
8951
8952     if (st > 0) {
8953       /* Set exact time per move, normally using st command */
8954       if (cps->stKludge) {
8955         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8956         seconds = st % 60;
8957         if (seconds == 0) {
8958           sprintf(buf, "level 1 %d\n", st/60);
8959         } else {
8960           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8961         }
8962       } else {
8963         sprintf(buf, "st %d\n", st);
8964       }
8965     } else {
8966       /* Set conventional or incremental time control, using level command */
8967       if (seconds == 0) {
8968         /* Note old gnuchess bug -- minutes:seconds used to not work.
8969            Fixed in later versions, but still avoid :seconds
8970            when seconds is 0. */
8971         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8972       } else {
8973         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8974                 seconds, inc/1000);
8975       }
8976     }
8977     SendToProgram(buf, cps);
8978
8979     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8980     /* Orthogonally, limit search to given depth */
8981     if (sd > 0) {
8982       if (cps->sdKludge) {
8983         sprintf(buf, "depth\n%d\n", sd);
8984       } else {
8985         sprintf(buf, "sd %d\n", sd);
8986       }
8987       SendToProgram(buf, cps);
8988     }
8989 }
8990
8991 void
8992 SendTimeRemaining(cps, machineWhite)
8993      ChessProgramState *cps;
8994      int /*boolean*/ machineWhite;
8995 {
8996     char message[MSG_SIZ];
8997     long time, otime;
8998
8999     /* Note: this routine must be called when the clocks are stopped
9000        or when they have *just* been set or switched; otherwise
9001        it will be off by the time since the current tick started.
9002     */
9003     if (machineWhite) {
9004         time = whiteTimeRemaining / 10;
9005         otime = blackTimeRemaining / 10;
9006     } else {
9007         time = blackTimeRemaining / 10;
9008         otime = whiteTimeRemaining / 10;
9009     }
9010     if (time <= 0) time = 1;
9011     if (otime <= 0) otime = 1;
9012
9013     sprintf(message, "time %ld\notim %ld\n", time, otime);
9014     SendToProgram(message, cps);
9015 }
9016
9017 int
9018 BoolFeature(p, name, loc, cps)
9019      char **p;
9020      char *name;
9021      int *loc;
9022      ChessProgramState *cps;
9023 {
9024   char buf[MSG_SIZ];
9025   int len = strlen(name);
9026   int val;
9027   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9028     (*p) += len + 1;
9029     sscanf(*p, "%d", &val);
9030     *loc = (val != 0);
9031     while (**p && **p != ' ') (*p)++;
9032     sprintf(buf, "accepted %s\n", name);
9033     SendToProgram(buf, cps);
9034     return TRUE;
9035   }
9036   return FALSE;
9037 }
9038
9039 int
9040 IntFeature(p, name, loc, cps)
9041      char **p;
9042      char *name;
9043      int *loc;
9044      ChessProgramState *cps;
9045 {
9046   char buf[MSG_SIZ];
9047   int len = strlen(name);
9048   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
9049     (*p) += len + 1;
9050     sscanf(*p, "%d", loc);
9051     while (**p && **p != ' ') (*p)++;
9052     sprintf(buf, "accepted %s\n", name);
9053     SendToProgram(buf, cps);
9054     return TRUE;
9055   }
9056   return FALSE;
9057 }
9058
9059 int
9060 StringFeature(p, name, loc, cps)
9061      char **p;
9062      char *name;
9063      char loc[];
9064      ChessProgramState *cps;
9065 {
9066   char buf[MSG_SIZ];
9067   int len = strlen(name);
9068   if (strncmp((*p), name, len) == 0
9069       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
9070     (*p) += len + 2;
9071     sscanf(*p, "%[^\"]", loc);
9072     while (**p && **p != '\"') (*p)++;
9073     if (**p == '\"') (*p)++;
9074     sprintf(buf, "accepted %s\n", name);
9075     SendToProgram(buf, cps);
9076     return TRUE;
9077   }
9078   return FALSE;
9079 }
9080
9081 void
9082 FeatureDone(cps, val)
9083      ChessProgramState* cps;
9084      int val;
9085 {
9086   DelayedEventCallback cb = GetDelayedEvent();
9087   if ((cb == InitBackEnd3 && cps == &first) ||
9088       (cb == TwoMachinesEventIfReady && cps == &second)) {
9089     CancelDelayedEvent();
9090     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
9091   }
9092   cps->initDone = val;
9093 }
9094
9095 /* Parse feature command from engine */
9096 void
9097 ParseFeatures(args, cps)
9098      char* args;
9099      ChessProgramState *cps;
9100 {
9101   char *p = args;
9102   char *q;
9103   int val;
9104   char buf[MSG_SIZ];
9105
9106   for (;;) {
9107     while (*p == ' ') p++;
9108     if (*p == NULLCHAR) return;
9109
9110     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9111     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
9112     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
9113     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
9114     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
9115     if (BoolFeature(&p, "reuse", &val, cps)) {
9116       /* Engine can disable reuse, but can't enable it if user said no */
9117       if (!val) cps->reuse = FALSE;
9118       continue;
9119     }
9120     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9121     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9122       if (gameMode == TwoMachinesPlay) {
9123         DisplayTwoMachinesTitle();
9124       } else {
9125         DisplayTitle("");
9126       }
9127       continue;
9128     }
9129     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9130     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9131     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9132     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9133     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9134     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9135     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9136     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9137     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9138     if (IntFeature(&p, "done", &val, cps)) {
9139       FeatureDone(cps, val);
9140       continue;
9141     }
9142
9143     /* unknown feature: complain and skip */
9144     q = p;
9145     while (*q && *q != '=') q++;
9146     sprintf(buf, "rejected %.*s\n", q-p, p);
9147     SendToProgram(buf, cps);
9148     p = q;
9149     if (*p == '=') {
9150       p++;
9151       if (*p == '\"') {
9152         p++;
9153         while (*p && *p != '\"') p++;
9154         if (*p == '\"') p++;
9155       } else {
9156         while (*p && *p != ' ') p++;
9157       }
9158     }
9159   }
9160
9161 }
9162
9163 void
9164 PeriodicUpdatesEvent(newState)
9165      int newState;
9166 {
9167     if (newState == appData.periodicUpdates)
9168       return;
9169
9170     appData.periodicUpdates=newState;
9171
9172     /* Display type changes, so update it now */
9173     DisplayAnalysis();
9174
9175     /* Get the ball rolling again... */
9176     if (newState) {
9177         AnalysisPeriodicEvent(1);
9178         StartAnalysisClock();
9179     }
9180 }
9181
9182 void
9183 PonderNextMoveEvent(newState)
9184      int newState;
9185 {
9186     if (newState == appData.ponderNextMove) return;
9187     if (gameMode == EditPosition) EditPositionDone();
9188     if (newState) {
9189         SendToProgram("hard\n", &first);
9190         if (gameMode == TwoMachinesPlay) {
9191             SendToProgram("hard\n", &second);
9192         }
9193     } else {
9194         SendToProgram("easy\n", &first);
9195         thinkOutput[0] = NULLCHAR;
9196         if (gameMode == TwoMachinesPlay) {
9197             SendToProgram("easy\n", &second);
9198         }
9199     }
9200     appData.ponderNextMove = newState;
9201 }
9202
9203 void
9204 ShowThinkingEvent(newState)
9205      int newState;
9206 {
9207     if (newState == appData.showThinking) return;
9208     if (gameMode == EditPosition) EditPositionDone();
9209     if (newState) {
9210         SendToProgram("post\n", &first);
9211         if (gameMode == TwoMachinesPlay) {
9212             SendToProgram("post\n", &second);
9213         }
9214     } else {
9215         SendToProgram("nopost\n", &first);
9216         thinkOutput[0] = NULLCHAR;
9217         if (gameMode == TwoMachinesPlay) {
9218             SendToProgram("nopost\n", &second);
9219         }
9220     }
9221     appData.showThinking = newState;
9222 }
9223
9224 void
9225 AskQuestionEvent(title, question, replyPrefix, which)
9226      char *title; char *question; char *replyPrefix; char *which;
9227 {
9228   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9229   if (pr == NoProc) return;
9230   AskQuestion(title, question, replyPrefix, pr);
9231 }
9232
9233 void
9234 DisplayMove(moveNumber)
9235      int moveNumber;
9236 {
9237     char message[MSG_SIZ];
9238     char res[MSG_SIZ];
9239     char cpThinkOutput[MSG_SIZ];
9240
9241     if (moveNumber == forwardMostMove - 1 ||
9242         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9243
9244         strcpy(cpThinkOutput, thinkOutput);
9245         if (strchr(cpThinkOutput, '\n'))
9246           *strchr(cpThinkOutput, '\n') = NULLCHAR;
9247     } else {
9248         *cpThinkOutput = NULLCHAR;
9249     }
9250
9251     if (moveNumber == forwardMostMove - 1 &&
9252         gameInfo.resultDetails != NULL) {
9253         if (gameInfo.resultDetails[0] == NULLCHAR) {
9254             sprintf(res, " %s", PGNResult(gameInfo.result));
9255         } else {
9256             sprintf(res, " {%s} %s",
9257                     gameInfo.resultDetails, PGNResult(gameInfo.result));
9258         }
9259     } else {
9260         res[0] = NULLCHAR;
9261     }
9262
9263     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9264         DisplayMessage(res, cpThinkOutput);
9265     } else {
9266         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9267                 WhiteOnMove(moveNumber) ? " " : ".. ",
9268                 parseList[moveNumber], res);
9269         DisplayMessage(message, cpThinkOutput);
9270     }
9271 }
9272
9273 void
9274 DisplayAnalysisText(text)
9275      char *text;
9276 {
9277     char buf[MSG_SIZ];
9278
9279     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
9280                 || appData.icsEngineAnalyze) {
9281         sprintf(buf, "Analysis (%s)", first.tidy);
9282         AnalysisPopUp(buf, text);
9283     }
9284 }
9285
9286 static int
9287 only_one_move(str)
9288      char *str;
9289 {
9290     while (*str && isspace(*str)) ++str;
9291     while (*str && !isspace(*str)) ++str;
9292     if (!*str) return 1;
9293     while (*str && isspace(*str)) ++str;
9294     if (!*str) return 1;
9295     return 0;
9296 }
9297
9298 void
9299 DisplayAnalysis()
9300 {
9301     char buf[MSG_SIZ];
9302     double nps;
9303     static char *xtra[] = { "", " (--)", " (++)" };
9304     int h, m, s, cs;
9305
9306     if (programStats.time == 0) {
9307         programStats.time = 1;
9308     }
9309
9310     if (programStats.got_only_move) {
9311         strcpy(buf, programStats.movelist);
9312     } else {
9313         nps = (u64ToDouble(programStats.nodes) /
9314               ((double)programStats.time /100.0));
9315
9316         cs = programStats.time % 100;
9317         s = programStats.time / 100;
9318         h = (s / (60*60));
9319         s = s - h*60*60;
9320         m = (s/60);
9321         s = s - m*60;
9322
9323         if (programStats.moves_left > 0 && appData.periodicUpdates) {
9324           if (programStats.move_name[0] != NULLCHAR) {
9325             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9326                     programStats.depth,
9327                     programStats.nr_moves-programStats.moves_left,
9328                     programStats.nr_moves, programStats.move_name,
9329                     ((float)programStats.score)/100.0, programStats.movelist,
9330                     only_one_move(programStats.movelist)?
9331                     xtra[programStats.got_fail] : "",
9332                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
9333           } else {
9334             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9335                     programStats.depth,
9336                     programStats.nr_moves-programStats.moves_left,
9337                     programStats.nr_moves, ((float)programStats.score)/100.0,
9338                     programStats.movelist,
9339                     only_one_move(programStats.movelist)?
9340                     xtra[programStats.got_fail] : "",
9341                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
9342           }
9343         } else {
9344             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d",
9345                     programStats.depth,
9346                     ((float)programStats.score)/100.0,
9347                     programStats.movelist,
9348                     only_one_move(programStats.movelist)?
9349                     xtra[programStats.got_fail] : "",
9350                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
9351         }
9352     }
9353     DisplayAnalysisText(buf);
9354 }
9355
9356 void
9357 DisplayComment(moveNumber, text)
9358      int moveNumber;
9359      char *text;
9360 {
9361     char title[MSG_SIZ];
9362
9363     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9364         strcpy(title, "Comment");
9365     } else {
9366         sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9367                 WhiteOnMove(moveNumber) ? " " : ".. ",
9368                 parseList[moveNumber]);
9369     }
9370
9371     CommentPopUp(title, text);
9372 }
9373
9374 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9375  * might be busy thinking or pondering.  It can be omitted if your
9376  * gnuchess is configured to stop thinking immediately on any user
9377  * input.  However, that gnuchess feature depends on the FIONREAD
9378  * ioctl, which does not work properly on some flavors of Unix.
9379  */
9380 void
9381 Attention(cps)
9382      ChessProgramState *cps;
9383 {
9384 #if ATTENTION
9385     if (!cps->useSigint) return;
9386     if (appData.noChessProgram || (cps->pr == NoProc)) return;
9387     switch (gameMode) {
9388       case MachinePlaysWhite:
9389       case MachinePlaysBlack:
9390       case TwoMachinesPlay:
9391       case IcsPlayingWhite:
9392       case IcsPlayingBlack:
9393       case AnalyzeMode:
9394       case AnalyzeFile:
9395         /* Skip if we know it isn't thinking */
9396         if (!cps->maybeThinking) return;
9397         if (appData.debugMode)
9398           fprintf(debugFP, "Interrupting %s\n", cps->which);
9399         InterruptChildProcess(cps->pr);
9400         cps->maybeThinking = FALSE;
9401         break;
9402       default:
9403         break;
9404     }
9405 #endif /*ATTENTION*/
9406 }
9407
9408 int
9409 CheckFlags()
9410 {
9411     if (whiteTimeRemaining <= 0) {
9412         if (!whiteFlag) {
9413             whiteFlag = TRUE;
9414             if (appData.icsActive) {
9415                 if (appData.autoCallFlag &&
9416                     gameMode == IcsPlayingBlack && !blackFlag) {
9417                   SendToICS(ics_prefix);
9418                   SendToICS("flag\n");
9419                 }
9420             } else {
9421                 if (blackFlag) {
9422                     DisplayTitle(_("Both flags fell"));
9423                 } else {
9424                     DisplayTitle(_("White's flag fell"));
9425                     if (appData.autoCallFlag) {
9426                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9427                         return TRUE;
9428                     }
9429                 }
9430             }
9431         }
9432     }
9433     if (blackTimeRemaining <= 0) {
9434         if (!blackFlag) {
9435             blackFlag = TRUE;
9436             if (appData.icsActive) {
9437                 if (appData.autoCallFlag &&
9438                     gameMode == IcsPlayingWhite && !whiteFlag) {
9439                   SendToICS(ics_prefix);
9440                   SendToICS("flag\n");
9441                 }
9442             } else {
9443                 if (whiteFlag) {
9444                     DisplayTitle(_("Both flags fell"));
9445                 } else {
9446                     DisplayTitle(_("Black's flag fell"));
9447                     if (appData.autoCallFlag) {
9448                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9449                         return TRUE;
9450                     }
9451                 }
9452             }
9453         }
9454     }
9455     return FALSE;
9456 }
9457
9458 void
9459 CheckTimeControl()
9460 {
9461     if (!appData.clockMode || appData.icsActive ||
9462         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9463
9464     if (timeIncrement >= 0) {
9465         if (WhiteOnMove(forwardMostMove)) {
9466             blackTimeRemaining += timeIncrement;
9467         } else {
9468             whiteTimeRemaining += timeIncrement;
9469         }
9470     }
9471     /*
9472      * add time to clocks when time control is achieved
9473      */
9474     if (movesPerSession) {
9475       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9476       case 0:
9477         /* White made time control */
9478         whiteTimeRemaining += timeControl;
9479         break;
9480       case 1:
9481         /* Black made time control */
9482         blackTimeRemaining += timeControl;
9483         break;
9484       default:
9485         break;
9486       }
9487     }
9488 }
9489
9490 void
9491 DisplayBothClocks()
9492 {
9493     int wom = gameMode == EditPosition ?
9494       !blackPlaysFirst : WhiteOnMove(currentMove);
9495     DisplayWhiteClock(whiteTimeRemaining, wom);
9496     DisplayBlackClock(blackTimeRemaining, !wom);
9497 }
9498
9499
9500 /* Timekeeping seems to be a portability nightmare.  I think everyone
9501    has ftime(), but I'm really not sure, so I'm including some ifdefs
9502    to use other calls if you don't.  Clocks will be less accurate if
9503    you have neither ftime nor gettimeofday.
9504 */
9505
9506 /* Get the current time as a TimeMark */
9507 void
9508 GetTimeMark(tm)
9509      TimeMark *tm;
9510 {
9511 #if HAVE_GETTIMEOFDAY
9512
9513     struct timeval timeVal;
9514     struct timezone timeZone;
9515
9516     gettimeofday(&timeVal, &timeZone);
9517     tm->sec = (long) timeVal.tv_sec;
9518     tm->ms = (int) (timeVal.tv_usec / 1000L);
9519
9520 #else /*!HAVE_GETTIMEOFDAY*/
9521 #if HAVE_FTIME
9522
9523 #include <sys/timeb.h>
9524     struct timeb timeB;
9525
9526     ftime(&timeB);
9527     tm->sec = (long) timeB.time;
9528     tm->ms = (int) timeB.millitm;
9529
9530 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9531     tm->sec = (long) time(NULL);
9532     tm->ms = 0;
9533 #endif
9534 #endif
9535 }
9536
9537 /* Return the difference in milliseconds between two
9538    time marks.  We assume the difference will fit in a long!
9539 */
9540 long
9541 SubtractTimeMarks(tm2, tm1)
9542      TimeMark *tm2, *tm1;
9543 {
9544     return 1000L*(tm2->sec - tm1->sec) +
9545            (long) (tm2->ms - tm1->ms);
9546 }
9547
9548
9549 /*
9550  * Code to manage the game clocks.
9551  *
9552  * In tournament play, black starts the clock and then white makes a move.
9553  * We give the human user a slight advantage if he is playing white---the
9554  * clocks don't run until he makes his first move, so it takes zero time.
9555  * Also, we don't account for network lag, so we could get out of sync
9556  * with GNU Chess's clock -- but then, referees are always right.
9557  */
9558
9559 static TimeMark tickStartTM;
9560 static long intendedTickLength;
9561
9562 long
9563 NextTickLength(timeRemaining)
9564      long timeRemaining;
9565 {
9566     long nominalTickLength, nextTickLength;
9567
9568     if (timeRemaining > 0L && timeRemaining <= 10000L)
9569       nominalTickLength = 100L;
9570     else
9571       nominalTickLength = 1000L;
9572     nextTickLength = timeRemaining % nominalTickLength;
9573     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9574
9575     return nextTickLength;
9576 }
9577
9578 /* Stop clocks and reset to a fresh time control */
9579 void
9580 ResetClocks()
9581 {
9582     (void) StopClockTimer();
9583     if (appData.icsActive) {
9584         whiteTimeRemaining = blackTimeRemaining = 0;
9585     } else {
9586         whiteTimeRemaining = blackTimeRemaining = timeControl;
9587     }
9588     if (whiteFlag || blackFlag) {
9589         DisplayTitle("");
9590         whiteFlag = blackFlag = FALSE;
9591     }
9592     DisplayBothClocks();
9593 }
9594
9595 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9596
9597 /* Decrement running clock by amount of time that has passed */
9598 void
9599 DecrementClocks()
9600 {
9601     long timeRemaining;
9602     long lastTickLength, fudge;
9603     TimeMark now;
9604
9605     if (!appData.clockMode) return;
9606     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9607
9608     GetTimeMark(&now);
9609
9610     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9611
9612     /* Fudge if we woke up a little too soon */
9613     fudge = intendedTickLength - lastTickLength;
9614     if (fudge < 0 || fudge > FUDGE) fudge = 0;
9615
9616     if (WhiteOnMove(forwardMostMove)) {
9617         timeRemaining = whiteTimeRemaining -= lastTickLength;
9618         DisplayWhiteClock(whiteTimeRemaining - fudge,
9619                           WhiteOnMove(currentMove));
9620     } else {
9621         timeRemaining = blackTimeRemaining -= lastTickLength;
9622         DisplayBlackClock(blackTimeRemaining - fudge,
9623                           !WhiteOnMove(currentMove));
9624     }
9625
9626     if (CheckFlags()) return;
9627
9628     tickStartTM = now;
9629     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9630     StartClockTimer(intendedTickLength);
9631
9632     /* if the time remaining has fallen below the alarm threshold, sound the
9633      * alarm. if the alarm has sounded and (due to a takeback or time control
9634      * with increment) the time remaining has increased to a level above the
9635      * threshold, reset the alarm so it can sound again.
9636      */
9637
9638     if (appData.icsActive && appData.icsAlarm) {
9639
9640         /* make sure we are dealing with the user's clock */
9641         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9642                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9643            )) return;
9644
9645         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9646             alarmSounded = FALSE;
9647         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
9648             PlayAlarmSound();
9649             alarmSounded = TRUE;
9650         }
9651     }
9652 }
9653
9654
9655 /* A player has just moved, so stop the previously running
9656    clock and (if in clock mode) start the other one.
9657    We redisplay both clocks in case we're in ICS mode, because
9658    ICS gives us an update to both clocks after every move.
9659    Note that this routine is called *after* forwardMostMove
9660    is updated, so the last fractional tick must be subtracted
9661    from the color that is *not* on move now.
9662 */
9663 void
9664 SwitchClocks()
9665 {
9666     long lastTickLength;
9667     TimeMark now;
9668     int flagged = FALSE;
9669
9670     GetTimeMark(&now);
9671
9672     if (StopClockTimer() && appData.clockMode) {
9673         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9674         if (WhiteOnMove(forwardMostMove)) {
9675             blackTimeRemaining -= lastTickLength;
9676         } else {
9677             whiteTimeRemaining -= lastTickLength;
9678         }
9679         flagged = CheckFlags();
9680     }
9681     CheckTimeControl();
9682
9683     if (flagged || !appData.clockMode) return;
9684
9685     switch (gameMode) {
9686       case MachinePlaysBlack:
9687       case MachinePlaysWhite:
9688       case BeginningOfGame:
9689         if (pausing) return;
9690         break;
9691
9692       case EditGame:
9693       case PlayFromGameFile:
9694       case IcsExamining:
9695         return;
9696
9697       default:
9698         break;
9699     }
9700
9701     tickStartTM = now;
9702     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9703       whiteTimeRemaining : blackTimeRemaining);
9704     StartClockTimer(intendedTickLength);
9705 }
9706
9707
9708 /* Stop both clocks */
9709 void
9710 StopClocks()
9711 {
9712     long lastTickLength;
9713     TimeMark now;
9714
9715     if (!StopClockTimer()) return;
9716     if (!appData.clockMode) return;
9717
9718     GetTimeMark(&now);
9719
9720     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9721     if (WhiteOnMove(forwardMostMove)) {
9722         whiteTimeRemaining -= lastTickLength;
9723         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9724     } else {
9725         blackTimeRemaining -= lastTickLength;
9726         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9727     }
9728     CheckFlags();
9729 }
9730
9731 /* Start clock of player on move.  Time may have been reset, so
9732    if clock is already running, stop and restart it. */
9733 void
9734 StartClocks()
9735 {
9736     (void) StopClockTimer(); /* in case it was running already */
9737     DisplayBothClocks();
9738     if (CheckFlags()) return;
9739
9740     if (!appData.clockMode) return;
9741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9742
9743     GetTimeMark(&tickStartTM);
9744     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9745       whiteTimeRemaining : blackTimeRemaining);
9746     StartClockTimer(intendedTickLength);
9747 }
9748
9749 char *
9750 TimeString(ms)
9751      long ms;
9752 {
9753     long second, minute, hour, day;
9754     char *sign = "";
9755     static char buf[32];
9756
9757     if (ms > 0 && ms <= 9900) {
9758       /* convert milliseconds to tenths, rounding up */
9759       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9760
9761       sprintf(buf, " %03.1f ", tenths/10.0);
9762       return buf;
9763     }
9764
9765     /* convert milliseconds to seconds, rounding up */
9766     /* use floating point to avoid strangeness of integer division
9767        with negative dividends on many machines */
9768     second = (long) floor(((double) (ms + 999L)) / 1000.0);
9769
9770     if (second < 0) {
9771         sign = "-";
9772         second = -second;
9773     }
9774
9775     day = second / (60 * 60 * 24);
9776     second = second % (60 * 60 * 24);
9777     hour = second / (60 * 60);
9778     second = second % (60 * 60);
9779     minute = second / 60;
9780     second = second % 60;
9781
9782     if (day > 0)
9783       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9784               sign, day, hour, minute, second);
9785     else if (hour > 0)
9786       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9787     else
9788       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9789
9790     return buf;
9791 }
9792
9793
9794 /*
9795  * This is necessary because some C libraries aren't ANSI C compliant yet.
9796  */
9797 char *
9798 StrStr(string, match)
9799      char *string, *match;
9800 {
9801     int i, length;
9802
9803     length = strlen(match);
9804
9805     for (i = strlen(string) - length; i >= 0; i--, string++)
9806       if (!strncmp(match, string, length))
9807         return string;
9808
9809     return NULL;
9810 }
9811
9812 char *
9813 StrCaseStr(string, match)
9814      char *string, *match;
9815 {
9816     int i, j, length;
9817
9818     length = strlen(match);
9819
9820     for (i = strlen(string) - length; i >= 0; i--, string++) {
9821         for (j = 0; j < length; j++) {
9822             if (ToLower(match[j]) != ToLower(string[j]))
9823               break;
9824         }
9825         if (j == length) return string;
9826     }
9827
9828     return NULL;
9829 }
9830
9831 #ifndef _amigados
9832 int
9833 StrCaseCmp(s1, s2)
9834      char *s1, *s2;
9835 {
9836     char c1, c2;
9837
9838     for (;;) {
9839         c1 = ToLower(*s1++);
9840         c2 = ToLower(*s2++);
9841         if (c1 > c2) return 1;
9842         if (c1 < c2) return -1;
9843         if (c1 == NULLCHAR) return 0;
9844     }
9845 }
9846
9847
9848 int
9849 ToLower(c)
9850      int c;
9851 {
9852     return isupper(c) ? tolower(c) : c;
9853 }
9854
9855
9856 int
9857 ToUpper(c)
9858      int c;
9859 {
9860     return islower(c) ? toupper(c) : c;
9861 }
9862 #endif /* !_amigados    */
9863
9864 char *
9865 StrSave(s)
9866      char *s;
9867 {
9868     char *ret;
9869
9870     if ((ret = (char *) malloc(strlen(s) + 1))) {
9871         strcpy(ret, s);
9872     }
9873     return ret;
9874 }
9875
9876 char *
9877 StrSavePtr(s, savePtr)
9878      char *s, **savePtr;
9879 {
9880     if (*savePtr) {
9881         free(*savePtr);
9882     }
9883     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9884         strcpy(*savePtr, s);
9885     }
9886     return(*savePtr);
9887 }
9888
9889 char *
9890 PGNDate()
9891 {
9892     time_t clock;
9893     struct tm *tm;
9894     char buf[MSG_SIZ];
9895
9896     clock = time((time_t *)NULL);
9897     tm = localtime(&clock);
9898     sprintf(buf, "%04d.%02d.%02d",
9899             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9900     return StrSave(buf);
9901 }
9902
9903
9904 char *
9905 PositionToFEN(move)
9906      int move;
9907 {
9908     int i, j, fromX, fromY, toX, toY;
9909     int whiteToPlay;
9910     char buf[128];
9911     char *p, *q;
9912     int emptycount;
9913
9914     whiteToPlay = (gameMode == EditPosition) ?
9915       !blackPlaysFirst : (move % 2 == 0);
9916     p = buf;
9917
9918     /* Piece placement data */
9919     for (i = BOARD_SIZE - 1; i >= 0; i--) {
9920         emptycount = 0;
9921         for (j = 0; j < BOARD_SIZE; j++) {
9922             if (boards[move][i][j] == EmptySquare) {
9923                 emptycount++;
9924             } else {
9925                 if (emptycount > 0) {
9926                     *p++ = '0' + emptycount;
9927                     emptycount = 0;
9928                 }
9929                 *p++ = PieceToChar(boards[move][i][j]);
9930             }
9931         }
9932         if (emptycount > 0) {
9933             *p++ = '0' + emptycount;
9934             emptycount = 0;
9935         }
9936         *p++ = '/';
9937     }
9938     *(p - 1) = ' ';
9939
9940     /* Active color */
9941     *p++ = whiteToPlay ? 'w' : 'b';
9942     *p++ = ' ';
9943
9944     /* !!We don't keep track of castling availability, so fake it */
9945     q = p;
9946     if (boards[move][0][4] == WhiteKing) {
9947         if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9948         if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9949     }
9950     if (boards[move][7][4] == BlackKing) {
9951         if (boards[move][7][7] == BlackRook) *p++ = 'k';
9952         if (boards[move][7][0] == BlackRook) *p++ = 'q';
9953     }
9954     if (q == p) *p++ = '-';
9955     *p++ = ' ';
9956
9957     /* En passant target square */
9958     if (move > backwardMostMove) {
9959         fromX = moveList[move - 1][0] - 'a';
9960         fromY = moveList[move - 1][1] - '1';
9961         toX = moveList[move - 1][2] - 'a';
9962         toY = moveList[move - 1][3] - '1';
9963         if (fromY == (whiteToPlay ? 6 : 1) &&
9964             toY == (whiteToPlay ? 4 : 3) &&
9965             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9966             fromX == toX) {
9967             /* 2-square pawn move just happened */
9968             *p++ = toX + 'a';
9969             *p++ = whiteToPlay ? '6' : '3';
9970         } else {
9971             *p++ = '-';
9972         }
9973     } else {
9974         *p++ = '-';
9975     }
9976
9977     /* !!We don't keep track of halfmove clock for 50-move rule */
9978     strcpy(p, " 0 ");
9979     p += 3;
9980
9981     /* Fullmove number */
9982     sprintf(p, "%d", (move / 2) + 1);
9983
9984     return StrSave(buf);
9985 }
9986
9987 Boolean
9988 ParseFEN(board, blackPlaysFirst, fen)
9989      Board board;
9990      int *blackPlaysFirst;
9991      char *fen;
9992 {
9993     int i, j;
9994     char *p;
9995     int emptycount;
9996
9997     p = fen;
9998
9999     /* Piece placement data */
10000     for (i = BOARD_SIZE - 1; i >= 0; i--) {
10001         j = 0;
10002         for (;;) {
10003             if (*p == '/' || *p == ' ') {
10004                 if (*p == '/') p++;
10005                 emptycount = BOARD_SIZE - j;
10006                 while (emptycount--) board[i][j++] = EmptySquare;
10007                 break;
10008             } else if (isdigit(*p)) {
10009                 emptycount = *p++ - '0';
10010                 if (j + emptycount > BOARD_SIZE) return FALSE;
10011                 while (emptycount--) board[i][j++] = EmptySquare;
10012             } else if (isalpha(*p)) {
10013                 if (j >= BOARD_SIZE) return FALSE;
10014                 board[i][j++] = CharToPiece(*p++);
10015             } else {
10016                 return FALSE;
10017             }
10018         }
10019     }
10020     while (*p == '/' || *p == ' ') p++;
10021
10022     /* Active color */
10023     switch (*p) {
10024       case 'w':
10025         *blackPlaysFirst = FALSE;
10026         break;
10027       case 'b':
10028         *blackPlaysFirst = TRUE;
10029         break;
10030       default:
10031         return FALSE;
10032     }
10033     /* !!We ignore the rest of the FEN notation */
10034     return TRUE;
10035 }
10036
10037 void
10038 EditPositionPasteFEN(char *fen)
10039 {
10040   if (fen != NULL) {
10041     Board initial_position;
10042
10043     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
10044       DisplayError(_("Bad FEN position in clipboard"), 0);
10045       return ;
10046     } else {
10047       int savedBlackPlaysFirst = blackPlaysFirst;
10048       EditPositionEvent();
10049       blackPlaysFirst = savedBlackPlaysFirst;
10050       CopyBoard(boards[0], initial_position);
10051       EditPositionDone();
10052       DisplayBothClocks();
10053       DrawPosition(FALSE, boards[currentMove]);
10054     }
10055   }
10056 }