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