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