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