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