Generate the xboard man page (now covering cmail too) from the texinfo
[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[2*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         DisplayNote(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         DisplayNote(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         DisplayNote(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                 programStats.got_only_move = 0;
4353
4354                 /* Buffer overflow protection */
4355                 if (buf1[0] != NULLCHAR) {
4356                     if (strlen(buf1) >= sizeof(programStats.movelist)
4357                         && appData.debugMode) {
4358                         fprintf(debugFP,
4359                                 "PV is too long; using the first %d bytes.\n",
4360                                 sizeof(programStats.movelist) - 1);
4361                     }
4362                     strncpy(programStats.movelist, buf1,
4363                             sizeof(programStats.movelist));
4364                     buf1[sizeof(programStats.movelist) - 1] = NULLCHAR;
4365                 } else {
4366                     sprintf(programStats.movelist, " no PV\n");
4367                 }
4368
4369                 if (programStats.seen_stat) {
4370                     programStats.ok_to_send = 1;
4371                 }
4372
4373                 if (strchr(programStats.movelist, '(') != NULL) {
4374                     programStats.line_is_book = 1;
4375                     programStats.nr_moves = 0;
4376                     programStats.moves_left = 0;
4377                 } else {
4378                     programStats.line_is_book = 0;
4379                 }
4380                   
4381                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s",
4382                         plylev, 
4383                         (gameMode == TwoMachinesPlay ?
4384                          ToUpper(cps->twoMachinesColor[0]) : ' '),
4385                         ((double) curscore) / 100.0,
4386                         prefixHint ? lastHint : "",
4387                         prefixHint ? " " : "", buf1);
4388
4389                 if (currentMove == forwardMostMove ||
4390                     gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
4391                     DisplayMove(currentMove - 1);
4392                     DisplayAnalysis();
4393                 }
4394                 return;
4395
4396             } else if ((p=StrStr(message, "(only move)")) != NULL) {
4397                 /* crafty (9.25+) says "(only move) <move>"
4398                  * if there is only 1 legal move
4399                  */
4400                 sscanf(p, "(only move) %s", buf1);
4401                 sprintf(thinkOutput, "%s (only move)", buf1);
4402                 sprintf(programStats.movelist, "%s (only move)", buf1);
4403                 programStats.depth = 1;
4404                 programStats.nr_moves = 1;
4405                 programStats.moves_left = 1;
4406                 programStats.nodes = 1;
4407                 programStats.time = 1;
4408                 programStats.got_only_move = 1;
4409
4410                 /* Not really, but we also use this member to
4411                    mean "line isn't going to change" (Crafty
4412                    isn't searching, so stats won't change) */
4413                 programStats.line_is_book = 1;
4414                   
4415                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4416                     gameMode == AnalyzeFile) {
4417                     DisplayMove(currentMove - 1);
4418                     DisplayAnalysis();
4419                 }
4420                 return;
4421             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
4422                               &time, &nodes, &plylev, &mvleft,
4423                               &mvtot, mvname) >= 5) {
4424                 /* The stat01: line is from Crafty (9.29+) in response
4425                    to the "." command */
4426                 programStats.seen_stat = 1;
4427                 cps->maybeThinking = TRUE;
4428
4429                 if (programStats.got_only_move || !appData.periodicUpdates)
4430                   return;
4431
4432                 programStats.depth = plylev;
4433                 programStats.time = time;
4434                 programStats.nodes = nodes;
4435                 programStats.moves_left = mvleft;
4436                 programStats.nr_moves = mvtot;
4437                 strcpy(programStats.move_name, mvname);
4438                 programStats.ok_to_send = 1;
4439                 DisplayAnalysis();
4440                 return;
4441
4442             } else if (strncmp(message,"++",2) == 0) {
4443                 /* Crafty 9.29+ outputs this */
4444                 programStats.got_fail = 2;
4445                 return;
4446
4447             } else if (strncmp(message,"--",2) == 0) {
4448                 /* Crafty 9.29+ outputs this */
4449                 programStats.got_fail = 1;
4450                 return;
4451
4452             } else if (thinkOutput[0] != NULLCHAR &&
4453                        strncmp(message, "    ", 4) == 0) {
4454                 p = message;
4455                 while (*p && *p == ' ') p++;
4456                 strcat(thinkOutput, " ");
4457                 strcat(thinkOutput, p);
4458                 strcat(programStats.movelist, " ");
4459                 strcat(programStats.movelist, p);
4460                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
4461                     gameMode == AnalyzeFile) {
4462                     DisplayMove(currentMove - 1);
4463                     DisplayAnalysis();
4464                 }
4465                 return;
4466             }
4467         }
4468     }
4469 }
4470
4471
4472 /* Parse a game score from the character string "game", and
4473    record it as the history of the current game.  The game
4474    score is NOT assumed to start from the standard position. 
4475    The display is not updated in any way.
4476    */
4477 void
4478 ParseGameHistory(game)
4479      char *game;
4480 {
4481     ChessMove moveType;
4482     int fromX, fromY, toX, toY, boardIndex;
4483     char promoChar;
4484     char *p, *q;
4485     char buf[MSG_SIZ];
4486
4487     if (appData.debugMode)
4488       fprintf(debugFP, "Parsing game history: %s\n", game);
4489
4490     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
4491     gameInfo.site = StrSave(appData.icsHost);
4492     gameInfo.date = PGNDate();
4493     gameInfo.round = StrSave("-");
4494
4495     /* Parse out names of players */
4496     while (*game == ' ') game++;
4497     p = buf;
4498     while (*game != ' ') *p++ = *game++;
4499     *p = NULLCHAR;
4500     gameInfo.white = StrSave(buf);
4501     while (*game == ' ') game++;
4502     p = buf;
4503     while (*game != ' ' && *game != '\n') *p++ = *game++;
4504     *p = NULLCHAR;
4505     gameInfo.black = StrSave(buf);
4506
4507     /* Parse moves */
4508     boardIndex = blackPlaysFirst ? 1 : 0;
4509     yynewstr(game);
4510     for (;;) {
4511         yyboardindex = boardIndex;
4512         moveType = (ChessMove) yylex();
4513         switch (moveType) {
4514           case WhitePromotionQueen:
4515           case BlackPromotionQueen:
4516           case WhitePromotionRook:
4517           case BlackPromotionRook:
4518           case WhitePromotionBishop:
4519           case BlackPromotionBishop:
4520           case WhitePromotionKnight:
4521           case BlackPromotionKnight:
4522           case WhitePromotionKing:
4523           case BlackPromotionKing:
4524           case NormalMove:
4525           case WhiteCapturesEnPassant:
4526           case BlackCapturesEnPassant:
4527           case WhiteKingSideCastle:
4528           case WhiteQueenSideCastle:
4529           case BlackKingSideCastle:
4530           case BlackQueenSideCastle:
4531           case WhiteKingSideCastleWild:
4532           case WhiteQueenSideCastleWild:
4533           case BlackKingSideCastleWild:
4534           case BlackQueenSideCastleWild:
4535           case IllegalMove:             /* maybe suicide chess, etc. */
4536             fromX = currentMoveString[0] - 'a';
4537             fromY = currentMoveString[1] - '1';
4538             toX = currentMoveString[2] - 'a';
4539             toY = currentMoveString[3] - '1';
4540             promoChar = currentMoveString[4];
4541             break;
4542           case WhiteDrop:
4543           case BlackDrop:
4544             fromX = moveType == WhiteDrop ?
4545               (int) CharToPiece(ToUpper(currentMoveString[0])) :
4546             (int) CharToPiece(ToLower(currentMoveString[0]));
4547             fromY = DROP_RANK;
4548             toX = currentMoveString[2] - 'a';
4549             toY = currentMoveString[3] - '1';
4550             promoChar = NULLCHAR;
4551             break;
4552           case AmbiguousMove:
4553             /* bug? */
4554             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
4555             DisplayError(buf, 0);
4556             return;
4557           case ImpossibleMove:
4558             /* bug? */
4559             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
4560             DisplayError(buf, 0);
4561             return;
4562           case (ChessMove) 0:   /* end of file */
4563             if (boardIndex < backwardMostMove) {
4564                 /* Oops, gap.  How did that happen? */
4565                 DisplayError("Gap in move list", 0);
4566                 return;
4567             }
4568             backwardMostMove =  blackPlaysFirst ? 1 : 0;
4569             if (boardIndex > forwardMostMove) {
4570                 forwardMostMove = boardIndex;
4571             }
4572             return;
4573           case ElapsedTime:
4574             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
4575                 strcat(parseList[boardIndex-1], " ");
4576                 strcat(parseList[boardIndex-1], yy_text);
4577             }
4578             continue;
4579           case Comment:
4580           case PGNTag:
4581           case NAG:
4582           default:
4583             /* ignore */
4584             continue;
4585           case WhiteWins:
4586           case BlackWins:
4587           case GameIsDrawn:
4588           case GameUnfinished:
4589             if (gameMode == IcsExamining) {
4590                 if (boardIndex < backwardMostMove) {
4591                     /* Oops, gap.  How did that happen? */
4592                     return;
4593                 }
4594                 backwardMostMove = blackPlaysFirst ? 1 : 0;
4595                 return;
4596             }
4597             gameInfo.result = moveType;
4598             p = strchr(yy_text, '{');
4599             if (p == NULL) p = strchr(yy_text, '(');
4600             if (p == NULL) {
4601                 p = yy_text;
4602                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
4603             } else {
4604                 q = strchr(p, *p == '{' ? '}' : ')');
4605                 if (q != NULL) *q = NULLCHAR;
4606                 p++;
4607             }
4608             gameInfo.resultDetails = StrSave(p);
4609             continue;
4610         }
4611         if (boardIndex >= forwardMostMove &&
4612             !(gameMode == IcsObserving && ics_gamenum == -1)) {
4613             backwardMostMove = blackPlaysFirst ? 1 : 0;
4614             return;
4615         }
4616         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
4617                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
4618                                  parseList[boardIndex]);
4619         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
4620         /* currentMoveString is set as a side-effect of yylex */
4621         strcpy(moveList[boardIndex], currentMoveString);
4622         strcat(moveList[boardIndex], "\n");
4623         boardIndex++;
4624         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
4625         switch (MateTest(boards[boardIndex],
4626                          PosFlags(boardIndex), EP_UNKNOWN)) {
4627           case MT_NONE:
4628           case MT_STALEMATE:
4629           default:
4630             break;
4631           case MT_CHECK:
4632             strcat(parseList[boardIndex - 1], "+");
4633             break;
4634           case MT_CHECKMATE:
4635             strcat(parseList[boardIndex - 1], "#");
4636             break;
4637         }
4638     }
4639 }
4640
4641
4642 /* Apply a move to the given board  */
4643 void
4644 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
4645      int fromX, fromY, toX, toY;
4646      int promoChar;
4647      Board board;
4648 {
4649     ChessSquare captured = board[toY][toX];
4650     if (fromY == DROP_RANK) {
4651         /* must be first */
4652         board[toY][toX] = (ChessSquare) fromX;
4653     } else if (fromX == toX && fromY == toY) {
4654         return;
4655     } else if (fromY == 0 && fromX == 4
4656         && board[fromY][fromX] == WhiteKing
4657         && toY == 0 && toX == 6) {
4658         board[fromY][fromX] = EmptySquare;
4659         board[toY][toX] = WhiteKing;
4660         board[fromY][7] = EmptySquare;
4661         board[toY][5] = WhiteRook;
4662     } else if (fromY == 0 && fromX == 4
4663                && board[fromY][fromX] == WhiteKing
4664                && toY == 0 && toX == 2) {
4665         board[fromY][fromX] = EmptySquare;
4666         board[toY][toX] = WhiteKing;
4667         board[fromY][0] = EmptySquare;
4668         board[toY][3] = WhiteRook;
4669     } else if (fromY == 0 && fromX == 3
4670                && board[fromY][fromX] == WhiteKing
4671                && toY == 0 && toX == 5) {
4672         board[fromY][fromX] = EmptySquare;
4673         board[toY][toX] = WhiteKing;
4674         board[fromY][7] = EmptySquare;
4675         board[toY][4] = WhiteRook;
4676     } else if (fromY == 0 && fromX == 3
4677                && board[fromY][fromX] == WhiteKing
4678                && toY == 0 && toX == 1) {
4679         board[fromY][fromX] = EmptySquare;
4680         board[toY][toX] = WhiteKing;
4681         board[fromY][0] = EmptySquare;
4682         board[toY][2] = WhiteRook;
4683     } else if (board[fromY][fromX] == WhitePawn
4684                && toY == 7) {
4685         /* white pawn promotion */
4686         board[7][toX] = CharToPiece(ToUpper(promoChar));
4687         if (board[7][toX] == EmptySquare) {
4688             board[7][toX] = WhiteQueen;
4689         }
4690         board[fromY][fromX] = EmptySquare;
4691     } else if ((fromY == 4)
4692                && (toX != fromX)
4693                && (board[fromY][fromX] == WhitePawn)
4694                && (board[toY][toX] == EmptySquare)) {
4695         board[fromY][fromX] = EmptySquare;
4696         board[toY][toX] = WhitePawn;
4697         captured = board[toY - 1][toX];
4698         board[toY - 1][toX] = EmptySquare;
4699     } else if (fromY == 7 && fromX == 4
4700                && board[fromY][fromX] == BlackKing
4701                && toY == 7 && toX == 6) {
4702         board[fromY][fromX] = EmptySquare;
4703         board[toY][toX] = BlackKing;
4704         board[fromY][7] = EmptySquare;
4705         board[toY][5] = BlackRook;
4706     } else if (fromY == 7 && fromX == 4
4707                && board[fromY][fromX] == BlackKing
4708                && toY == 7 && toX == 2) {
4709         board[fromY][fromX] = EmptySquare;
4710         board[toY][toX] = BlackKing;
4711         board[fromY][0] = EmptySquare;
4712         board[toY][3] = BlackRook;
4713     } else if (fromY == 7 && fromX == 3
4714                && board[fromY][fromX] == BlackKing
4715                && toY == 7 && toX == 5) {
4716         board[fromY][fromX] = EmptySquare;
4717         board[toY][toX] = BlackKing;
4718         board[fromY][7] = EmptySquare;
4719         board[toY][4] = BlackRook;
4720     } else if (fromY == 7 && fromX == 3
4721                && board[fromY][fromX] == BlackKing
4722                && toY == 7 && toX == 1) {
4723         board[fromY][fromX] = EmptySquare;
4724         board[toY][toX] = BlackKing;
4725         board[fromY][0] = EmptySquare;
4726         board[toY][2] = BlackRook;
4727     } else if (board[fromY][fromX] == BlackPawn
4728                && toY == 0) {
4729         /* black pawn promotion */
4730         board[0][toX] = CharToPiece(ToLower(promoChar));
4731         if (board[0][toX] == EmptySquare) {
4732             board[0][toX] = BlackQueen;
4733         }
4734         board[fromY][fromX] = EmptySquare;
4735     } else if ((fromY == 3)
4736                && (toX != fromX)
4737                && (board[fromY][fromX] == BlackPawn)
4738                && (board[toY][toX] == EmptySquare)) {
4739         board[fromY][fromX] = EmptySquare;
4740         board[toY][toX] = BlackPawn;
4741         captured = board[toY + 1][toX];
4742         board[toY + 1][toX] = EmptySquare;
4743     } else {
4744         board[toY][toX] = board[fromY][fromX];
4745         board[fromY][fromX] = EmptySquare;
4746     }
4747     if (gameInfo.variant == VariantCrazyhouse) {
4748 #if 0
4749       /* !!A lot more code needs to be written to support holdings */
4750       if (fromY == DROP_RANK) {
4751         /* Delete from holdings */
4752         if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
4753       }
4754       if (captured != EmptySquare) {
4755         /* Add to holdings */
4756         if (captured < BlackPawn) {
4757           holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
4758         } else {
4759           holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
4760         }
4761       }
4762 #endif
4763     } else if (gameInfo.variant == VariantAtomic) {
4764       if (captured != EmptySquare) {
4765         int y, x;
4766         for (y = toY-1; y <= toY+1; y++) {
4767           for (x = toX-1; x <= toX+1; x++) {
4768             if (y >= 0 && y <= 7 && x >= 0 && x <= 7 &&
4769                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
4770               board[y][x] = EmptySquare;
4771             }
4772           }
4773         }
4774         board[toY][toX] = EmptySquare;
4775       }
4776     }
4777 }
4778
4779 /* Updates forwardMostMove */
4780 void
4781 MakeMove(fromX, fromY, toX, toY, promoChar)
4782      int fromX, fromY, toX, toY;
4783      int promoChar;
4784 {
4785     forwardMostMove++;
4786     if (forwardMostMove >= MAX_MOVES) {
4787       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
4788                         0, 1);
4789       return;
4790     }
4791     SwitchClocks();
4792     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
4793     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
4794     if (commentList[forwardMostMove] != NULL) {
4795         free(commentList[forwardMostMove]);
4796         commentList[forwardMostMove] = NULL;
4797     }
4798     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
4799     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
4800     gameInfo.result = GameUnfinished;
4801     if (gameInfo.resultDetails != NULL) {
4802         free(gameInfo.resultDetails);
4803         gameInfo.resultDetails = NULL;
4804     }
4805     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
4806                               moveList[forwardMostMove - 1]);
4807     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
4808                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
4809                              fromY, fromX, toY, toX, promoChar,
4810                              parseList[forwardMostMove - 1]);
4811     switch (MateTest(boards[forwardMostMove],
4812                      PosFlags(forwardMostMove), EP_UNKNOWN)){
4813       case MT_NONE:
4814       case MT_STALEMATE:
4815       default:
4816         break;
4817       case MT_CHECK:
4818         strcat(parseList[forwardMostMove - 1], "+");
4819         break;
4820       case MT_CHECKMATE:
4821         strcat(parseList[forwardMostMove - 1], "#");
4822         break;
4823     }
4824 }
4825
4826 /* Updates currentMove if not pausing */
4827 void
4828 ShowMove(fromX, fromY, toX, toY)
4829 {
4830     int instant = (gameMode == PlayFromGameFile) ?
4831         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
4832     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
4833         if (!instant) {
4834             if (forwardMostMove == currentMove + 1) {
4835                 AnimateMove(boards[forwardMostMove - 1],
4836                             fromX, fromY, toX, toY);
4837             }
4838             if (appData.highlightLastMove) {
4839                 SetHighlights(fromX, fromY, toX, toY);
4840             }
4841         }
4842         currentMove = forwardMostMove;
4843     }
4844
4845     if (instant) return;
4846     DisplayMove(currentMove - 1);
4847     DrawPosition(FALSE, boards[currentMove]);
4848     DisplayBothClocks();
4849     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
4850 }
4851
4852
4853 void
4854 InitChessProgram(cps)
4855      ChessProgramState *cps;
4856 {
4857     char buf[MSG_SIZ];
4858     if (appData.noChessProgram) return;
4859     hintRequested = FALSE;
4860     bookRequested = FALSE;
4861     SendToProgram(cps->initString, cps);
4862     if (gameInfo.variant != VariantNormal &&
4863         gameInfo.variant != VariantLoadable) {
4864       char *v = VariantName(gameInfo.variant);
4865       if (StrStr(cps->variants, v) == NULL) {
4866         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
4867         DisplayFatalError(buf, 0, 1);
4868         return;
4869       }
4870       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
4871       SendToProgram(buf, cps);
4872     }
4873     if (cps->sendICS) {
4874       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
4875       SendToProgram(buf, cps);
4876     }
4877     cps->maybeThinking = FALSE;
4878     cps->offeredDraw = 0;
4879     if (!appData.icsActive) {
4880         SendTimeControl(cps, movesPerSession, timeControl,
4881                         timeIncrement, appData.searchDepth,
4882                         searchTime);
4883     }
4884     if (appData.showThinking) {
4885         SendToProgram("post\n", cps);
4886     }
4887     SendToProgram("hard\n", cps);
4888     if (!appData.ponderNextMove) {
4889         /* Warning: "easy" is a toggle in GNU Chess, so don't send
4890            it without being sure what state we are in first.  "hard"
4891            is not a toggle, so that one is OK.
4892          */
4893         SendToProgram("easy\n", cps);
4894     }
4895     if (cps->usePing) {
4896       sprintf(buf, "ping %d\n", ++cps->lastPing);
4897       SendToProgram(buf, cps);
4898     }
4899     cps->initDone = TRUE;
4900 }   
4901
4902
4903 void
4904 StartChessProgram(cps)
4905      ChessProgramState *cps;
4906 {
4907     char buf[MSG_SIZ];
4908     int err;
4909
4910     if (appData.noChessProgram) return;
4911     cps->initDone = FALSE;
4912
4913     if (strcmp(cps->host, "localhost") == 0) {
4914         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
4915     } else if (*appData.remoteShell == NULLCHAR) {
4916         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
4917     } else {
4918         if (*appData.remoteUser == NULLCHAR) {
4919             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
4920                     cps->program);
4921         } else {
4922             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
4923                     cps->host, appData.remoteUser, cps->program);
4924         }
4925         err = StartChildProcess(buf, "", &cps->pr);
4926     }
4927     
4928     if (err != 0) {
4929         sprintf(buf, "Startup failure on '%s'", cps->program);
4930         DisplayFatalError(buf, err, 1);
4931         cps->pr = NoProc;
4932         cps->isr = NULL;
4933         return;
4934     }
4935     
4936     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
4937     if (cps->protocolVersion > 1) {
4938       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
4939       SendToProgram(buf, cps);
4940     } else {
4941       SendToProgram("xboard\n", cps);
4942     }
4943 }
4944
4945
4946 void
4947 TwoMachinesEventIfReady P((void))
4948 {
4949   if (first.lastPing != first.lastPong) {
4950     DisplayMessage("", "Waiting for first chess program");
4951     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4952     return;
4953   }
4954   if (second.lastPing != second.lastPong) {
4955     DisplayMessage("", "Waiting for second chess program");
4956     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
4957     return;
4958   }
4959   ThawUI();
4960   TwoMachinesEvent();
4961 }
4962
4963 void
4964 NextMatchGame P((void))
4965 {
4966     Reset(FALSE, TRUE);
4967     if (*appData.loadGameFile != NULLCHAR) {
4968         LoadGameFromFile(appData.loadGameFile,
4969                          appData.loadGameIndex,
4970                          appData.loadGameFile, FALSE);
4971     } else if (*appData.loadPositionFile != NULLCHAR) {
4972         LoadPositionFromFile(appData.loadPositionFile,
4973                              appData.loadPositionIndex,
4974                              appData.loadPositionFile);
4975     }
4976     TwoMachinesEventIfReady();
4977 }
4978
4979 void
4980 GameEnds(result, resultDetails, whosays)
4981      ChessMove result;
4982      char *resultDetails;
4983      int whosays;
4984 {
4985     GameMode nextGameMode;
4986     int isIcsGame;
4987
4988     if (appData.debugMode) {
4989       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
4990               result, resultDetails ? resultDetails : "(null)", whosays);
4991     }
4992
4993     if (appData.icsActive && whosays == GE_ENGINE) {
4994         /* If we are playing on ICS, the server decides when the
4995            game is over, but the engine can offer to draw, claim 
4996            a draw, or resign. 
4997          */
4998 #if ZIPPY
4999         if (appData.zippyPlay && first.initDone) {
5000             if (result == GameIsDrawn) {
5001                 /* In case draw still needs to be claimed */
5002                 SendToICS(ics_prefix);
5003                 SendToICS("draw\n");
5004             } else if (StrCaseStr(resultDetails, "resign")) {
5005                 SendToICS(ics_prefix);
5006                 SendToICS("resign\n");
5007             }
5008         }
5009 #endif
5010         return;
5011     }
5012
5013     /* If we're loading the game from a file, stop */
5014     if (whosays == GE_FILE) {
5015       (void) StopLoadGameTimer();
5016       gameFileFP = NULL;
5017     }
5018
5019     /* Cancel draw offers */
5020    first.offeredDraw = second.offeredDraw = 0;
5021
5022     /* If this is an ICS game, only ICS can really say it's done;
5023        if not, anyone can. */
5024     isIcsGame = (gameMode == IcsPlayingWhite || 
5025                  gameMode == IcsPlayingBlack || 
5026                  gameMode == IcsObserving    || 
5027                  gameMode == IcsExamining);
5028
5029     if (!isIcsGame || whosays == GE_ICS) {
5030         /* OK -- not an ICS game, or ICS said it was done */
5031         StopClocks();
5032         if (!isIcsGame && !appData.noChessProgram) 
5033           SetUserThinkingEnables();
5034     
5035         if (resultDetails != NULL) {
5036             gameInfo.result = result;
5037             gameInfo.resultDetails = StrSave(resultDetails);
5038
5039             /* Tell program how game ended in case it is learning */
5040             if (gameMode == MachinePlaysWhite ||
5041                 gameMode == MachinePlaysBlack ||
5042                 gameMode == TwoMachinesPlay ||
5043                 gameMode == IcsPlayingWhite ||
5044                 gameMode == IcsPlayingBlack ||
5045                 gameMode == BeginningOfGame) {
5046                 char buf[MSG_SIZ];
5047                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
5048                         resultDetails);
5049                 if (first.pr != NoProc) {
5050                     SendToProgram(buf, &first);
5051                 }
5052                 if (second.pr != NoProc &&
5053                     gameMode == TwoMachinesPlay) {
5054                     SendToProgram(buf, &second);
5055                 }
5056             }
5057
5058             /* display last move only if game was not loaded from file */
5059             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
5060                 DisplayMove(currentMove - 1);
5061     
5062             if (forwardMostMove != 0) {
5063                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
5064                     if (*appData.saveGameFile != NULLCHAR) {
5065                         SaveGameToFile(appData.saveGameFile, TRUE);
5066                     } else if (appData.autoSaveGames) {
5067                         AutoSaveGame();
5068                     }
5069                     if (*appData.savePositionFile != NULLCHAR) {
5070                         SavePositionToFile(appData.savePositionFile);
5071                     }
5072                 }
5073             }
5074         }
5075
5076         if (appData.icsActive) {
5077             if (appData.quietPlay &&
5078                 (gameMode == IcsPlayingWhite ||
5079                  gameMode == IcsPlayingBlack)) {
5080                 SendToICS(ics_prefix);
5081                 SendToICS("set shout 1\n");
5082             }
5083             nextGameMode = IcsIdle;
5084             ics_user_moved = FALSE;
5085             /* clean up premove.  It's ugly when the game has ended and the
5086              * premove highlights are still on the board.
5087              */
5088             if (gotPremove) {
5089               gotPremove = FALSE;
5090               ClearPremoveHighlights();
5091               DrawPosition(FALSE, boards[currentMove]);
5092             }
5093             if (whosays == GE_ICS) {
5094                 switch (result) {
5095                 case WhiteWins:
5096                     if (gameMode == IcsPlayingWhite)
5097                         PlayIcsWinSound();
5098                     else if(gameMode == IcsPlayingBlack)
5099                         PlayIcsLossSound();
5100                     break;
5101                 case BlackWins:
5102                     if (gameMode == IcsPlayingBlack)
5103                         PlayIcsWinSound();
5104                     else if(gameMode == IcsPlayingWhite)
5105                         PlayIcsLossSound();
5106                     break;
5107                 case GameIsDrawn:
5108                     PlayIcsDrawSound();
5109                     break;
5110                 default:
5111                     PlayIcsUnfinishedSound();
5112                 }
5113             }
5114         } else if (gameMode == EditGame ||
5115                    gameMode == PlayFromGameFile || 
5116                    gameMode == AnalyzeMode || 
5117                    gameMode == AnalyzeFile) {
5118             nextGameMode = gameMode;
5119         } else {
5120             nextGameMode = EndOfGame;
5121         }
5122         pausing = FALSE;
5123         ModeHighlight();
5124     } else {
5125         nextGameMode = gameMode;
5126     }
5127
5128     if (appData.noChessProgram) {
5129         gameMode = nextGameMode;
5130         ModeHighlight();
5131         return;
5132     }
5133
5134     if (first.reuse) {
5135         /* Put first chess program into idle state */
5136         if (first.pr != NoProc &&
5137             (gameMode == MachinePlaysWhite ||
5138              gameMode == MachinePlaysBlack ||
5139              gameMode == TwoMachinesPlay ||
5140              gameMode == IcsPlayingWhite ||
5141              gameMode == IcsPlayingBlack ||
5142              gameMode == BeginningOfGame)) {
5143             SendToProgram("force\n", &first);
5144             if (first.usePing) {
5145               char buf[MSG_SIZ];
5146               sprintf(buf, "ping %d\n", ++first.lastPing);
5147               SendToProgram(buf, &first);
5148             }
5149         }
5150     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5151         /* Kill off first chess program */
5152         if (first.isr != NULL)
5153           RemoveInputSource(first.isr);
5154         first.isr = NULL;
5155     
5156         if (first.pr != NoProc) {
5157             ExitAnalyzeMode();
5158             SendToProgram("quit\n", &first);
5159             DestroyChildProcess(first.pr, first.useSigterm);
5160         }
5161         first.pr = NoProc;
5162     }
5163     if (second.reuse) {
5164         /* Put second chess program into idle state */
5165         if (second.pr != NoProc &&
5166             gameMode == TwoMachinesPlay) {
5167             SendToProgram("force\n", &second);
5168             if (second.usePing) {
5169               char buf[MSG_SIZ];
5170               sprintf(buf, "ping %d\n", ++second.lastPing);
5171               SendToProgram(buf, &second);
5172             }
5173         }
5174     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
5175         /* Kill off second chess program */
5176         if (second.isr != NULL)
5177           RemoveInputSource(second.isr);
5178         second.isr = NULL;
5179     
5180         if (second.pr != NoProc) {
5181             SendToProgram("quit\n", &second);
5182             DestroyChildProcess(second.pr, second.useSigterm);
5183         }
5184         second.pr = NoProc;
5185     }
5186
5187     if (matchMode && gameMode == TwoMachinesPlay) {
5188         switch (result) {
5189         case WhiteWins:
5190           if (first.twoMachinesColor[0] == 'w') {
5191             first.matchWins++;
5192           } else {
5193             second.matchWins++;
5194           }
5195           break;
5196         case BlackWins:
5197           if (first.twoMachinesColor[0] == 'b') {
5198             first.matchWins++;
5199           } else {
5200             second.matchWins++;
5201           }
5202           break;
5203         default:
5204           break;
5205         }
5206         if (matchGame < appData.matchGames) {
5207             char *tmp;
5208             tmp = first.twoMachinesColor;
5209             first.twoMachinesColor = second.twoMachinesColor;
5210             second.twoMachinesColor = tmp;
5211             gameMode = nextGameMode;
5212             matchGame++;
5213             ScheduleDelayedEvent(NextMatchGame, 10000);
5214             return;
5215         } else {
5216             char buf[MSG_SIZ];
5217             gameMode = nextGameMode;
5218             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
5219                     first.tidy, second.tidy,
5220                     first.matchWins, second.matchWins,
5221                     appData.matchGames - (first.matchWins + second.matchWins));
5222             DisplayFatalError(buf, 0, 0);
5223         }
5224     }
5225     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5226         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
5227       ExitAnalyzeMode();
5228     gameMode = nextGameMode;
5229     ModeHighlight();
5230 }
5231
5232 /* Assumes program was just initialized (initString sent).
5233    Leaves program in force mode. */
5234 void
5235 FeedMovesToProgram(cps, upto) 
5236      ChessProgramState *cps;
5237      int upto;
5238 {
5239     int i;
5240     
5241     if (appData.debugMode)
5242       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
5243               startedFromSetupPosition ? "position and " : "",
5244               backwardMostMove, upto, cps->which);
5245     SendToProgram("force\n", cps);
5246     if (startedFromSetupPosition) {
5247         SendBoard(cps, backwardMostMove);
5248     }
5249     for (i = backwardMostMove; i < upto; i++) {
5250         SendMoveToProgram(i, cps);
5251     }
5252 }
5253
5254
5255 void
5256 ResurrectChessProgram()
5257 {
5258      /* The chess program may have exited.
5259         If so, restart it and feed it all the moves made so far. */
5260
5261     if (appData.noChessProgram || first.pr != NoProc) return;
5262     
5263     StartChessProgram(&first);
5264     InitChessProgram(&first);
5265     FeedMovesToProgram(&first, currentMove);
5266
5267     if (!first.sendTime) {
5268         /* can't tell gnuchess what its clock should read,
5269            so we bow to its notion. */
5270         ResetClocks();
5271         timeRemaining[0][currentMove] = whiteTimeRemaining;
5272         timeRemaining[1][currentMove] = blackTimeRemaining;
5273     }
5274
5275     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
5276         first.analysisSupport) {
5277       SendToProgram("analyze\n", &first);
5278       first.analyzing = TRUE;
5279     }
5280 }
5281
5282 /*
5283  * Button procedures
5284  */
5285 void
5286 Reset(redraw, init)
5287      int redraw, init;
5288 {
5289     int i;
5290
5291     if (appData.debugMode) {
5292         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
5293                 redraw, init, gameMode);
5294     }
5295
5296     pausing = pauseExamInvalid = FALSE;
5297     startedFromSetupPosition = blackPlaysFirst = FALSE;
5298     firstMove = TRUE;
5299     whiteFlag = blackFlag = FALSE;
5300     userOfferedDraw = FALSE;
5301     hintRequested = bookRequested = FALSE;
5302     first.maybeThinking = FALSE;
5303     second.maybeThinking = FALSE;
5304     thinkOutput[0] = NULLCHAR;
5305     lastHint[0] = NULLCHAR;
5306     ClearGameInfo(&gameInfo);
5307     gameInfo.variant = StringToVariant(appData.variant);
5308     ics_user_moved = ics_clock_paused = FALSE;
5309     ics_getting_history = H_FALSE;
5310     ics_gamenum = -1;
5311     white_holding[0] = black_holding[0] = NULLCHAR;
5312     ClearProgramStats();
5313     
5314     ResetFrontEnd();
5315     ClearHighlights();
5316     flipView = appData.flipView;
5317     ClearPremoveHighlights();
5318     gotPremove = FALSE;
5319     alarmSounded = FALSE;
5320
5321     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
5322     ExitAnalyzeMode();
5323     gameMode = BeginningOfGame;
5324     ModeHighlight();
5325     InitPosition(redraw);
5326     for (i = 0; i < MAX_MOVES; i++) {
5327         if (commentList[i] != NULL) {
5328             free(commentList[i]);
5329             commentList[i] = NULL;
5330         }
5331     }
5332     ResetClocks();
5333     timeRemaining[0][0] = whiteTimeRemaining;
5334     timeRemaining[1][0] = blackTimeRemaining;
5335     if (first.pr == NULL) {
5336         StartChessProgram(&first);
5337     }
5338     if (init) InitChessProgram(&first);
5339     DisplayTitle("");
5340     DisplayMessage("", "");
5341     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5342 }
5343
5344 void
5345 AutoPlayGameLoop()
5346 {
5347     for (;;) {
5348         if (!AutoPlayOneMove())
5349           return;
5350         if (matchMode || appData.timeDelay == 0)
5351           continue;
5352         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
5353           return;
5354         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
5355         break;
5356     }
5357 }
5358
5359
5360 int
5361 AutoPlayOneMove()
5362 {
5363     int fromX, fromY, toX, toY;
5364
5365     if (appData.debugMode) {
5366       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
5367     }
5368
5369     if (gameMode != PlayFromGameFile)
5370       return FALSE;
5371
5372     if (currentMove >= forwardMostMove) {
5373       gameMode = EditGame;
5374       ModeHighlight();
5375       return FALSE;
5376     }
5377     
5378     toX = moveList[currentMove][2] - 'a';
5379     toY = moveList[currentMove][3] - '1';
5380
5381     if (moveList[currentMove][1] == '@') {
5382         if (appData.highlightLastMove) {
5383             SetHighlights(-1, -1, toX, toY);
5384         }
5385     } else {
5386         fromX = moveList[currentMove][0] - 'a';
5387         fromY = moveList[currentMove][1] - '1';
5388         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
5389
5390         if (appData.highlightLastMove) {
5391             SetHighlights(fromX, fromY, toX, toY);
5392         }
5393     }
5394     DisplayMove(currentMove);
5395     SendMoveToProgram(currentMove++, &first);
5396     DisplayBothClocks();
5397     DrawPosition(FALSE, boards[currentMove]);
5398     if (commentList[currentMove] != NULL) {
5399         DisplayComment(currentMove - 1, commentList[currentMove]);
5400     }
5401     return TRUE;
5402 }
5403
5404
5405 int
5406 LoadGameOneMove(readAhead)
5407      ChessMove readAhead;
5408 {
5409     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
5410     char promoChar = NULLCHAR;
5411     ChessMove moveType;
5412     char move[MSG_SIZ];
5413     char *p, *q;
5414     
5415     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
5416         gameMode != AnalyzeMode && gameMode != Training) {
5417         gameFileFP = NULL;
5418         return FALSE;
5419     }
5420     
5421     yyboardindex = forwardMostMove;
5422     if (readAhead != (ChessMove)0) {
5423       moveType = readAhead;
5424     } else {
5425       if (gameFileFP == NULL)
5426           return FALSE;
5427       moveType = (ChessMove) yylex();
5428     }
5429     
5430     done = FALSE;
5431     switch (moveType) {
5432       case Comment:
5433         if (appData.debugMode) 
5434           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
5435         p = yy_text;
5436         if (*p == '{' || *p == '[' || *p == '(') {
5437             p[strlen(p) - 1] = NULLCHAR;
5438             p++;
5439         }
5440
5441         /* append the comment but don't display it */
5442         while (*p == '\n') p++;
5443         AppendComment(currentMove, p);
5444         return TRUE;
5445
5446       case WhiteCapturesEnPassant:
5447       case BlackCapturesEnPassant:
5448       case WhitePromotionQueen:
5449       case BlackPromotionQueen:
5450       case WhitePromotionRook:
5451       case BlackPromotionRook:
5452       case WhitePromotionBishop:
5453       case BlackPromotionBishop:
5454       case WhitePromotionKnight:
5455       case BlackPromotionKnight:
5456       case WhitePromotionKing:
5457       case BlackPromotionKing:
5458       case NormalMove:
5459       case WhiteKingSideCastle:
5460       case WhiteQueenSideCastle:
5461       case BlackKingSideCastle:
5462       case BlackQueenSideCastle:
5463       case WhiteKingSideCastleWild:
5464       case WhiteQueenSideCastleWild:
5465       case BlackKingSideCastleWild:
5466       case BlackQueenSideCastleWild:
5467         if (appData.debugMode)
5468           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5469         fromX = currentMoveString[0] - 'a';
5470         fromY = currentMoveString[1] - '1';
5471         toX = currentMoveString[2] - 'a';
5472         toY = currentMoveString[3] - '1';
5473         promoChar = currentMoveString[4];
5474         break;
5475
5476       case WhiteDrop:
5477       case BlackDrop:
5478         if (appData.debugMode)
5479           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
5480         fromX = moveType == WhiteDrop ?
5481           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5482         (int) CharToPiece(ToLower(currentMoveString[0]));
5483         fromY = DROP_RANK;
5484         toX = currentMoveString[2] - 'a';
5485         toY = currentMoveString[3] - '1';
5486         break;
5487
5488       case WhiteWins:
5489       case BlackWins:
5490       case GameIsDrawn:
5491       case GameUnfinished:
5492         if (appData.debugMode)
5493           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
5494         p = strchr(yy_text, '{');
5495         if (p == NULL) p = strchr(yy_text, '(');
5496         if (p == NULL) {
5497             p = yy_text;
5498             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
5499         } else {
5500             q = strchr(p, *p == '{' ? '}' : ')');
5501             if (q != NULL) *q = NULLCHAR;
5502             p++;
5503         }
5504         GameEnds(moveType, p, GE_FILE);
5505         done = TRUE;
5506         if (cmailMsgLoaded) {
5507             ClearHighlights();
5508             flipView = WhiteOnMove(currentMove);
5509             if (moveType == GameUnfinished) flipView = !flipView;
5510             if (appData.debugMode)
5511               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
5512         }
5513         break;
5514
5515       case (ChessMove) 0:       /* end of file */
5516         if (appData.debugMode)
5517           fprintf(debugFP, "Parser hit end of file\n");
5518         switch (MateTest(boards[currentMove], PosFlags(currentMove),
5519                          EP_UNKNOWN)) {
5520           case MT_NONE:
5521           case MT_CHECK:
5522             break;
5523           case MT_CHECKMATE:
5524             if (WhiteOnMove(currentMove)) {
5525                 GameEnds(BlackWins, "Black mates", GE_FILE);
5526             } else {
5527                 GameEnds(WhiteWins, "White mates", GE_FILE);
5528             }
5529             break;
5530           case MT_STALEMATE:
5531             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5532             break;
5533         }
5534         done = TRUE;
5535         break;
5536
5537       case MoveNumberOne:
5538         if (lastLoadGameStart == GNUChessGame) {
5539             /* GNUChessGames have numbers, but they aren't move numbers */
5540             if (appData.debugMode)
5541               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5542                       yy_text, (int) moveType);
5543             return LoadGameOneMove((ChessMove)0); /* tail recursion */
5544         }
5545         /* else fall thru */
5546
5547       case XBoardGame:
5548       case GNUChessGame:
5549       case PGNTag:
5550         /* Reached start of next game in file */
5551         if (appData.debugMode)
5552           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
5553         switch (MateTest(boards[currentMove], PosFlags(currentMove),
5554                          EP_UNKNOWN)) {
5555           case MT_NONE:
5556           case MT_CHECK:
5557             break;
5558           case MT_CHECKMATE:
5559             if (WhiteOnMove(currentMove)) {
5560                 GameEnds(BlackWins, "Black mates", GE_FILE);
5561             } else {
5562                 GameEnds(WhiteWins, "White mates", GE_FILE);
5563             }
5564             break;
5565           case MT_STALEMATE:
5566             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
5567             break;
5568         }
5569         done = TRUE;
5570         break;
5571
5572       case PositionDiagram:     /* should not happen; ignore */
5573       case ElapsedTime:         /* ignore */
5574       case NAG:                 /* ignore */
5575         if (appData.debugMode)
5576           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
5577                   yy_text, (int) moveType);
5578         return LoadGameOneMove((ChessMove)0); /* tail recursion */
5579
5580       case IllegalMove:
5581         if (appData.testLegality) {
5582             if (appData.debugMode)
5583               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
5584             sprintf(move, "Illegal move: %d.%s%s",
5585                     (forwardMostMove / 2) + 1,
5586                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5587             DisplayError(move, 0);
5588             done = TRUE;
5589         } else {
5590             if (appData.debugMode)
5591               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
5592                       yy_text, currentMoveString);
5593             fromX = currentMoveString[0] - 'a';
5594             fromY = currentMoveString[1] - '1';
5595             toX = currentMoveString[2] - 'a';
5596             toY = currentMoveString[3] - '1';
5597             promoChar = currentMoveString[4];
5598         }
5599         break;
5600
5601       case AmbiguousMove:
5602         if (appData.debugMode)
5603           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
5604         sprintf(move, "Ambiguous move: %d.%s%s",
5605                 (forwardMostMove / 2) + 1,
5606                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5607         DisplayError(move, 0);
5608         done = TRUE;
5609         break;
5610
5611       default:
5612       case ImpossibleMove:
5613         if (appData.debugMode)
5614           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
5615         sprintf(move, "Illegal move: %d.%s%s",
5616                 (forwardMostMove / 2) + 1,
5617                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
5618         DisplayError(move, 0);
5619         done = TRUE;
5620         break;
5621     }
5622
5623     if (done) {
5624         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
5625             DrawPosition(FALSE, boards[currentMove]);
5626             DisplayBothClocks();
5627             if (!appData.matchMode && commentList[currentMove] != NULL)
5628               DisplayComment(currentMove - 1, commentList[currentMove]);
5629         }
5630         (void) StopLoadGameTimer();
5631         gameFileFP = NULL;
5632         cmailOldMove = forwardMostMove;
5633         return FALSE;
5634     } else {
5635         /* currentMoveString is set as a side-effect of yylex */
5636         strcat(currentMoveString, "\n");
5637         strcpy(moveList[forwardMostMove], currentMoveString);
5638         
5639         thinkOutput[0] = NULLCHAR;
5640         MakeMove(fromX, fromY, toX, toY, promoChar);
5641         currentMove = forwardMostMove;
5642         return TRUE;
5643     }
5644 }
5645
5646 /* Load the nth game from the given file */
5647 int
5648 LoadGameFromFile(filename, n, title, useList)
5649      char *filename;
5650      int n;
5651      char *title;
5652      /*Boolean*/ int useList;
5653 {
5654     FILE *f;
5655     char buf[MSG_SIZ];
5656
5657     if (strcmp(filename, "-") == 0) {
5658         f = stdin;
5659         title = "stdin";
5660     } else {
5661         f = fopen(filename, "rb");
5662         if (f == NULL) {
5663             sprintf(buf, "Can't open \"%s\"", filename);
5664             DisplayError(buf, errno);
5665             return FALSE;
5666         }
5667     }
5668     if (fseek(f, 0, 0) == -1) {
5669         /* f is not seekable; probably a pipe */
5670         useList = FALSE;
5671     }
5672     if (useList && n == 0) {
5673         int error = GameListBuild(f);
5674         if (error) {
5675             DisplayError("Cannot build game list", error);
5676         } else if (!ListEmpty(&gameList) &&
5677                    ((ListGame *) gameList.tailPred)->number > 1) {
5678             GameListPopUp(f, title);
5679             return TRUE;
5680         }
5681         GameListDestroy();
5682         n = 1;
5683     }
5684     if (n == 0) n = 1;
5685     return LoadGame(f, n, title, FALSE);
5686 }
5687
5688
5689 void
5690 MakeRegisteredMove()
5691 {
5692     int fromX, fromY, toX, toY;
5693     char promoChar;
5694     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5695         switch (cmailMoveType[lastLoadGameNumber - 1]) {
5696           case CMAIL_MOVE:
5697           case CMAIL_DRAW:
5698             if (appData.debugMode)
5699               fprintf(debugFP, "Restoring %s for game %d\n",
5700                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
5701     
5702             thinkOutput[0] = NULLCHAR;
5703             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
5704             fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
5705             fromY = cmailMove[lastLoadGameNumber - 1][1] - '1';
5706             toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
5707             toY = cmailMove[lastLoadGameNumber - 1][3] - '1';
5708             promoChar = cmailMove[lastLoadGameNumber - 1][4];
5709             MakeMove(fromX, fromY, toX, toY, promoChar);
5710             ShowMove(fromX, fromY, toX, toY);
5711               
5712             switch (MateTest(boards[currentMove], PosFlags(currentMove),
5713                              EP_UNKNOWN)) {
5714               case MT_NONE:
5715               case MT_CHECK:
5716                 break;
5717                 
5718               case MT_CHECKMATE:
5719                 if (WhiteOnMove(currentMove)) {
5720                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
5721                 } else {
5722                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
5723                 }
5724                 break;
5725                 
5726               case MT_STALEMATE:
5727                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5728                 break;
5729             }
5730
5731             break;
5732             
5733           case CMAIL_RESIGN:
5734             if (WhiteOnMove(currentMove)) {
5735                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
5736             } else {
5737                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
5738             }
5739             break;
5740             
5741           case CMAIL_ACCEPT:
5742             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
5743             break;
5744               
5745           default:
5746             break;
5747         }
5748     }
5749
5750     return;
5751 }
5752
5753 /* Wrapper around LoadGame for use when a Cmail message is loaded */
5754 int
5755 CmailLoadGame(f, gameNumber, title, useList)
5756      FILE *f;
5757      int gameNumber;
5758      char *title;
5759      int useList;
5760 {
5761     int retVal;
5762
5763     if (gameNumber > nCmailGames) {
5764         DisplayError("No more games in this message", 0);
5765         return FALSE;
5766     }
5767     if (f == lastLoadGameFP) {
5768         int offset = gameNumber - lastLoadGameNumber;
5769         if (offset == 0) {
5770             cmailMsg[0] = NULLCHAR;
5771             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
5772                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
5773                 nCmailMovesRegistered--;
5774             }
5775             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5776             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
5777                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
5778             }
5779         } else {
5780             if (! RegisterMove()) return FALSE;
5781         }
5782     }
5783
5784     retVal = LoadGame(f, gameNumber, title, useList);
5785
5786     /* Make move registered during previous look at this game, if any */
5787     MakeRegisteredMove();
5788
5789     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
5790         commentList[currentMove]
5791           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
5792         DisplayComment(currentMove - 1, commentList[currentMove]);
5793     }
5794
5795     return retVal;
5796 }
5797
5798 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
5799 int
5800 ReloadGame(offset)
5801      int offset;
5802 {
5803     int gameNumber = lastLoadGameNumber + offset;
5804     if (lastLoadGameFP == NULL) {
5805         DisplayError("No game has been loaded yet", 0);
5806         return FALSE;
5807     }
5808     if (gameNumber <= 0) {
5809         DisplayError("Can't back up any further", 0);
5810         return FALSE;
5811     }
5812     if (cmailMsgLoaded) {
5813         return CmailLoadGame(lastLoadGameFP, gameNumber,
5814                              lastLoadGameTitle, lastLoadGameUseList);
5815     } else {
5816         return LoadGame(lastLoadGameFP, gameNumber,
5817                         lastLoadGameTitle, lastLoadGameUseList);
5818     }
5819 }
5820
5821
5822
5823 /* Load the nth game from open file f */
5824 int
5825 LoadGame(f, gameNumber, title, useList)
5826      FILE *f;
5827      int gameNumber;
5828      char *title;
5829      int useList;
5830 {
5831     ChessMove cm;
5832     char buf[MSG_SIZ];
5833     int gn = gameNumber;
5834     ListGame *lg = NULL;
5835     int numPGNTags = 0;
5836     int err;
5837     GameMode oldGameMode;
5838
5839     if (appData.debugMode) 
5840         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
5841
5842     if (gameMode == Training )
5843         SetTrainingModeOff();
5844
5845     oldGameMode = gameMode;
5846     if (gameMode != BeginningOfGame) {
5847       Reset(FALSE, TRUE);
5848     }
5849
5850     gameFileFP = f;
5851     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
5852         fclose(lastLoadGameFP);
5853     }
5854
5855     if (useList) {
5856         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
5857         
5858         if (lg) {
5859             fseek(f, lg->offset, 0);
5860             GameListHighlight(gameNumber);
5861             gn = 1;
5862         }
5863         else {
5864             DisplayError("Game number out of range", 0);
5865             return FALSE;
5866         }
5867     } else {
5868         GameListDestroy();
5869         if (fseek(f, 0, 0) == -1) {
5870             if (f == lastLoadGameFP ?
5871                 gameNumber == lastLoadGameNumber + 1 :
5872                 gameNumber == 1) {
5873                 gn = 1;
5874             } else {
5875                 DisplayError("Can't seek on game file", 0);
5876                 return FALSE;
5877             }
5878         }
5879     }
5880     lastLoadGameFP = f;
5881     lastLoadGameNumber = gameNumber;
5882     strcpy(lastLoadGameTitle, title);
5883     lastLoadGameUseList = useList;
5884
5885     yynewfile(f);
5886
5887
5888     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
5889         sprintf(buf, "%s vs. %s", lg->gameInfo.white,
5890                 lg->gameInfo.black);
5891             DisplayTitle(buf);
5892     } else if (*title != NULLCHAR) {
5893         if (gameNumber > 1) {
5894             sprintf(buf, "%s %d", title, gameNumber);
5895             DisplayTitle(buf);
5896         } else {
5897             DisplayTitle(title);
5898         }
5899     }
5900
5901     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
5902         gameMode = PlayFromGameFile;
5903         ModeHighlight();
5904     }
5905
5906     currentMove = forwardMostMove = backwardMostMove = 0;
5907     CopyBoard(boards[0], initialPosition);
5908     StopClocks();
5909
5910     /*
5911      * Skip the first gn-1 games in the file.
5912      * Also skip over anything that precedes an identifiable 
5913      * start of game marker, to avoid being confused by 
5914      * garbage at the start of the file.  Currently 
5915      * recognized start of game markers are the move number "1",
5916      * the pattern "gnuchess .* game", the pattern
5917      * "^[#;%] [^ ]* game file", and a PGN tag block.  
5918      * A game that starts with one of the latter two patterns
5919      * will also have a move number 1, possibly
5920      * following a position diagram.
5921      * 5-4-02: Let's try being more lenient and allowing a game to
5922      * start with an unnumbered move.  Does that break anything?
5923      */
5924     cm = lastLoadGameStart = (ChessMove) 0;
5925     while (gn > 0) {
5926         yyboardindex = forwardMostMove;
5927         cm = (ChessMove) yylex();
5928         switch (cm) {
5929           case (ChessMove) 0:
5930             if (cmailMsgLoaded) {
5931                 nCmailGames = CMAIL_MAX_GAMES - gn;
5932             } else {
5933                 Reset(TRUE, TRUE);
5934                 DisplayError("Game not found in file", 0);
5935             }
5936             return FALSE;
5937
5938           case GNUChessGame:
5939           case XBoardGame:
5940             gn--;
5941             lastLoadGameStart = cm;
5942             break;
5943             
5944           case MoveNumberOne:
5945             switch (lastLoadGameStart) {
5946               case GNUChessGame:
5947               case XBoardGame:
5948               case PGNTag:
5949                 break;
5950               case MoveNumberOne:
5951               case (ChessMove) 0:
5952                 gn--;           /* count this game */
5953                 lastLoadGameStart = cm;
5954                 break;
5955               default:
5956                 /* impossible */
5957                 break;
5958             }
5959             break;
5960
5961           case PGNTag:
5962             switch (lastLoadGameStart) {
5963               case GNUChessGame:
5964               case PGNTag:
5965               case MoveNumberOne:
5966               case (ChessMove) 0:
5967                 gn--;           /* count this game */
5968                 lastLoadGameStart = cm;
5969                 break;
5970               case XBoardGame:
5971                 lastLoadGameStart = cm; /* game counted already */
5972                 break;
5973               default:
5974                 /* impossible */
5975                 break;
5976             }
5977             if (gn > 0) {
5978                 do {
5979                     yyboardindex = forwardMostMove;
5980                     cm = (ChessMove) yylex();
5981                 } while (cm == PGNTag || cm == Comment);
5982             }
5983             break;
5984
5985           case WhiteWins:
5986           case BlackWins:
5987           case GameIsDrawn:
5988             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
5989                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
5990                     != CMAIL_OLD_RESULT) {
5991                     nCmailResults ++ ;
5992                     cmailResult[  CMAIL_MAX_GAMES
5993                                 - gn - 1] = CMAIL_OLD_RESULT;
5994                 }
5995             }
5996             break;
5997
5998           case NormalMove:
5999             /* Only a NormalMove can be at the start of a game
6000              * without a position diagram. */
6001             if (lastLoadGameStart == (ChessMove) 0) {
6002               gn--;
6003               lastLoadGameStart = MoveNumberOne;
6004             }
6005             break;
6006
6007           default:
6008             break;
6009         }
6010     }
6011     
6012     if (appData.debugMode)
6013       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
6014
6015     if (cm == XBoardGame) {
6016         /* Skip any header junk before position diagram and/or move 1 */
6017         for (;;) {
6018             yyboardindex = forwardMostMove;
6019             cm = (ChessMove) yylex();
6020
6021             if (cm == (ChessMove) 0 ||
6022                 cm == GNUChessGame || cm == XBoardGame) {
6023                 /* Empty game; pretend end-of-file and handle later */
6024                 cm = (ChessMove) 0;
6025                 break;
6026             }
6027
6028             if (cm == MoveNumberOne || cm == PositionDiagram ||
6029                 cm == PGNTag || cm == Comment)
6030               break;
6031         }
6032     } else if (cm == GNUChessGame) {
6033         if (gameInfo.event != NULL) {
6034             free(gameInfo.event);
6035         }
6036         gameInfo.event = StrSave(yy_text);
6037     }   
6038
6039     startedFromSetupPosition = FALSE;
6040     while (cm == PGNTag) {
6041         if (appData.debugMode) 
6042           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
6043         err = ParsePGNTag(yy_text, &gameInfo);
6044         if (!err) numPGNTags++;
6045
6046         if (gameInfo.fen != NULL) {
6047           Board initial_position;
6048           startedFromSetupPosition = TRUE;
6049           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
6050             Reset(TRUE, TRUE);
6051             DisplayError("Bad FEN position in file", 0);
6052             return FALSE;
6053           }
6054           CopyBoard(boards[0], initial_position);
6055           if (blackPlaysFirst) {
6056             currentMove = forwardMostMove = backwardMostMove = 1;
6057             CopyBoard(boards[1], initial_position);
6058             strcpy(moveList[0], "");
6059             strcpy(parseList[0], "");
6060             timeRemaining[0][1] = whiteTimeRemaining;
6061             timeRemaining[1][1] = blackTimeRemaining;
6062             if (commentList[0] != NULL) {
6063               commentList[1] = commentList[0];
6064               commentList[0] = NULL;
6065             }
6066           } else {
6067             currentMove = forwardMostMove = backwardMostMove = 0;
6068           }
6069           yyboardindex = forwardMostMove;
6070           free(gameInfo.fen);
6071           gameInfo.fen = NULL;
6072         }
6073
6074         yyboardindex = forwardMostMove;
6075         cm = (ChessMove) yylex();
6076
6077         /* Handle comments interspersed among the tags */
6078         while (cm == Comment) {
6079             char *p;
6080             if (appData.debugMode) 
6081               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6082             p = yy_text;
6083             if (*p == '{' || *p == '[' || *p == '(') {
6084                 p[strlen(p) - 1] = NULLCHAR;
6085                 p++;
6086             }
6087             while (*p == '\n') p++;
6088             AppendComment(currentMove, p);
6089             yyboardindex = forwardMostMove;
6090             cm = (ChessMove) yylex();
6091         }
6092     }
6093
6094     /* don't rely on existence of Event tag since if game was
6095      * pasted from clipboard the Event tag may not exist
6096      */
6097     if (numPGNTags > 0){
6098         char *tags;
6099         if (gameInfo.variant == VariantNormal) {
6100           gameInfo.variant = StringToVariant(gameInfo.event);
6101         }
6102         if (!matchMode) {
6103           tags = PGNTags(&gameInfo);
6104           TagsPopUp(tags, CmailMsg());
6105           free(tags);
6106         }
6107     } else {
6108         /* Make something up, but don't display it now */
6109         SetGameInfo();
6110         TagsPopDown();
6111     }
6112
6113     if (cm == PositionDiagram) {
6114         int i, j;
6115         char *p;
6116         Board initial_position;
6117
6118         if (appData.debugMode)
6119           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
6120
6121         if (!startedFromSetupPosition) {
6122             p = yy_text;
6123             for (i = BOARD_SIZE - 1; i >= 0; i--)
6124               for (j = 0; j < BOARD_SIZE; p++)
6125                 switch (*p) {
6126                   case '[':
6127                   case '-':
6128                   case ' ':
6129                   case '\t':
6130                   case '\n':
6131                   case '\r':
6132                     break;
6133                   default:
6134                     initial_position[i][j++] = CharToPiece(*p);
6135                     break;
6136                 }
6137             while (*p == ' ' || *p == '\t' ||
6138                    *p == '\n' || *p == '\r') p++;
6139         
6140             if (strncmp(p, "black", strlen("black"))==0)
6141               blackPlaysFirst = TRUE;
6142             else
6143               blackPlaysFirst = FALSE;
6144             startedFromSetupPosition = TRUE;
6145         
6146             CopyBoard(boards[0], initial_position);
6147             if (blackPlaysFirst) {
6148                 currentMove = forwardMostMove = backwardMostMove = 1;
6149                 CopyBoard(boards[1], initial_position);
6150                 strcpy(moveList[0], "");
6151                 strcpy(parseList[0], "");
6152                 timeRemaining[0][1] = whiteTimeRemaining;
6153                 timeRemaining[1][1] = blackTimeRemaining;
6154                 if (commentList[0] != NULL) {
6155                     commentList[1] = commentList[0];
6156                     commentList[0] = NULL;
6157                 }
6158             } else {
6159                 currentMove = forwardMostMove = backwardMostMove = 0;
6160             }
6161         }
6162         yyboardindex = forwardMostMove;
6163         cm = (ChessMove) yylex();
6164     }
6165
6166     if (first.pr == NoProc) {
6167         StartChessProgram(&first);
6168     }
6169     InitChessProgram(&first);
6170     SendToProgram("force\n", &first);
6171     if (startedFromSetupPosition) {
6172         SendBoard(&first, forwardMostMove);
6173         DisplayBothClocks();
6174     }      
6175
6176     while (cm == Comment) {
6177         char *p;
6178         if (appData.debugMode) 
6179           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
6180         p = yy_text;
6181         if (*p == '{' || *p == '[' || *p == '(') {
6182             p[strlen(p) - 1] = NULLCHAR;
6183             p++;
6184         }
6185         while (*p == '\n') p++;
6186         AppendComment(currentMove, p);
6187         yyboardindex = forwardMostMove;
6188         cm = (ChessMove) yylex();
6189     }
6190
6191     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
6192         cm == WhiteWins || cm == BlackWins ||
6193         cm == GameIsDrawn || cm == GameUnfinished) {
6194         DisplayMessage("", "No moves in game");
6195         if (cmailMsgLoaded) {
6196             if (appData.debugMode)
6197               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
6198             ClearHighlights();
6199             flipView = FALSE;
6200         }
6201         DrawPosition(FALSE, boards[currentMove]);
6202         DisplayBothClocks();
6203         gameMode = EditGame;
6204         ModeHighlight();
6205         gameFileFP = NULL;
6206         cmailOldMove = 0;
6207         return TRUE;
6208     }
6209
6210     if (commentList[currentMove] != NULL) {
6211       if (!matchMode && (pausing || appData.timeDelay != 0)) {
6212         DisplayComment(currentMove - 1, commentList[currentMove]);
6213       }
6214     }
6215     if (!matchMode && appData.timeDelay != 0) 
6216       DrawPosition(FALSE, boards[currentMove]);
6217
6218     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
6219       programStats.ok_to_send = 1;
6220     }
6221
6222     /* if the first token after the PGN tags is a move
6223      * and not move number 1, retrieve it from the parser 
6224      */
6225     if (cm != MoveNumberOne)
6226         LoadGameOneMove(cm);
6227
6228     /* load the remaining moves from the file */
6229     while (LoadGameOneMove((ChessMove)0)) {
6230       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
6231       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
6232     }
6233
6234     /* rewind to the start of the game */
6235     currentMove = backwardMostMove;
6236
6237     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
6238
6239     if (oldGameMode == AnalyzeFile ||
6240         oldGameMode == AnalyzeMode) {
6241       AnalyzeFileEvent();
6242     }
6243
6244     if (matchMode || appData.timeDelay == 0) {
6245       ToEndEvent();
6246       gameMode = EditGame;
6247       ModeHighlight();
6248     } else if (appData.timeDelay > 0) {
6249       AutoPlayGameLoop();
6250     }
6251
6252     if (appData.debugMode) 
6253         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
6254     return TRUE;
6255 }
6256
6257 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
6258 int
6259 ReloadPosition(offset)
6260      int offset;
6261 {
6262     int positionNumber = lastLoadPositionNumber + offset;
6263     if (lastLoadPositionFP == NULL) {
6264         DisplayError("No position has been loaded yet", 0);
6265         return FALSE;
6266     }
6267     if (positionNumber <= 0) {
6268         DisplayError("Can't back up any further", 0);
6269         return FALSE;
6270     }
6271     return LoadPosition(lastLoadPositionFP, positionNumber,
6272                         lastLoadPositionTitle);
6273 }
6274
6275 /* Load the nth position from the given file */
6276 int
6277 LoadPositionFromFile(filename, n, title)
6278      char *filename;
6279      int n;
6280      char *title;
6281 {
6282     FILE *f;
6283     char buf[MSG_SIZ];
6284
6285     if (strcmp(filename, "-") == 0) {
6286         return LoadPosition(stdin, n, "stdin");
6287     } else {
6288         f = fopen(filename, "rb");
6289         if (f == NULL) {
6290             sprintf(buf, "Can't open \"%s\"", filename);
6291             DisplayError(buf, errno);
6292             return FALSE;
6293         } else {
6294             return LoadPosition(f, n, title);
6295         }
6296     }
6297 }
6298
6299 /* Load the nth position from the given open file, and close it */
6300 int
6301 LoadPosition(f, positionNumber, title)
6302      FILE *f;
6303      int positionNumber;
6304      char *title;
6305 {
6306     char *p, line[MSG_SIZ];
6307     Board initial_position;
6308     int i, j, fenMode, pn;
6309     
6310     if (gameMode == Training )
6311         SetTrainingModeOff();
6312
6313     if (gameMode != BeginningOfGame) {
6314         Reset(FALSE, TRUE);
6315     }
6316     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
6317         fclose(lastLoadPositionFP);
6318     }
6319     if (positionNumber == 0) positionNumber = 1;
6320     lastLoadPositionFP = f;
6321     lastLoadPositionNumber = positionNumber;
6322     strcpy(lastLoadPositionTitle, title);
6323     if (first.pr == NoProc) {
6324       StartChessProgram(&first);
6325       InitChessProgram(&first);
6326     }    
6327     pn = positionNumber;
6328     if (positionNumber < 0) {
6329         /* Negative position number means to seek to that byte offset */
6330         if (fseek(f, -positionNumber, 0) == -1) {
6331             DisplayError("Can't seek on position file", 0);
6332             return FALSE;
6333         };
6334         pn = 1;
6335     } else {
6336         if (fseek(f, 0, 0) == -1) {
6337             if (f == lastLoadPositionFP ?
6338                 positionNumber == lastLoadPositionNumber + 1 :
6339                 positionNumber == 1) {
6340                 pn = 1;
6341             } else {
6342                 DisplayError("Can't seek on position file", 0);
6343                 return FALSE;
6344             }
6345         }
6346     }
6347     /* See if this file is FEN or old-style xboard */
6348     if (fgets(line, MSG_SIZ, f) == NULL) {
6349         DisplayError("Position not found in file", 0);
6350         return FALSE;
6351     }
6352     switch (line[0]) {
6353       case '#':  case 'x':
6354       default:
6355         fenMode = FALSE;
6356         break;
6357       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
6358       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
6359       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
6360       case '7':  case '8':
6361         fenMode = TRUE;
6362         break;
6363     }
6364
6365     if (pn >= 2) {
6366         if (fenMode || line[0] == '#') pn--;
6367         while (pn > 0) {
6368             /* skip postions before number pn */
6369             if (fgets(line, MSG_SIZ, f) == NULL) {
6370                 Reset(TRUE, TRUE);
6371                 DisplayError("Position not found in file", 0);
6372                 return FALSE;
6373             }
6374             if (fenMode || line[0] == '#') pn--;
6375         }
6376     }
6377
6378     if (fenMode) {
6379         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
6380             DisplayError("Bad FEN position in file", 0);
6381             return FALSE;
6382         }
6383     } else {
6384         (void) fgets(line, MSG_SIZ, f);
6385         (void) fgets(line, MSG_SIZ, f);
6386     
6387         for (i = BOARD_SIZE - 1; i >= 0; i--) {
6388             (void) fgets(line, MSG_SIZ, f);
6389             for (p = line, j = 0; j < BOARD_SIZE; p++) {
6390                 if (*p == ' ')
6391                   continue;
6392                 initial_position[i][j++] = CharToPiece(*p);
6393             }
6394         }
6395     
6396         blackPlaysFirst = FALSE;
6397         if (!feof(f)) {
6398             (void) fgets(line, MSG_SIZ, f);
6399             if (strncmp(line, "black", strlen("black"))==0)
6400               blackPlaysFirst = TRUE;
6401         }
6402     }
6403     startedFromSetupPosition = TRUE;
6404     
6405     SendToProgram("force\n", &first);
6406     CopyBoard(boards[0], initial_position);
6407     if (blackPlaysFirst) {
6408         currentMove = forwardMostMove = backwardMostMove = 1;
6409         strcpy(moveList[0], "");
6410         strcpy(parseList[0], "");
6411         CopyBoard(boards[1], initial_position);
6412         DisplayMessage("", "Black to play");
6413     } else {
6414         currentMove = forwardMostMove = backwardMostMove = 0;
6415         DisplayMessage("", "White to play");
6416     }
6417     SendBoard(&first, forwardMostMove);
6418
6419     if (positionNumber > 1) {
6420         sprintf(line, "%s %d", title, positionNumber);
6421         DisplayTitle(line);
6422     } else {
6423         DisplayTitle(title);
6424     }
6425     gameMode = EditGame;
6426     ModeHighlight();
6427     ResetClocks();
6428     timeRemaining[0][1] = whiteTimeRemaining;
6429     timeRemaining[1][1] = blackTimeRemaining;
6430     DrawPosition(FALSE, boards[currentMove]);
6431    
6432     return TRUE;
6433 }
6434
6435
6436 void
6437 CopyPlayerNameIntoFileName(dest, src)
6438      char **dest, *src;
6439 {
6440     while (*src != NULLCHAR && *src != ',') {
6441         if (*src == ' ') {
6442             *(*dest)++ = '_';
6443             src++;
6444         } else {
6445             *(*dest)++ = *src++;
6446         }
6447     }
6448 }
6449
6450 char *DefaultFileName(ext)
6451      char *ext;
6452 {
6453     static char def[MSG_SIZ];
6454     char *p;
6455
6456     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
6457         p = def;
6458         CopyPlayerNameIntoFileName(&p, gameInfo.white);
6459         *p++ = '-';
6460         CopyPlayerNameIntoFileName(&p, gameInfo.black);
6461         *p++ = '.';
6462         strcpy(p, ext);
6463     } else {
6464         def[0] = NULLCHAR;
6465     }
6466     return def;
6467 }
6468
6469 /* Save the current game to the given file */
6470 int
6471 SaveGameToFile(filename, append)
6472      char *filename;
6473      int append;
6474 {
6475     FILE *f;
6476     char buf[MSG_SIZ];
6477
6478     if (strcmp(filename, "-") == 0) {
6479         return SaveGame(stdout, 0, NULL);
6480     } else {
6481         f = fopen(filename, append ? "a" : "w");
6482         if (f == NULL) {
6483             sprintf(buf, "Can't open \"%s\"", filename);
6484             DisplayError(buf, errno);
6485             return FALSE;
6486         } else {
6487             return SaveGame(f, 0, NULL);
6488         }
6489     }
6490 }
6491
6492 char *
6493 SavePart(str)
6494      char *str;
6495 {
6496     static char buf[MSG_SIZ];
6497     char *p;
6498     
6499     p = strchr(str, ' ');
6500     if (p == NULL) return str;
6501     strncpy(buf, str, p - str);
6502     buf[p - str] = NULLCHAR;
6503     return buf;
6504 }
6505
6506 #define PGN_MAX_LINE 75
6507
6508 /* Save game in PGN style and close the file */
6509 int
6510 SaveGamePGN(f)
6511      FILE *f;
6512 {
6513     int i, offset, linelen, newblock;
6514     time_t tm;
6515     char *movetext;
6516     char numtext[32];
6517     int movelen, numlen, blank;
6518     
6519     tm = time((time_t *) NULL);
6520     
6521     PrintPGNTags(f, &gameInfo);
6522     
6523     if (backwardMostMove > 0 || startedFromSetupPosition) {
6524         char *fen = PositionToFEN(backwardMostMove);
6525         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
6526         fprintf(f, "\n{--------------\n");
6527         PrintPosition(f, backwardMostMove);
6528         fprintf(f, "--------------}\n");
6529         free(fen);
6530     } else {
6531         fprintf(f, "\n");
6532     }
6533
6534     i = backwardMostMove;
6535     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6536     linelen = 0;
6537     newblock = TRUE;
6538
6539     while (i < forwardMostMove) {
6540         /* Print comments preceding this move */
6541         if (commentList[i] != NULL) {
6542             if (linelen > 0) fprintf(f, "\n");
6543             fprintf(f, "{\n%s}\n", commentList[i]);
6544             linelen = 0;
6545             newblock = TRUE;
6546         }
6547
6548         /* Format move number */
6549         if ((i % 2) == 0) {
6550             sprintf(numtext, "%d.", (i - offset)/2 + 1);
6551         } else {
6552             if (newblock) {
6553                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
6554             } else {
6555                 numtext[0] = NULLCHAR;
6556             }
6557         }
6558         numlen = strlen(numtext);
6559         newblock = FALSE;
6560
6561         /* Print move number */
6562         blank = linelen > 0 && numlen > 0;
6563         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
6564             fprintf(f, "\n");
6565             linelen = 0;
6566             blank = 0;
6567         }
6568         if (blank) {
6569             fprintf(f, " ");
6570             linelen++;
6571         }
6572         fprintf(f, numtext);
6573         linelen += numlen;
6574
6575         /* Get move */
6576         movetext = SavePart(parseList[i]);
6577         movelen = strlen(movetext);
6578
6579         /* Print move */
6580         blank = linelen > 0 && movelen > 0;
6581         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
6582             fprintf(f, "\n");
6583             linelen = 0;
6584             blank = 0;
6585         }
6586         if (blank) {
6587             fprintf(f, " ");
6588             linelen++;
6589         }
6590         fprintf(f, movetext);
6591         linelen += movelen;
6592
6593         i++;
6594     }
6595     
6596     /* Start a new line */
6597     if (linelen > 0) fprintf(f, "\n");
6598
6599     /* Print comments after last move */
6600     if (commentList[i] != NULL) {
6601         fprintf(f, "{\n%s}\n", commentList[i]);
6602     }
6603
6604     /* Print result */
6605     if (gameInfo.resultDetails != NULL &&
6606         gameInfo.resultDetails[0] != NULLCHAR) {
6607         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
6608                 PGNResult(gameInfo.result));
6609     } else {
6610         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6611     }
6612
6613     fclose(f);
6614     return TRUE;
6615 }
6616
6617 /* Save game in old style and close the file */
6618 int
6619 SaveGameOldStyle(f)
6620      FILE *f;
6621 {
6622     int i, offset;
6623     time_t tm;
6624     
6625     tm = time((time_t *) NULL);
6626     
6627     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
6628     PrintOpponents(f);
6629     
6630     if (backwardMostMove > 0 || startedFromSetupPosition) {
6631         fprintf(f, "\n[--------------\n");
6632         PrintPosition(f, backwardMostMove);
6633         fprintf(f, "--------------]\n");
6634     } else {
6635         fprintf(f, "\n");
6636     }
6637
6638     i = backwardMostMove;
6639     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
6640
6641     while (i < forwardMostMove) {
6642         if (commentList[i] != NULL) {
6643             fprintf(f, "[%s]\n", commentList[i]);
6644         }
6645
6646         if ((i % 2) == 1) {
6647             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
6648             i++;
6649         } else {
6650             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
6651             i++;
6652             if (commentList[i] != NULL) {
6653                 fprintf(f, "\n");
6654                 continue;
6655             }
6656             if (i >= forwardMostMove) {
6657                 fprintf(f, "\n");
6658                 break;
6659             }
6660             fprintf(f, "%s\n", parseList[i]);
6661             i++;
6662         }
6663     }
6664     
6665     if (commentList[i] != NULL) {
6666         fprintf(f, "[%s]\n", commentList[i]);
6667     }
6668
6669     /* This isn't really the old style, but it's close enough */
6670     if (gameInfo.resultDetails != NULL &&
6671         gameInfo.resultDetails[0] != NULLCHAR) {
6672         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
6673                 gameInfo.resultDetails);
6674     } else {
6675         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
6676     }
6677
6678     fclose(f);
6679     return TRUE;
6680 }
6681
6682 /* Save the current game to open file f and close the file */
6683 int
6684 SaveGame(f, dummy, dummy2)
6685      FILE *f;
6686      int dummy;
6687      char *dummy2;
6688 {
6689     if (gameMode == EditPosition) EditPositionDone();
6690     if (appData.oldSaveStyle)
6691       return SaveGameOldStyle(f);
6692     else
6693       return SaveGamePGN(f);
6694 }
6695
6696 /* Save the current position to the given file */
6697 int
6698 SavePositionToFile(filename)
6699      char *filename;
6700 {
6701     FILE *f;
6702     char buf[MSG_SIZ];
6703
6704     if (strcmp(filename, "-") == 0) {
6705         return SavePosition(stdout, 0, NULL);
6706     } else {
6707         f = fopen(filename, "a");
6708         if (f == NULL) {
6709             sprintf(buf, "Can't open \"%s\"", filename);
6710             DisplayError(buf, errno);
6711             return FALSE;
6712         } else {
6713             SavePosition(f, 0, NULL);
6714             return TRUE;
6715         }
6716     }
6717 }
6718
6719 /* Save the current position to the given open file and close the file */
6720 int
6721 SavePosition(f, dummy, dummy2)
6722      FILE *f;
6723      int dummy;
6724      char *dummy2;
6725 {
6726     time_t tm;
6727     char *fen;
6728     
6729     if (appData.oldSaveStyle) {
6730         tm = time((time_t *) NULL);
6731     
6732         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
6733         PrintOpponents(f);
6734         fprintf(f, "[--------------\n");
6735         PrintPosition(f, currentMove);
6736         fprintf(f, "--------------]\n");
6737     } else {
6738         fen = PositionToFEN(currentMove);
6739         fprintf(f, "%s\n", fen);
6740         free(fen);
6741     }
6742     fclose(f);
6743     return TRUE;
6744 }
6745
6746 void
6747 ReloadCmailMsgEvent(unregister)
6748      int unregister;
6749 {
6750 #if !WIN32
6751     static char *inFilename = NULL;
6752     static char *outFilename;
6753     int i;
6754     struct stat inbuf, outbuf;
6755     int status;
6756     
6757     /* Any registered moves are unregistered if unregister is set, */
6758     /* i.e. invoked by the signal handler */
6759     if (unregister) {
6760         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6761             cmailMoveRegistered[i] = FALSE;
6762             if (cmailCommentList[i] != NULL) {
6763                 free(cmailCommentList[i]);
6764                 cmailCommentList[i] = NULL;
6765             }
6766         }
6767         nCmailMovesRegistered = 0;
6768     }
6769
6770     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
6771         cmailResult[i] = CMAIL_NOT_RESULT;
6772     }
6773     nCmailResults = 0;
6774
6775     if (inFilename == NULL) {
6776         /* Because the filenames are static they only get malloced once  */
6777         /* and they never get freed                                      */
6778         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
6779         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
6780
6781         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
6782         sprintf(outFilename, "%s.out", appData.cmailGameName);
6783     }
6784     
6785     status = stat(outFilename, &outbuf);
6786     if (status < 0) {
6787         cmailMailedMove = FALSE;
6788     } else {
6789         status = stat(inFilename, &inbuf);
6790         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
6791     }
6792     
6793     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
6794        counts the games, notes how each one terminated, etc.
6795        
6796        It would be nice to remove this kludge and instead gather all
6797        the information while building the game list.  (And to keep it
6798        in the game list nodes instead of having a bunch of fixed-size
6799        parallel arrays.)  Note this will require getting each game's
6800        termination from the PGN tags, as the game list builder does
6801        not process the game moves.  --mann
6802        */
6803     cmailMsgLoaded = TRUE;
6804     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
6805     
6806     /* Load first game in the file or popup game menu */
6807     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
6808
6809 #endif /* !WIN32 */
6810     return;
6811 }
6812
6813 int
6814 RegisterMove()
6815 {
6816     FILE *f;
6817     char string[MSG_SIZ];
6818
6819     if (   cmailMailedMove
6820         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
6821         return TRUE;            /* Allow free viewing  */
6822     }
6823
6824     /* Unregister move to ensure that we don't leave RegisterMove        */
6825     /* with the move registered when the conditions for registering no   */
6826     /* longer hold                                                       */
6827     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
6828         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
6829         nCmailMovesRegistered --;
6830
6831         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
6832           {
6833               free(cmailCommentList[lastLoadGameNumber - 1]);
6834               cmailCommentList[lastLoadGameNumber - 1] = NULL;
6835           }
6836     }
6837
6838     if (cmailOldMove == -1) {
6839         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
6840         return FALSE;
6841     }
6842
6843     if (currentMove > cmailOldMove + 1) {
6844         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
6845         return FALSE;
6846     }
6847
6848     if (currentMove < cmailOldMove) {
6849         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
6850         return FALSE;
6851     }
6852
6853     if (forwardMostMove > currentMove) {
6854         /* Silently truncate extra moves */
6855         TruncateGame();
6856     }
6857
6858     if (   (currentMove == cmailOldMove + 1)
6859         || (   (currentMove == cmailOldMove)
6860             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
6861                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
6862         if (gameInfo.result != GameUnfinished) {
6863             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
6864         }
6865
6866         if (commentList[currentMove] != NULL) {
6867             cmailCommentList[lastLoadGameNumber - 1]
6868               = StrSave(commentList[currentMove]);
6869         }
6870         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
6871
6872         if (appData.debugMode)
6873           fprintf(debugFP, "Saving %s for game %d\n",
6874                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
6875
6876         sprintf(string,
6877                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
6878         
6879         f = fopen(string, "w");
6880         if (appData.oldSaveStyle) {
6881             SaveGameOldStyle(f); /* also closes the file */
6882             
6883             sprintf(string, "%s.pos.out", appData.cmailGameName);
6884             f = fopen(string, "w");
6885             SavePosition(f, 0, NULL); /* also closes the file */
6886         } else {
6887             fprintf(f, "{--------------\n");
6888             PrintPosition(f, currentMove);
6889             fprintf(f, "--------------}\n\n");
6890             
6891             SaveGame(f, 0, NULL); /* also closes the file*/
6892         }
6893         
6894         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
6895         nCmailMovesRegistered ++;
6896     } else if (nCmailGames == 1) {
6897         DisplayError("You have not made a move yet", 0);
6898         return FALSE;
6899     }
6900
6901     return TRUE;
6902 }
6903
6904 void
6905 MailMoveEvent()
6906 {
6907 #if !WIN32
6908     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
6909     FILE *commandOutput;
6910     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
6911     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
6912     int nBuffers;
6913     int i;
6914     int archived;
6915     char *arcDir;
6916
6917     if (! cmailMsgLoaded) {
6918         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
6919         return;
6920     }
6921
6922     if (nCmailGames == nCmailResults) {
6923         DisplayError("No unfinished games", 0);
6924         return;
6925     }
6926
6927 #if CMAIL_PROHIBIT_REMAIL
6928     if (cmailMailedMove) {
6929         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);
6930         DisplayError(msg, 0);
6931         return;
6932     }
6933 #endif
6934
6935     if (! (cmailMailedMove || RegisterMove())) return;
6936     
6937     if (   cmailMailedMove
6938         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
6939         sprintf(string, partCommandString,
6940                 appData.debugMode ? " -v" : "", appData.cmailGameName);
6941         commandOutput = popen(string, "rb");
6942
6943         if (commandOutput == NULL) {
6944             DisplayError("Failed to invoke cmail", 0);
6945         } else {
6946             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
6947                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
6948             }
6949             if (nBuffers > 1) {
6950                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
6951                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
6952                 nBytes = MSG_SIZ - 1;
6953             } else {
6954                 (void) memcpy(msg, buffer, nBytes);
6955             }
6956             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
6957
6958             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
6959                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
6960
6961                 archived = TRUE;
6962                 for (i = 0; i < nCmailGames; i ++) {
6963                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
6964                         archived = FALSE;
6965                     }
6966                 }
6967                 if (   archived
6968                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
6969                         != NULL)) {
6970                     sprintf(buffer, "%s/%s.%s.archive",
6971                             arcDir,
6972                             appData.cmailGameName,
6973                             gameInfo.date);
6974                     LoadGameFromFile(buffer, 1, buffer, FALSE);
6975                     cmailMsgLoaded = FALSE;
6976                 }
6977             }
6978
6979             DisplayInformation(msg);
6980             pclose(commandOutput);
6981         }
6982     } else {
6983         if ((*cmailMsg) != '\0') {
6984             DisplayInformation(cmailMsg);
6985         }
6986     }
6987
6988     return;
6989 #endif /* !WIN32 */
6990 }
6991
6992 char *
6993 CmailMsg()
6994 {
6995 #if WIN32
6996     return NULL;
6997 #else
6998     int  prependComma = 0;
6999     char number[5];
7000     char string[MSG_SIZ];       /* Space for game-list */
7001     int  i;
7002     
7003     if (!cmailMsgLoaded) return "";
7004
7005     if (cmailMailedMove) {
7006         sprintf(cmailMsg, "Waiting for reply from opponent\n");
7007     } else {
7008         /* Create a list of games left */
7009         sprintf(string, "[");
7010         for (i = 0; i < nCmailGames; i ++) {
7011             if (! (   cmailMoveRegistered[i]
7012                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
7013                 if (prependComma) {
7014                     sprintf(number, ",%d", i + 1);
7015                 } else {
7016                     sprintf(number, "%d", i + 1);
7017                     prependComma = 1;
7018                 }
7019                 
7020                 strcat(string, number);
7021             }
7022         }
7023         strcat(string, "]");
7024
7025         if (nCmailMovesRegistered + nCmailResults == 0) {
7026             switch (nCmailGames) {
7027               case 1:
7028                 sprintf(cmailMsg,
7029                         "Still need to make move for game\n");
7030                 break;
7031                 
7032               case 2:
7033                 sprintf(cmailMsg,
7034                         "Still need to make moves for both games\n");
7035                 break;
7036                 
7037               default:
7038                 sprintf(cmailMsg,
7039                         "Still need to make moves for all %d games\n",
7040                         nCmailGames);
7041                 break;
7042             }
7043         } else {
7044             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
7045               case 1:
7046                 sprintf(cmailMsg,
7047                         "Still need to make a move for game %s\n",
7048                         string);
7049                 break;
7050                 
7051               case 0:
7052                 if (nCmailResults == nCmailGames) {
7053                     sprintf(cmailMsg, "No unfinished games\n");
7054                 } else {
7055                     sprintf(cmailMsg, "Ready to send mail\n");
7056                 }
7057                 break;
7058                 
7059               default:
7060                 sprintf(cmailMsg,
7061                         "Still need to make moves for games %s\n",
7062                         string);
7063             }
7064         }
7065     }
7066     return cmailMsg;
7067 #endif /* WIN32 */
7068 }
7069
7070 void
7071 ResetGameEvent()
7072 {
7073     if (gameMode == Training)
7074       SetTrainingModeOff();
7075
7076     Reset(TRUE, TRUE);
7077     cmailMsgLoaded = FALSE;
7078     if (appData.icsActive) {
7079       SendToICS(ics_prefix);
7080       SendToICS("refresh\n");
7081     }
7082 }
7083
7084 static int exiting = 0;
7085
7086 void
7087 ExitEvent(status)
7088      int status;
7089 {
7090     exiting++;
7091     if (exiting > 2) {
7092       /* Give up on clean exit */
7093       exit(status);
7094     }
7095     if (exiting > 1) {
7096       /* Keep trying for clean exit */
7097       return;
7098     }
7099
7100     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
7101
7102     if (telnetISR != NULL) {
7103       RemoveInputSource(telnetISR);
7104     }
7105     if (icsPR != NoProc) {
7106       DestroyChildProcess(icsPR, TRUE);
7107     }
7108     /* Save game if resource set and not already saved by GameEnds() */
7109     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
7110       if (*appData.saveGameFile != NULLCHAR) {
7111         SaveGameToFile(appData.saveGameFile, TRUE);
7112       } else if (appData.autoSaveGames) {
7113         AutoSaveGame();
7114       }
7115       if (*appData.savePositionFile != NULLCHAR) {
7116         SavePositionToFile(appData.savePositionFile);
7117       }
7118     }
7119     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7120
7121     /* Kill off chess programs */
7122     if (first.pr != NoProc) {
7123         ExitAnalyzeMode();
7124         SendToProgram("quit\n", &first);
7125         DestroyChildProcess(first.pr, first.useSigterm);
7126     }
7127     if (second.pr != NoProc) {
7128         SendToProgram("quit\n", &second);
7129         DestroyChildProcess(second.pr, second.useSigterm);
7130     }
7131     if (first.isr != NULL) {
7132         RemoveInputSource(first.isr);
7133     }
7134     if (second.isr != NULL) {
7135         RemoveInputSource(second.isr);
7136     }
7137
7138     ShutDownFrontEnd();
7139     exit(status);
7140 }
7141
7142 void
7143 PauseEvent()
7144 {
7145     if (appData.debugMode)
7146         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
7147     if (pausing) {
7148         pausing = FALSE;
7149         ModeHighlight();
7150         if (gameMode == MachinePlaysWhite ||
7151             gameMode == MachinePlaysBlack) {
7152             StartClocks();
7153         } else {
7154             DisplayBothClocks();
7155         }
7156         if (gameMode == PlayFromGameFile) {
7157             if (appData.timeDelay >= 0) 
7158                 AutoPlayGameLoop();
7159         } else if (gameMode == IcsExamining && pauseExamInvalid) {
7160             Reset(FALSE, TRUE);
7161             SendToICS(ics_prefix);
7162             SendToICS("refresh\n");
7163         } else if (currentMove < forwardMostMove) {
7164             ForwardInner(forwardMostMove);
7165         }
7166         pauseExamInvalid = FALSE;
7167     } else {
7168         switch (gameMode) {
7169           default:
7170             return;
7171           case IcsExamining:
7172             pauseExamForwardMostMove = forwardMostMove;
7173             pauseExamInvalid = FALSE;
7174             /* fall through */
7175           case IcsObserving:
7176           case IcsPlayingWhite:
7177           case IcsPlayingBlack:
7178             pausing = TRUE;
7179             ModeHighlight();
7180             return;
7181           case PlayFromGameFile:
7182             (void) StopLoadGameTimer();
7183             pausing = TRUE;
7184             ModeHighlight();
7185             break;
7186           case BeginningOfGame:
7187             if (appData.icsActive) return;
7188             /* else fall through */
7189           case MachinePlaysWhite:
7190           case MachinePlaysBlack:
7191           case TwoMachinesPlay:
7192             if (forwardMostMove == 0)
7193               return;           /* don't pause if no one has moved */
7194             if ((gameMode == MachinePlaysWhite &&
7195                  !WhiteOnMove(forwardMostMove)) ||
7196                 (gameMode == MachinePlaysBlack &&
7197                  WhiteOnMove(forwardMostMove))) {
7198                 StopClocks();
7199             }
7200             pausing = TRUE;
7201             ModeHighlight();
7202             break;
7203         }
7204     }
7205 }
7206
7207 void
7208 EditCommentEvent()
7209 {
7210     char title[MSG_SIZ];
7211
7212     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
7213         strcpy(title, "Edit comment");
7214     } else {
7215         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
7216                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
7217                 parseList[currentMove - 1]);
7218     }
7219
7220     EditCommentPopUp(currentMove, title, commentList[currentMove]);
7221 }
7222
7223
7224 void
7225 EditTagsEvent()
7226 {
7227     char *tags = PGNTags(&gameInfo);
7228     EditTagsPopUp(tags);
7229     free(tags);
7230 }
7231
7232 void
7233 AnalyzeModeEvent()
7234 {
7235     if (appData.noChessProgram || gameMode == AnalyzeMode)
7236       return;
7237
7238     if (gameMode != AnalyzeFile) {
7239         EditGameEvent();
7240         if (gameMode != EditGame) return;
7241         ResurrectChessProgram();
7242         SendToProgram("analyze\n", &first);
7243         first.analyzing = TRUE;
7244         /*first.maybeThinking = TRUE;*/
7245         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7246         AnalysisPopUp("Analysis",
7247                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7248     }
7249     gameMode = AnalyzeMode;
7250     pausing = FALSE;
7251     ModeHighlight();
7252     SetGameInfo();
7253
7254     StartAnalysisClock();
7255     GetTimeMark(&lastNodeCountTime);
7256     lastNodeCount = 0;
7257 }
7258
7259 void
7260 AnalyzeFileEvent()
7261 {
7262     if (appData.noChessProgram || gameMode == AnalyzeFile)
7263       return;
7264
7265     if (gameMode != AnalyzeMode) {
7266         EditGameEvent();
7267         if (gameMode != EditGame) return;
7268         ResurrectChessProgram();
7269         SendToProgram("analyze\n", &first);
7270         first.analyzing = TRUE;
7271         /*first.maybeThinking = TRUE;*/
7272         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
7273         AnalysisPopUp("Analysis",
7274                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
7275     }
7276     gameMode = AnalyzeFile;
7277     pausing = FALSE;
7278     ModeHighlight();
7279     SetGameInfo();
7280
7281     StartAnalysisClock();
7282     GetTimeMark(&lastNodeCountTime);
7283     lastNodeCount = 0;
7284 }
7285
7286 void
7287 MachineWhiteEvent()
7288 {
7289     char buf[MSG_SIZ];
7290
7291     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
7292       return;
7293
7294
7295     if (gameMode == PlayFromGameFile || 
7296         gameMode == TwoMachinesPlay  || 
7297         gameMode == Training         || 
7298         gameMode == AnalyzeMode      || 
7299         gameMode == EndOfGame)
7300         EditGameEvent();
7301
7302     if (gameMode == EditPosition) 
7303         EditPositionDone();
7304
7305     if (!WhiteOnMove(currentMove)) {
7306         DisplayError("It is not White's turn", 0);
7307         return;
7308     }
7309   
7310     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7311       ExitAnalyzeMode();
7312
7313     if (gameMode == EditGame || gameMode == AnalyzeMode || 
7314         gameMode == AnalyzeFile)
7315         TruncateGame();
7316
7317     ResurrectChessProgram();    /* in case it isn't running */
7318     gameMode = MachinePlaysWhite;
7319     pausing = FALSE;
7320     ModeHighlight();
7321     SetGameInfo();
7322     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7323     DisplayTitle(buf);
7324     if (first.sendName) {
7325       sprintf(buf, "name %s\n", gameInfo.black);
7326       SendToProgram(buf, &first);
7327     }
7328     if (first.sendTime) {
7329       if (first.useColors) {
7330         SendToProgram("black\n", &first); /*gnu kludge*/
7331       }
7332       SendTimeRemaining(&first, TRUE);
7333     }
7334     if (first.useColors) {
7335       SendToProgram("white\ngo\n", &first);
7336     } else {
7337       SendToProgram("go\n", &first);
7338     }
7339     SetMachineThinkingEnables();
7340     first.maybeThinking = TRUE;
7341     StartClocks();
7342
7343     if (appData.autoFlipView && !flipView) {
7344       flipView = !flipView;
7345       DrawPosition(FALSE, NULL);
7346     }
7347 }
7348
7349 void
7350 MachineBlackEvent()
7351 {
7352     char buf[MSG_SIZ];
7353
7354     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
7355         return;
7356
7357
7358     if (gameMode == PlayFromGameFile || 
7359         gameMode == TwoMachinesPlay  || 
7360         gameMode == Training         || 
7361         gameMode == AnalyzeMode      || 
7362         gameMode == EndOfGame)
7363         EditGameEvent();
7364
7365     if (gameMode == EditPosition) 
7366         EditPositionDone();
7367
7368     if (WhiteOnMove(currentMove)) {
7369         DisplayError("It is not Black's turn", 0);
7370         return;
7371     }
7372     
7373     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
7374       ExitAnalyzeMode();
7375
7376     if (gameMode == EditGame || gameMode == AnalyzeMode || 
7377         gameMode == AnalyzeFile)
7378         TruncateGame();
7379
7380     ResurrectChessProgram();    /* in case it isn't running */
7381     gameMode = MachinePlaysBlack;
7382     pausing = FALSE;
7383     ModeHighlight();
7384     SetGameInfo();
7385     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7386     DisplayTitle(buf);
7387     if (first.sendName) {
7388       sprintf(buf, "name %s\n", gameInfo.white);
7389       SendToProgram(buf, &first);
7390     }
7391     if (first.sendTime) {
7392       if (first.useColors) {
7393         SendToProgram("white\n", &first); /*gnu kludge*/
7394       }
7395       SendTimeRemaining(&first, FALSE);
7396     }
7397     if (first.useColors) {
7398       SendToProgram("black\ngo\n", &first);
7399     } else {
7400       SendToProgram("go\n", &first);
7401     }
7402     SetMachineThinkingEnables();
7403     first.maybeThinking = TRUE;
7404     StartClocks();
7405
7406     if (appData.autoFlipView && flipView) {
7407       flipView = !flipView;
7408       DrawPosition(FALSE, NULL);
7409     }
7410 }
7411
7412
7413 void
7414 DisplayTwoMachinesTitle()
7415 {
7416     char buf[MSG_SIZ];
7417     if (appData.matchGames > 0) {
7418         if (first.twoMachinesColor[0] == 'w') {
7419             sprintf(buf, "%s vs. %s (%d-%d-%d)",
7420                     gameInfo.white, gameInfo.black,
7421                     first.matchWins, second.matchWins,
7422                     matchGame - 1 - (first.matchWins + second.matchWins));
7423         } else {
7424             sprintf(buf, "%s vs. %s (%d-%d-%d)",
7425                     gameInfo.white, gameInfo.black,
7426                     second.matchWins, first.matchWins,
7427                     matchGame - 1 - (first.matchWins + second.matchWins));
7428         }
7429     } else {
7430         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
7431     }
7432     DisplayTitle(buf);
7433 }
7434
7435 void
7436 TwoMachinesEvent P((void))
7437 {
7438     int i;
7439     char buf[MSG_SIZ];
7440     ChessProgramState *onmove;
7441     
7442     if (appData.noChessProgram) return;
7443
7444     switch (gameMode) {
7445       case TwoMachinesPlay:
7446         return;
7447       case MachinePlaysWhite:
7448       case MachinePlaysBlack:
7449         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
7450             DisplayError("Wait until your turn,\nor select Move Now", 0);
7451             return;
7452         }
7453         /* fall through */
7454       case BeginningOfGame:
7455       case PlayFromGameFile:
7456       case EndOfGame:
7457         EditGameEvent();
7458         if (gameMode != EditGame) return;
7459         break;
7460       case EditPosition:
7461         EditPositionDone();
7462         break;
7463       case AnalyzeMode:
7464       case AnalyzeFile:
7465         ExitAnalyzeMode();
7466         break;
7467       case EditGame:
7468       default:
7469         break;
7470     }
7471
7472     forwardMostMove = currentMove;
7473     ResurrectChessProgram();    /* in case first program isn't running */
7474
7475     if (second.pr == NULL) {
7476         StartChessProgram(&second);
7477         if (second.protocolVersion == 1) {
7478           TwoMachinesEventIfReady();
7479         } else {
7480           /* kludge: allow timeout for initial "feature" command */
7481           FreezeUI();
7482           DisplayMessage("", "Starting second chess program");
7483           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
7484         }
7485         return;
7486     }
7487     DisplayMessage("", "");
7488     InitChessProgram(&second);
7489     SendToProgram("force\n", &second);
7490     if (startedFromSetupPosition) {
7491         SendBoard(&second, backwardMostMove);
7492     }
7493     for (i = backwardMostMove; i < forwardMostMove; i++) {
7494         SendMoveToProgram(i, &second);
7495     }
7496
7497     gameMode = TwoMachinesPlay;
7498     pausing = FALSE;
7499     ModeHighlight();
7500     SetGameInfo();
7501     DisplayTwoMachinesTitle();
7502     firstMove = TRUE;
7503     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
7504         onmove = &first;
7505     } else {
7506         onmove = &second;
7507     }
7508
7509     SendToProgram(first.computerString, &first);
7510     if (first.sendName) {
7511       sprintf(buf, "name %s\n", second.tidy);
7512       SendToProgram(buf, &first);
7513     }
7514     SendToProgram(second.computerString, &second);
7515     if (second.sendName) {
7516       sprintf(buf, "name %s\n", first.tidy);
7517       SendToProgram(buf, &second);
7518     }
7519
7520     if (!first.sendTime || !second.sendTime) {
7521         ResetClocks();
7522         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7523         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7524     }
7525     if (onmove->sendTime) {
7526       if (onmove->useColors) {
7527         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
7528       }
7529       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
7530     }
7531     if (onmove->useColors) {
7532       SendToProgram(onmove->twoMachinesColor, onmove);
7533     }
7534     SendToProgram("go\n", onmove);
7535     onmove->maybeThinking = TRUE;
7536     SetMachineThinkingEnables();
7537
7538     StartClocks();
7539 }
7540
7541 void
7542 TrainingEvent()
7543 {
7544     if (gameMode == Training) {
7545       SetTrainingModeOff();
7546       gameMode = PlayFromGameFile;
7547       DisplayMessage("", "Training mode off");
7548     } else {
7549       gameMode = Training;
7550       animateTraining = appData.animate;
7551
7552       /* make sure we are not already at the end of the game */
7553       if (currentMove < forwardMostMove) {
7554         SetTrainingModeOn();
7555         DisplayMessage("", "Training mode on");
7556       } else {
7557         gameMode = PlayFromGameFile;
7558         DisplayError("Already at end of game", 0);
7559       }
7560     }
7561     ModeHighlight();
7562 }
7563
7564 void
7565 IcsClientEvent()
7566 {
7567     if (!appData.icsActive) return;
7568     switch (gameMode) {
7569       case IcsPlayingWhite:
7570       case IcsPlayingBlack:
7571       case IcsObserving:
7572       case IcsIdle:
7573       case BeginningOfGame:
7574       case IcsExamining:
7575         return;
7576
7577       case EditGame:
7578         break;
7579
7580       case EditPosition:
7581         EditPositionDone();
7582         break;
7583
7584       case AnalyzeMode:
7585       case AnalyzeFile:
7586         ExitAnalyzeMode();
7587         break;
7588         
7589       default:
7590         EditGameEvent();
7591         break;
7592     }
7593
7594     gameMode = IcsIdle;
7595     ModeHighlight();
7596     return;
7597 }
7598
7599
7600 void
7601 EditGameEvent()
7602 {
7603     int i;
7604
7605     switch (gameMode) {
7606       case Training:
7607         SetTrainingModeOff();
7608         break;
7609       case MachinePlaysWhite:
7610       case MachinePlaysBlack:
7611       case BeginningOfGame:
7612         SendToProgram("force\n", &first);
7613         SetUserThinkingEnables();
7614         break;
7615       case PlayFromGameFile:
7616         (void) StopLoadGameTimer();
7617         if (gameFileFP != NULL) {
7618             gameFileFP = NULL;
7619         }
7620         break;
7621       case EditPosition:
7622         EditPositionDone();
7623         break;
7624       case AnalyzeMode:
7625       case AnalyzeFile:
7626         ExitAnalyzeMode();
7627         SendToProgram("force\n", &first);
7628         break;
7629       case TwoMachinesPlay:
7630         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
7631         ResurrectChessProgram();
7632         SetUserThinkingEnables();
7633         break;
7634       case EndOfGame:
7635         ResurrectChessProgram();
7636         break;
7637       case IcsPlayingBlack:
7638       case IcsPlayingWhite:
7639         DisplayError("Warning: You are still playing a game", 0);
7640         break;
7641       case IcsObserving:
7642         DisplayError("Warning: You are still observing a game", 0);
7643         break;
7644       case IcsExamining:
7645         DisplayError("Warning: You are still examining a game", 0);
7646         break;
7647       case IcsIdle:
7648         break;
7649       case EditGame:
7650       default:
7651         return;
7652     }
7653     
7654     pausing = FALSE;
7655     StopClocks();
7656     first.offeredDraw = second.offeredDraw = 0;
7657
7658     if (gameMode == PlayFromGameFile) {
7659         whiteTimeRemaining = timeRemaining[0][currentMove];
7660         blackTimeRemaining = timeRemaining[1][currentMove];
7661         DisplayTitle("");
7662     }
7663
7664     if (gameMode == MachinePlaysWhite ||
7665         gameMode == MachinePlaysBlack ||
7666         gameMode == TwoMachinesPlay ||
7667         gameMode == EndOfGame) {
7668         i = forwardMostMove;
7669         while (i > currentMove) {
7670             SendToProgram("undo\n", &first);
7671             i--;
7672         }
7673         whiteTimeRemaining = timeRemaining[0][currentMove];
7674         blackTimeRemaining = timeRemaining[1][currentMove];
7675         DisplayBothClocks();
7676         if (whiteFlag || blackFlag) {
7677             whiteFlag = blackFlag = 0;
7678         }
7679         DisplayTitle("");
7680     }           
7681     
7682     gameMode = EditGame;
7683     ModeHighlight();
7684     SetGameInfo();
7685 }
7686
7687
7688 void
7689 EditPositionEvent()
7690 {
7691     if (gameMode == EditPosition) {
7692         EditGameEvent();
7693         return;
7694     }
7695     
7696     EditGameEvent();
7697     if (gameMode != EditGame) return;
7698     
7699     gameMode = EditPosition;
7700     ModeHighlight();
7701     SetGameInfo();
7702     if (currentMove > 0)
7703       CopyBoard(boards[0], boards[currentMove]);
7704     
7705     blackPlaysFirst = !WhiteOnMove(currentMove);
7706     ResetClocks();
7707     currentMove = forwardMostMove = backwardMostMove = 0;
7708     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7709     DisplayMove(-1);
7710 }
7711
7712 void
7713 ExitAnalyzeMode()
7714 {
7715     if (first.analysisSupport && first.analyzing) {
7716       SendToProgram("exit\n", &first);
7717       first.analyzing = FALSE;
7718     }
7719     AnalysisPopDown();
7720     thinkOutput[0] = NULLCHAR;
7721 }
7722
7723 void
7724 EditPositionDone()
7725 {
7726     startedFromSetupPosition = TRUE;
7727     InitChessProgram(&first);
7728     SendToProgram("force\n", &first);
7729     if (blackPlaysFirst) {
7730         strcpy(moveList[0], "");
7731         strcpy(parseList[0], "");
7732         currentMove = forwardMostMove = backwardMostMove = 1;
7733         CopyBoard(boards[1], boards[0]);
7734     } else {
7735         currentMove = forwardMostMove = backwardMostMove = 0;
7736     }
7737     SendBoard(&first, forwardMostMove);
7738     DisplayTitle("");
7739     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7740     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7741     gameMode = EditGame;
7742     ModeHighlight();
7743     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
7744 }
7745
7746 /* Pause for `ms' milliseconds */
7747 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7748 void
7749 TimeDelay(ms)
7750      long ms;
7751 {
7752     TimeMark m1, m2;
7753
7754     GetTimeMark(&m1);
7755     do {
7756         GetTimeMark(&m2);
7757     } while (SubtractTimeMarks(&m2, &m1) < ms);
7758 }
7759
7760 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
7761 void
7762 SendMultiLineToICS(buf)
7763      char *buf;
7764 {
7765     char temp[MSG_SIZ+1], *p;
7766     int len;
7767
7768     len = strlen(buf);
7769     if (len > MSG_SIZ)
7770       len = MSG_SIZ;
7771   
7772     strncpy(temp, buf, len);
7773     temp[len] = 0;
7774
7775     p = temp;
7776     while (*p) {
7777         if (*p == '\n' || *p == '\r')
7778           *p = ' ';
7779         ++p;
7780     }
7781
7782     strcat(temp, "\n");
7783     SendToICS(temp);
7784     SendToPlayer(temp, strlen(temp));
7785 }
7786
7787 void
7788 SetWhiteToPlayEvent()
7789 {
7790     if (gameMode == EditPosition) {
7791         blackPlaysFirst = FALSE;
7792         DisplayBothClocks();    /* works because currentMove is 0 */
7793     } else if (gameMode == IcsExamining) {
7794         SendToICS(ics_prefix);
7795         SendToICS("tomove white\n");
7796     }
7797 }
7798
7799 void
7800 SetBlackToPlayEvent()
7801 {
7802     if (gameMode == EditPosition) {
7803         blackPlaysFirst = TRUE;
7804         currentMove = 1;        /* kludge */
7805         DisplayBothClocks();
7806         currentMove = 0;
7807     } else if (gameMode == IcsExamining) {
7808         SendToICS(ics_prefix);
7809         SendToICS("tomove black\n");
7810     }
7811 }
7812
7813 void
7814 EditPositionMenuEvent(selection, x, y)
7815      ChessSquare selection;
7816      int x, y;
7817 {
7818     char buf[MSG_SIZ];
7819
7820     if (gameMode != EditPosition && gameMode != IcsExamining) return;
7821
7822     switch (selection) {
7823       case ClearBoard:
7824         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
7825             SendToICS(ics_prefix);
7826             SendToICS("bsetup clear\n");
7827         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
7828             SendToICS(ics_prefix);
7829             SendToICS("clearboard\n");
7830         } else {
7831             for (x = 0; x < BOARD_SIZE; x++) {
7832                 for (y = 0; y < BOARD_SIZE; y++) {
7833                     if (gameMode == IcsExamining) {
7834                         if (boards[currentMove][y][x] != EmptySquare) {
7835                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
7836                                     'a' + x, '1' + y);
7837                             SendToICS(buf);
7838                         }
7839                     } else {
7840                         boards[0][y][x] = EmptySquare;
7841                     }
7842                 }
7843             }
7844         }
7845         if (gameMode == EditPosition) {
7846             DrawPosition(FALSE, boards[0]);
7847         }
7848         break;
7849
7850       case WhitePlay:
7851         SetWhiteToPlayEvent();
7852         break;
7853
7854       case BlackPlay:
7855         SetBlackToPlayEvent();
7856         break;
7857
7858       case EmptySquare:
7859         if (gameMode == IcsExamining) {
7860             sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y);
7861             SendToICS(buf);
7862         } else {
7863             boards[0][y][x] = EmptySquare;
7864             DrawPosition(FALSE, boards[0]);
7865         }
7866         break;
7867
7868       default:
7869         if (gameMode == IcsExamining) {
7870             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
7871                     PieceToChar(selection), 'a' + x, '1' + y);
7872             SendToICS(buf);
7873         } else {
7874             boards[0][y][x] = selection;
7875             DrawPosition(FALSE, boards[0]);
7876         }
7877         break;
7878     }
7879 }
7880
7881
7882 void
7883 DropMenuEvent(selection, x, y)
7884      ChessSquare selection;
7885      int x, y;
7886 {
7887     ChessMove moveType;
7888
7889     switch (gameMode) {
7890       case IcsPlayingWhite:
7891       case MachinePlaysBlack:
7892         if (!WhiteOnMove(currentMove)) {
7893             DisplayMoveError("It is Black's turn");
7894             return;
7895         }
7896         moveType = WhiteDrop;
7897         break;
7898       case IcsPlayingBlack:
7899       case MachinePlaysWhite:
7900         if (WhiteOnMove(currentMove)) {
7901             DisplayMoveError("It is White's turn");
7902             return;
7903         }
7904         moveType = BlackDrop;
7905         break;
7906       case EditGame:
7907         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7908         break;
7909       default:
7910         return;
7911     }
7912
7913     if (moveType == BlackDrop && selection < BlackPawn) {
7914       selection = (ChessSquare) ((int) selection
7915                                  + (int) BlackPawn - (int) WhitePawn);
7916     }
7917     if (boards[currentMove][y][x] != EmptySquare) {
7918         DisplayMoveError("That square is occupied");
7919         return;
7920     }
7921
7922     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
7923 }
7924
7925 void
7926 AcceptEvent()
7927 {
7928     /* Accept a pending offer of any kind from opponent */
7929     
7930     if (appData.icsActive) {
7931         SendToICS(ics_prefix);
7932         SendToICS("accept\n");
7933     } else if (cmailMsgLoaded) {
7934         if (currentMove == cmailOldMove &&
7935             commentList[cmailOldMove] != NULL &&
7936             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7937                    "Black offers a draw" : "White offers a draw")) {
7938             TruncateGame();
7939             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
7940             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
7941         } else {
7942             DisplayError("There is no pending offer on this move", 0);
7943             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7944         }
7945     } else {
7946         /* Not used for offers from chess program */
7947     }
7948 }
7949
7950 void
7951 DeclineEvent()
7952 {
7953     /* Decline a pending offer of any kind from opponent */
7954     
7955     if (appData.icsActive) {
7956         SendToICS(ics_prefix);
7957         SendToICS("decline\n");
7958     } else if (cmailMsgLoaded) {
7959         if (currentMove == cmailOldMove &&
7960             commentList[cmailOldMove] != NULL &&
7961             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
7962                    "Black offers a draw" : "White offers a draw")) {
7963 #ifdef NOTDEF
7964             AppendComment(cmailOldMove, "Draw declined");
7965             DisplayComment(cmailOldMove - 1, "Draw declined");
7966 #endif /*NOTDEF*/
7967         } else {
7968             DisplayError("There is no pending offer on this move", 0);
7969         }
7970     } else {
7971         /* Not used for offers from chess program */
7972     }
7973 }
7974
7975 void
7976 RematchEvent()
7977 {
7978     /* Issue ICS rematch command */
7979     if (appData.icsActive) {
7980         SendToICS(ics_prefix);
7981         SendToICS("rematch\n");
7982     }
7983 }
7984
7985 void
7986 CallFlagEvent()
7987 {
7988     /* Call your opponent's flag (claim a win on time) */
7989     if (appData.icsActive) {
7990         SendToICS(ics_prefix);
7991         SendToICS("flag\n");
7992     } else {
7993         switch (gameMode) {
7994           default:
7995             return;
7996           case MachinePlaysWhite:
7997             if (whiteFlag) {
7998                 if (blackFlag)
7999                   GameEnds(GameIsDrawn, "Both players ran out of time",
8000                            GE_PLAYER);
8001                 else
8002                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
8003             } else {
8004                 DisplayError("Your opponent is not out of time", 0);
8005             }
8006             break;
8007           case MachinePlaysBlack:
8008             if (blackFlag) {
8009                 if (whiteFlag)
8010                   GameEnds(GameIsDrawn, "Both players ran out of time",
8011                            GE_PLAYER);
8012                 else
8013                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
8014             } else {
8015                 DisplayError("Your opponent is not out of time", 0);
8016             }
8017             break;
8018         }
8019     }
8020 }
8021
8022 void
8023 DrawEvent()
8024 {
8025     /* Offer draw or accept pending draw offer from opponent */
8026     
8027     if (appData.icsActive) {
8028         /* Note: tournament rules require draw offers to be
8029            made after you make your move but before you punch
8030            your clock.  Currently ICS doesn't let you do that;
8031            instead, you immediately punch your clock after making
8032            a move, but you can offer a draw at any time. */
8033         
8034         SendToICS(ics_prefix);
8035         SendToICS("draw\n");
8036     } else if (cmailMsgLoaded) {
8037         if (currentMove == cmailOldMove &&
8038             commentList[cmailOldMove] != NULL &&
8039             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
8040                    "Black offers a draw" : "White offers a draw")) {
8041             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8042             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
8043         } else if (currentMove == cmailOldMove + 1) {
8044             char *offer = WhiteOnMove(cmailOldMove) ?
8045               "White offers a draw" : "Black offers a draw";
8046             AppendComment(currentMove, offer);
8047             DisplayComment(currentMove - 1, offer);
8048             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
8049         } else {
8050             DisplayError("You must make your move before offering a draw", 0);
8051             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8052         }
8053     } else if (first.offeredDraw) {
8054         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8055     } else {
8056         if (first.sendDrawOffers) {
8057             SendToProgram("draw\n", &first);
8058             userOfferedDraw = TRUE;
8059         }
8060     }
8061 }
8062
8063 void
8064 AdjournEvent()
8065 {
8066     /* Offer Adjourn or accept pending Adjourn offer from opponent */
8067     
8068     if (appData.icsActive) {
8069         SendToICS(ics_prefix);
8070         SendToICS("adjourn\n");
8071     } else {
8072         /* Currently GNU Chess doesn't offer or accept Adjourns */
8073     }
8074 }
8075
8076
8077 void
8078 AbortEvent()
8079 {
8080     /* Offer Abort or accept pending Abort offer from opponent */
8081     
8082     if (appData.icsActive) {
8083         SendToICS(ics_prefix);
8084         SendToICS("abort\n");
8085     } else {
8086         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
8087     }
8088 }
8089
8090 void
8091 ResignEvent()
8092 {
8093     /* Resign.  You can do this even if it's not your turn. */
8094     
8095     if (appData.icsActive) {
8096         SendToICS(ics_prefix);
8097         SendToICS("resign\n");
8098     } else {
8099         switch (gameMode) {
8100           case MachinePlaysWhite:
8101             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8102             break;
8103           case MachinePlaysBlack:
8104             GameEnds(BlackWins, "White resigns", GE_PLAYER);
8105             break;
8106           case EditGame:
8107             if (cmailMsgLoaded) {
8108                 TruncateGame();
8109                 if (WhiteOnMove(cmailOldMove)) {
8110                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
8111                 } else {
8112                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8113                 }
8114                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
8115             }
8116             break;
8117           default:
8118             break;
8119         }
8120     }
8121 }
8122
8123
8124 void
8125 StopObservingEvent()
8126 {
8127     /* Stop observing current games */
8128     SendToICS(ics_prefix);
8129     SendToICS("unobserve\n");
8130 }
8131
8132 void
8133 StopExaminingEvent()
8134 {
8135     /* Stop observing current game */
8136     SendToICS(ics_prefix);
8137     SendToICS("unexamine\n");
8138 }
8139
8140 void
8141 ForwardInner(target)
8142      int target;
8143 {
8144     int limit;
8145
8146     if (appData.debugMode)
8147         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
8148                 target, currentMove, forwardMostMove);
8149
8150     if (gameMode == EditPosition)
8151       return;
8152
8153     if (gameMode == PlayFromGameFile && !pausing)
8154       PauseEvent();
8155     
8156     if (gameMode == IcsExamining && pausing)
8157       limit = pauseExamForwardMostMove;
8158     else
8159       limit = forwardMostMove;
8160     
8161     if (target > limit) target = limit;
8162
8163     if (target > 0 && moveList[target - 1][0]) {
8164         int fromX, fromY, toX, toY;
8165         toX = moveList[target - 1][2] - 'a';
8166         toY = moveList[target - 1][3] - '1';
8167         if (moveList[target - 1][1] == '@') {
8168             if (appData.highlightLastMove) {
8169                 SetHighlights(-1, -1, toX, toY);
8170             }
8171         } else {
8172             fromX = moveList[target - 1][0] - 'a';
8173             fromY = moveList[target - 1][1] - '1';
8174             if (target == currentMove + 1) {
8175                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8176             }
8177             if (appData.highlightLastMove) {
8178                 SetHighlights(fromX, fromY, toX, toY);
8179             }
8180         }
8181     }
8182     if (gameMode == EditGame || gameMode == AnalyzeMode || 
8183         gameMode == Training || gameMode == PlayFromGameFile || 
8184         gameMode == AnalyzeFile) {
8185         while (currentMove < target) {
8186             SendMoveToProgram(currentMove++, &first);
8187         }
8188     } else {
8189         currentMove = target;
8190     }
8191     
8192     if (gameMode == EditGame || gameMode == EndOfGame) {
8193         whiteTimeRemaining = timeRemaining[0][currentMove];
8194         blackTimeRemaining = timeRemaining[1][currentMove];
8195     }
8196     DisplayBothClocks();
8197     DisplayMove(currentMove - 1);
8198     DrawPosition(FALSE, boards[currentMove]);
8199     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8200     if (commentList[currentMove] && !matchMode && gameMode != Training) {
8201         DisplayComment(currentMove - 1, commentList[currentMove]);
8202     }
8203 }
8204
8205
8206 void
8207 ForwardEvent()
8208 {
8209     if (gameMode == IcsExamining && !pausing) {
8210         SendToICS(ics_prefix);
8211         SendToICS("forward\n");
8212     } else {
8213         ForwardInner(currentMove + 1);
8214     }
8215 }
8216
8217 void
8218 ToEndEvent()
8219 {
8220     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8221         /* to optimze, we temporarily turn off analysis mode while we feed
8222          * the remaining moves to the engine. Otherwise we get analysis output
8223          * after each move.
8224          */ 
8225         if (first.analysisSupport) {
8226           SendToProgram("exit\nforce\n", &first);
8227           first.analyzing = FALSE;
8228         }
8229     }
8230         
8231     if (gameMode == IcsExamining && !pausing) {
8232         SendToICS(ics_prefix);
8233         SendToICS("forward 999999\n");
8234     } else {
8235         ForwardInner(forwardMostMove);
8236     }
8237
8238     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8239         /* we have fed all the moves, so reactivate analysis mode */
8240         SendToProgram("analyze\n", &first);
8241         first.analyzing = TRUE;
8242         /*first.maybeThinking = TRUE;*/
8243         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8244     }
8245 }
8246
8247 void
8248 BackwardInner(target)
8249      int target;
8250 {
8251     if (appData.debugMode)
8252         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
8253                 target, currentMove, forwardMostMove);
8254
8255     if (gameMode == EditPosition) return;
8256     if (currentMove <= backwardMostMove) {
8257         ClearHighlights();
8258         DrawPosition(FALSE, boards[currentMove]);
8259         return;
8260     }
8261     if (gameMode == PlayFromGameFile && !pausing)
8262       PauseEvent();
8263     
8264     if (moveList[target][0]) {
8265         int fromX, fromY, toX, toY;
8266         toX = moveList[target][2] - 'a';
8267         toY = moveList[target][3] - '1';
8268         if (moveList[target][1] == '@') {
8269             if (appData.highlightLastMove) {
8270                 SetHighlights(-1, -1, toX, toY);
8271             }
8272         } else {
8273             fromX = moveList[target][0] - 'a';
8274             fromY = moveList[target][1] - '1';
8275             if (target == currentMove - 1) {
8276                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
8277             }
8278             if (appData.highlightLastMove) {
8279                 SetHighlights(fromX, fromY, toX, toY);
8280             }
8281         }
8282     }
8283     if (gameMode == EditGame || gameMode==AnalyzeMode ||
8284         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8285         while (currentMove > target) {
8286             SendToProgram("undo\n", &first);
8287             currentMove--;
8288         }
8289     } else {
8290         currentMove = target;
8291     }
8292     
8293     if (gameMode == EditGame || gameMode == EndOfGame) {
8294         whiteTimeRemaining = timeRemaining[0][currentMove];
8295         blackTimeRemaining = timeRemaining[1][currentMove];
8296     }
8297     DisplayBothClocks();
8298     DisplayMove(currentMove - 1);
8299     DrawPosition(FALSE, boards[currentMove]);
8300     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8301     if (commentList[currentMove] != NULL) {
8302         DisplayComment(currentMove - 1, commentList[currentMove]);
8303     }
8304 }
8305
8306 void
8307 BackwardEvent()
8308 {
8309     if (gameMode == IcsExamining && !pausing) {
8310         SendToICS(ics_prefix);
8311         SendToICS("backward\n");
8312     } else {
8313         BackwardInner(currentMove - 1);
8314     }
8315 }
8316
8317 void
8318 ToStartEvent()
8319 {
8320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8321         /* to optimze, we temporarily turn off analysis mode while we undo
8322          * all the moves. Otherwise we get analysis output after each undo.
8323          */ 
8324         if (first.analysisSupport) {
8325           SendToProgram("exit\nforce\n", &first);
8326           first.analyzing = FALSE;
8327         }
8328     }
8329
8330     if (gameMode == IcsExamining && !pausing) {
8331         SendToICS(ics_prefix);
8332         SendToICS("backward 999999\n");
8333     } else {
8334         BackwardInner(backwardMostMove);
8335     }
8336
8337     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
8338         /* we have fed all the moves, so reactivate analysis mode */
8339         SendToProgram("analyze\n", &first);
8340         first.analyzing = TRUE;
8341         /*first.maybeThinking = TRUE;*/
8342         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
8343     }
8344 }
8345
8346 void
8347 ToNrEvent(int to)
8348 {
8349   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
8350   if (to >= forwardMostMove) to = forwardMostMove;
8351   if (to <= backwardMostMove) to = backwardMostMove;
8352   if (to < currentMove) {
8353     BackwardInner(to);
8354   } else {
8355     ForwardInner(to);
8356   }
8357 }
8358
8359 void
8360 RevertEvent()
8361 {
8362     if (gameMode != IcsExamining) {
8363         DisplayError("You are not examining a game", 0);
8364         return;
8365     }
8366     if (pausing) {
8367         DisplayError("You can't revert while pausing", 0);
8368         return;
8369     }
8370     SendToICS(ics_prefix);
8371     SendToICS("revert\n");
8372 }
8373
8374 void
8375 RetractMoveEvent()
8376 {
8377     switch (gameMode) {
8378       case MachinePlaysWhite:
8379       case MachinePlaysBlack:
8380         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
8381             DisplayError("Wait until your turn,\nor select Move Now", 0);
8382             return;
8383         }
8384         if (forwardMostMove < 2) return;
8385         currentMove = forwardMostMove = forwardMostMove - 2;
8386         whiteTimeRemaining = timeRemaining[0][currentMove];
8387         blackTimeRemaining = timeRemaining[1][currentMove];
8388         DisplayBothClocks();
8389         DisplayMove(currentMove - 1);
8390         ClearHighlights();/*!! could figure this out*/
8391         DrawPosition(FALSE, boards[currentMove]);
8392         SendToProgram("remove\n", &first);
8393         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
8394         break;
8395
8396       case BeginningOfGame:
8397       default:
8398         break;
8399
8400       case IcsPlayingWhite:
8401       case IcsPlayingBlack:
8402         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
8403             SendToICS(ics_prefix);
8404             SendToICS("takeback 2\n");
8405         } else {
8406             SendToICS(ics_prefix);
8407             SendToICS("takeback 1\n");
8408         }
8409         break;
8410     }
8411 }
8412
8413 void
8414 MoveNowEvent()
8415 {
8416     ChessProgramState *cps;
8417
8418     switch (gameMode) {
8419       case MachinePlaysWhite:
8420         if (!WhiteOnMove(forwardMostMove)) {
8421             DisplayError("It is your turn", 0);
8422             return;
8423         }
8424         cps = &first;
8425         break;
8426       case MachinePlaysBlack:
8427         if (WhiteOnMove(forwardMostMove)) {
8428             DisplayError("It is your turn", 0);
8429             return;
8430         }
8431         cps = &first;
8432         break;
8433       case TwoMachinesPlay:
8434         if (WhiteOnMove(forwardMostMove) ==
8435             (first.twoMachinesColor[0] == 'w')) {
8436             cps = &first;
8437         } else {
8438             cps = &second;
8439         }
8440         break;
8441       case BeginningOfGame:
8442       default:
8443         return;
8444     }
8445     SendToProgram("?\n", cps);
8446 }
8447
8448 void
8449 TruncateGameEvent()
8450 {
8451     EditGameEvent();
8452     if (gameMode != EditGame) return;
8453     TruncateGame();
8454 }
8455
8456 void
8457 TruncateGame()
8458 {
8459     if (forwardMostMove > currentMove) {
8460         if (gameInfo.resultDetails != NULL) {
8461             free(gameInfo.resultDetails);
8462             gameInfo.resultDetails = NULL;
8463             gameInfo.result = GameUnfinished;
8464         }
8465         forwardMostMove = currentMove;
8466         HistorySet(parseList, backwardMostMove, forwardMostMove,
8467                    currentMove-1);
8468     }
8469 }
8470
8471 void
8472 HintEvent()
8473 {
8474     if (appData.noChessProgram) return;
8475     switch (gameMode) {
8476       case MachinePlaysWhite:
8477         if (WhiteOnMove(forwardMostMove)) {
8478             DisplayError("Wait until your turn", 0);
8479             return;
8480         }
8481         break;
8482       case BeginningOfGame:
8483       case MachinePlaysBlack:
8484         if (!WhiteOnMove(forwardMostMove)) {
8485             DisplayError("Wait until your turn", 0);
8486             return;
8487         }
8488         break;
8489       default:
8490         DisplayError("No hint available", 0);
8491         return;
8492     }
8493     SendToProgram("hint\n", &first);
8494     hintRequested = TRUE;
8495 }
8496
8497 void
8498 BookEvent()
8499 {
8500     if (appData.noChessProgram) return;
8501     switch (gameMode) {
8502       case MachinePlaysWhite:
8503         if (WhiteOnMove(forwardMostMove)) {
8504             DisplayError("Wait until your turn", 0);
8505             return;
8506         }
8507         break;
8508       case BeginningOfGame:
8509       case MachinePlaysBlack:
8510         if (!WhiteOnMove(forwardMostMove)) {
8511             DisplayError("Wait until your turn", 0);
8512             return;
8513         }
8514         break;
8515       case EditPosition:
8516         EditPositionDone();
8517         break;
8518       case TwoMachinesPlay:
8519         return;
8520       default:
8521         break;
8522     }
8523     SendToProgram("bk\n", &first);
8524     bookOutput[0] = NULLCHAR;
8525     bookRequested = TRUE;
8526 }
8527
8528 void
8529 AboutGameEvent()
8530 {
8531     char *tags = PGNTags(&gameInfo);
8532     TagsPopUp(tags, CmailMsg());
8533     free(tags);
8534 }
8535
8536 /* end button procedures */
8537
8538 void
8539 PrintPosition(fp, move)
8540      FILE *fp;
8541      int move;
8542 {
8543     int i, j;
8544     
8545     for (i = BOARD_SIZE - 1; i >= 0; i--) {
8546         for (j = 0; j < BOARD_SIZE; j++) {
8547             char c = PieceToChar(boards[move][i][j]);
8548             fputc(c == 'x' ? '.' : c, fp);
8549             fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp);
8550         }
8551     }
8552     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
8553       fprintf(fp, "white to play\n");
8554     else
8555       fprintf(fp, "black to play\n");
8556 }
8557
8558 void
8559 PrintOpponents(fp)
8560      FILE *fp;
8561 {
8562     if (gameInfo.white != NULL) {
8563         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
8564     } else {
8565         fprintf(fp, "\n");
8566     }
8567 }
8568
8569 /* Find last component of program's own name, using some heuristics */
8570 void
8571 TidyProgramName(prog, host, buf)
8572      char *prog, *host, buf[MSG_SIZ];
8573 {
8574     char *p, *q;
8575     int local = (strcmp(host, "localhost") == 0);
8576     while (!local && (p = strchr(prog, ';')) != NULL) {
8577         p++;
8578         while (*p == ' ') p++;
8579         prog = p;
8580     }
8581     if (*prog == '"' || *prog == '\'') {
8582         q = strchr(prog + 1, *prog);
8583     } else {
8584         q = strchr(prog, ' ');
8585     }
8586     if (q == NULL) q = prog + strlen(prog);
8587     p = q;
8588     while (p >= prog && *p != '/' && *p != '\\') p--;
8589     p++;
8590     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
8591     memcpy(buf, p, q - p);
8592     buf[q - p] = NULLCHAR;
8593     if (!local) {
8594         strcat(buf, "@");
8595         strcat(buf, host);
8596     }
8597 }
8598
8599 char *
8600 TimeControlTagValue()
8601 {
8602     char buf[MSG_SIZ];
8603     if (!appData.clockMode) {
8604         strcpy(buf, "-");
8605     } else if (movesPerSession > 0) {
8606         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
8607     } else if (timeIncrement == 0) {
8608         sprintf(buf, "%ld", timeControl/1000);
8609     } else {
8610         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
8611     }
8612     return StrSave(buf);
8613 }
8614
8615 void
8616 SetGameInfo()
8617 {
8618     /* This routine is used only for certain modes */
8619     VariantClass v = gameInfo.variant;
8620     ClearGameInfo(&gameInfo);
8621     gameInfo.variant = v;
8622
8623     switch (gameMode) {
8624       case MachinePlaysWhite:
8625         gameInfo.event = StrSave("Computer chess game");
8626         gameInfo.site = StrSave(HostName());
8627         gameInfo.date = PGNDate();
8628         gameInfo.round = StrSave("-");
8629         gameInfo.white = StrSave(first.tidy);
8630         gameInfo.black = StrSave(UserName());
8631         gameInfo.timeControl = TimeControlTagValue();
8632         break;
8633
8634       case MachinePlaysBlack:
8635         gameInfo.event = StrSave("Computer chess game");
8636         gameInfo.site = StrSave(HostName());
8637         gameInfo.date = PGNDate();
8638         gameInfo.round = StrSave("-");
8639         gameInfo.white = StrSave(UserName());
8640         gameInfo.black = StrSave(first.tidy);
8641         gameInfo.timeControl = TimeControlTagValue();
8642         break;
8643
8644       case TwoMachinesPlay:
8645         gameInfo.event = StrSave("Computer chess game");
8646         gameInfo.site = StrSave(HostName());
8647         gameInfo.date = PGNDate();
8648         if (matchGame > 0) {
8649             char buf[MSG_SIZ];
8650             sprintf(buf, "%d", matchGame);
8651             gameInfo.round = StrSave(buf);
8652         } else {
8653             gameInfo.round = StrSave("-");
8654         }
8655         if (first.twoMachinesColor[0] == 'w') {
8656             gameInfo.white = StrSave(first.tidy);
8657             gameInfo.black = StrSave(second.tidy);
8658         } else {
8659             gameInfo.white = StrSave(second.tidy);
8660             gameInfo.black = StrSave(first.tidy);
8661         }
8662         gameInfo.timeControl = TimeControlTagValue();
8663         break;
8664
8665       case EditGame:
8666         gameInfo.event = StrSave("Edited game");
8667         gameInfo.site = StrSave(HostName());
8668         gameInfo.date = PGNDate();
8669         gameInfo.round = StrSave("-");
8670         gameInfo.white = StrSave("-");
8671         gameInfo.black = StrSave("-");
8672         break;
8673
8674       case EditPosition:
8675         gameInfo.event = StrSave("Edited position");
8676         gameInfo.site = StrSave(HostName());
8677         gameInfo.date = PGNDate();
8678         gameInfo.round = StrSave("-");
8679         gameInfo.white = StrSave("-");
8680         gameInfo.black = StrSave("-");
8681         break;
8682
8683       case IcsPlayingWhite:
8684       case IcsPlayingBlack:
8685       case IcsObserving:
8686       case IcsExamining:
8687         break;
8688
8689       case PlayFromGameFile:
8690         gameInfo.event = StrSave("Game from non-PGN file");
8691         gameInfo.site = StrSave(HostName());
8692         gameInfo.date = PGNDate();
8693         gameInfo.round = StrSave("-");
8694         gameInfo.white = StrSave("?");
8695         gameInfo.black = StrSave("?");
8696         break;
8697
8698       default:
8699         break;
8700     }
8701 }
8702
8703 void
8704 ReplaceComment(index, text)
8705      int index;
8706      char *text;
8707 {
8708     int len;
8709
8710     while (*text == '\n') text++;
8711     len = strlen(text);
8712     while (len > 0 && text[len - 1] == '\n') len--;
8713
8714     if (commentList[index] != NULL)
8715       free(commentList[index]);
8716
8717     if (len == 0) {
8718         commentList[index] = NULL;
8719         return;
8720     }
8721     commentList[index] = (char *) malloc(len + 2);
8722     strncpy(commentList[index], text, len);
8723     commentList[index][len] = '\n';
8724     commentList[index][len + 1] = NULLCHAR;
8725 }
8726
8727 void
8728 CrushCRs(text)
8729      char *text;
8730 {
8731   char *p = text;
8732   char *q = text;
8733   char ch;
8734
8735   do {
8736     ch = *p++;
8737     if (ch == '\r') continue;
8738     *q++ = ch;
8739   } while (ch != '\0');
8740 }
8741
8742 void
8743 AppendComment(index, text)
8744      int index;
8745      char *text;
8746 {
8747     int oldlen, len;
8748     char *old;
8749
8750     CrushCRs(text);
8751     while (*text == '\n') text++;
8752     len = strlen(text);
8753     while (len > 0 && text[len - 1] == '\n') len--;
8754
8755     if (len == 0) return;
8756
8757     if (commentList[index] != NULL) {
8758         old = commentList[index];
8759         oldlen = strlen(old);
8760         commentList[index] = (char *) malloc(oldlen + len + 2);
8761         strcpy(commentList[index], old);
8762         free(old);
8763         strncpy(&commentList[index][oldlen], text, len);
8764         commentList[index][oldlen + len] = '\n';
8765         commentList[index][oldlen + len + 1] = NULLCHAR;
8766     } else {
8767         commentList[index] = (char *) malloc(len + 2);
8768         strncpy(commentList[index], text, len);
8769         commentList[index][len] = '\n';
8770         commentList[index][len + 1] = NULLCHAR;
8771     }
8772 }
8773
8774 void
8775 SendToProgram(message, cps)
8776      char *message;
8777      ChessProgramState *cps;
8778 {
8779     int count, outCount, error;
8780     char buf[MSG_SIZ];
8781
8782     if (cps->pr == NULL) return;
8783     Attention(cps);
8784     
8785     if (appData.debugMode) {
8786         TimeMark now;
8787         GetTimeMark(&now);
8788         fprintf(debugFP, "%ld >%-6s: %s", 
8789                 SubtractTimeMarks(&now, &programStartTime),
8790                 cps->which, message);
8791     }
8792     
8793     count = strlen(message);
8794     outCount = OutputToProcess(cps->pr, message, count, &error);
8795     if (outCount < count && !exiting) {
8796         sprintf(buf, "Error writing to %s chess program", cps->which);
8797         DisplayFatalError(buf, error, 1);
8798     }
8799 }
8800
8801 void
8802 ReceiveFromProgram(isr, closure, message, count, error)
8803      InputSourceRef isr;
8804      VOIDSTAR closure;
8805      char *message;
8806      int count;
8807      int error;
8808 {
8809     char *end_str;
8810     char buf[MSG_SIZ];
8811     ChessProgramState *cps = (ChessProgramState *)closure;
8812
8813     if (isr != cps->isr) return; /* Killed intentionally */
8814     if (count <= 0) {
8815         if (count == 0) {
8816             sprintf(buf,
8817                     "Error: %s chess program (%s) exited unexpectedly",
8818                     cps->which, cps->program);
8819             RemoveInputSource(cps->isr);
8820             DisplayFatalError(buf, 0, 1);
8821         } else {
8822             sprintf(buf,
8823                     "Error reading from %s chess program (%s)",
8824                     cps->which, cps->program);
8825             RemoveInputSource(cps->isr);
8826             DisplayFatalError(buf, error, 1);
8827         }
8828         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8829         return;
8830     }
8831     
8832     if ((end_str = strchr(message, '\r')) != NULL)
8833       *end_str = NULLCHAR;
8834     if ((end_str = strchr(message, '\n')) != NULL)
8835       *end_str = NULLCHAR;
8836     
8837     if (appData.debugMode) {
8838         TimeMark now;
8839         GetTimeMark(&now);
8840         fprintf(debugFP, "%ld <%-6s: %s\n", 
8841                 SubtractTimeMarks(&now, &programStartTime),
8842                 cps->which, message);
8843     }
8844     HandleMachineMove(message, cps);
8845 }
8846
8847
8848 void
8849 SendTimeControl(cps, mps, tc, inc, sd, st)
8850      ChessProgramState *cps;
8851      int mps, inc, sd, st;
8852      long tc;
8853 {
8854     char buf[MSG_SIZ];
8855     int seconds = (tc / 1000) % 60;
8856
8857     if (st > 0) {
8858       /* Set exact time per move, normally using st command */
8859       if (cps->stKludge) {
8860         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
8861         seconds = st % 60;
8862         if (seconds == 0) {
8863           sprintf(buf, "level 1 %d\n", st/60);
8864         } else {
8865           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
8866         }
8867       } else {
8868         sprintf(buf, "st %d\n", st);
8869       }
8870     } else {
8871       /* Set conventional or incremental time control, using level command */
8872       if (seconds == 0) {
8873         /* Note old gnuchess bug -- minutes:seconds used to not work.
8874            Fixed in later versions, but still avoid :seconds
8875            when seconds is 0. */
8876         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
8877       } else {
8878         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
8879                 seconds, inc/1000);
8880       }
8881     }
8882     SendToProgram(buf, cps);
8883
8884     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
8885     /* Orthogonally, limit search to given depth */
8886     if (sd > 0) {
8887       if (cps->sdKludge) {
8888         sprintf(buf, "depth\n%d\n", sd);
8889       } else {
8890         sprintf(buf, "sd %d\n", sd);
8891       }
8892       SendToProgram(buf, cps);
8893     }
8894 }
8895
8896 void
8897 SendTimeRemaining(cps, machineWhite)
8898      ChessProgramState *cps;
8899      int /*boolean*/ machineWhite;
8900 {
8901     char message[MSG_SIZ];
8902     long time, otime;
8903
8904     /* Note: this routine must be called when the clocks are stopped
8905        or when they have *just* been set or switched; otherwise
8906        it will be off by the time since the current tick started.
8907     */
8908     if (machineWhite) {
8909         time = whiteTimeRemaining / 10;
8910         otime = blackTimeRemaining / 10;
8911     } else {
8912         time = blackTimeRemaining / 10;
8913         otime = whiteTimeRemaining / 10;
8914     }
8915     if (time <= 0) time = 1;
8916     if (otime <= 0) otime = 1;
8917     
8918     sprintf(message, "time %ld\notim %ld\n", time, otime);
8919     SendToProgram(message, cps);
8920 }
8921
8922 int
8923 BoolFeature(p, name, loc, cps)
8924      char **p;
8925      char *name;
8926      int *loc;
8927      ChessProgramState *cps;
8928 {
8929   char buf[MSG_SIZ];
8930   int len = strlen(name);
8931   int val;
8932   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8933     (*p) += len + 1;
8934     sscanf(*p, "%d", &val);
8935     *loc = (val != 0);
8936     while (**p && **p != ' ') (*p)++;
8937     sprintf(buf, "accepted %s\n", name);
8938     SendToProgram(buf, cps);
8939     return TRUE;
8940   }
8941   return FALSE;
8942 }
8943
8944 int
8945 IntFeature(p, name, loc, cps)
8946      char **p;
8947      char *name;
8948      int *loc;
8949      ChessProgramState *cps;
8950 {
8951   char buf[MSG_SIZ];
8952   int len = strlen(name);
8953   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
8954     (*p) += len + 1;
8955     sscanf(*p, "%d", loc);
8956     while (**p && **p != ' ') (*p)++;
8957     sprintf(buf, "accepted %s\n", name);
8958     SendToProgram(buf, cps);
8959     return TRUE;
8960   }
8961   return FALSE;
8962 }
8963
8964 int
8965 StringFeature(p, name, loc, cps)
8966      char **p;
8967      char *name;
8968      char loc[];
8969      ChessProgramState *cps;
8970 {
8971   char buf[MSG_SIZ];
8972   int len = strlen(name);
8973   if (strncmp((*p), name, len) == 0
8974       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
8975     (*p) += len + 2;
8976     sscanf(*p, "%[^\"]", loc);
8977     while (**p && **p != '\"') (*p)++;
8978     if (**p == '\"') (*p)++;
8979     sprintf(buf, "accepted %s\n", name);
8980     SendToProgram(buf, cps);
8981     return TRUE;
8982   }
8983   return FALSE;
8984 }
8985
8986 void
8987 FeatureDone(cps, val)
8988      ChessProgramState* cps;
8989      int val;
8990 {
8991   DelayedEventCallback cb = GetDelayedEvent();
8992   if ((cb == InitBackEnd3 && cps == &first) ||
8993       (cb == TwoMachinesEventIfReady && cps == &second)) {
8994     CancelDelayedEvent();
8995     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
8996   }
8997   cps->initDone = val;
8998 }
8999
9000 /* Parse feature command from engine */
9001 void
9002 ParseFeatures(args, cps)
9003      char* args;
9004      ChessProgramState *cps;  
9005 {
9006   char *p = args;
9007   char *q;
9008   int val;
9009   char buf[MSG_SIZ];
9010
9011   for (;;) {
9012     while (*p == ' ') p++;
9013     if (*p == NULLCHAR) return;
9014
9015     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
9016     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
9017     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
9018     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
9019     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
9020     if (BoolFeature(&p, "reuse", &val, cps)) {
9021       /* Engine can disable reuse, but can't enable it if user said no */
9022       if (!val) cps->reuse = FALSE;
9023       continue;
9024     }
9025     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
9026     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
9027       if (gameMode == TwoMachinesPlay) {
9028         DisplayTwoMachinesTitle();
9029       } else {
9030         DisplayTitle("");
9031       }
9032       continue;
9033     }
9034     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
9035     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
9036     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
9037     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
9038     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
9039     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
9040     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
9041     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
9042     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
9043     if (IntFeature(&p, "done", &val, cps)) {
9044       FeatureDone(cps, val);
9045       continue;
9046     }
9047
9048     /* unknown feature: complain and skip */
9049     q = p;
9050     while (*q && *q != '=') q++;
9051     sprintf(buf, "rejected %.*s\n", q-p, p);
9052     SendToProgram(buf, cps);
9053     p = q;
9054     if (*p == '=') {
9055       p++;
9056       if (*p == '\"') {
9057         p++;
9058         while (*p && *p != '\"') p++;
9059         if (*p == '\"') p++;
9060       } else {
9061         while (*p && *p != ' ') p++;
9062       }
9063     }
9064   }
9065
9066 }
9067
9068 void
9069 PeriodicUpdatesEvent(newState)
9070      int newState;
9071 {
9072     if (newState == appData.periodicUpdates)
9073       return;
9074
9075     appData.periodicUpdates=newState;
9076
9077     /* Display type changes, so update it now */
9078     DisplayAnalysis();
9079
9080     /* Get the ball rolling again... */
9081     if (newState) {
9082         AnalysisPeriodicEvent(1);
9083         StartAnalysisClock();
9084     }
9085 }
9086
9087 void
9088 PonderNextMoveEvent(newState)
9089      int newState;
9090 {
9091     if (newState == appData.ponderNextMove) return;
9092     if (gameMode == EditPosition) EditPositionDone();
9093     if (newState) {
9094         SendToProgram("hard\n", &first);
9095         if (gameMode == TwoMachinesPlay) {
9096             SendToProgram("hard\n", &second);
9097         }
9098     } else {
9099         SendToProgram("easy\n", &first);
9100         thinkOutput[0] = NULLCHAR;
9101         if (gameMode == TwoMachinesPlay) {
9102             SendToProgram("easy\n", &second);
9103         }
9104     }
9105     appData.ponderNextMove = newState;
9106 }
9107
9108 void
9109 ShowThinkingEvent(newState)
9110      int newState;
9111 {
9112     if (newState == appData.showThinking) return;
9113     if (gameMode == EditPosition) EditPositionDone();
9114     if (newState) {
9115         SendToProgram("post\n", &first);
9116         if (gameMode == TwoMachinesPlay) {
9117             SendToProgram("post\n", &second);
9118         }
9119     } else {
9120         SendToProgram("nopost\n", &first);
9121         thinkOutput[0] = NULLCHAR;
9122         if (gameMode == TwoMachinesPlay) {
9123             SendToProgram("nopost\n", &second);
9124         }
9125     }
9126     appData.showThinking = newState;
9127 }
9128
9129 void
9130 AskQuestionEvent(title, question, replyPrefix, which)
9131      char *title; char *question; char *replyPrefix; char *which;
9132 {
9133   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
9134   if (pr == NoProc) return;
9135   AskQuestion(title, question, replyPrefix, pr);
9136 }
9137
9138 void
9139 DisplayMove(moveNumber)
9140      int moveNumber;
9141 {
9142     char message[MSG_SIZ];
9143     char res[MSG_SIZ];
9144     char cpThinkOutput[MSG_SIZ];
9145
9146     if (moveNumber == forwardMostMove - 1 || 
9147         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9148
9149         strcpy(cpThinkOutput, thinkOutput);
9150         if (strchr(cpThinkOutput, '\n'))
9151           *strchr(cpThinkOutput, '\n') = NULLCHAR;
9152     } else {
9153         *cpThinkOutput = NULLCHAR;
9154     }
9155
9156     if (moveNumber == forwardMostMove - 1 &&
9157         gameInfo.resultDetails != NULL) {
9158         if (gameInfo.resultDetails[0] == NULLCHAR) {
9159             sprintf(res, " %s", PGNResult(gameInfo.result));
9160         } else {
9161             sprintf(res, " {%s} %s",
9162                     gameInfo.resultDetails, PGNResult(gameInfo.result));
9163         }
9164     } else {
9165         res[0] = NULLCHAR;
9166     }
9167     
9168     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9169         DisplayMessage(res, cpThinkOutput);
9170     } else {
9171         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
9172                 WhiteOnMove(moveNumber) ? " " : ".. ",
9173                 parseList[moveNumber], res);
9174         DisplayMessage(message, cpThinkOutput);
9175     }
9176 }
9177
9178 void
9179 DisplayAnalysisText(text)
9180      char *text;
9181 {
9182     char buf[MSG_SIZ];
9183
9184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
9185         sprintf(buf, "Analysis (%s)", first.tidy);
9186         AnalysisPopUp(buf, text);
9187     }
9188 }
9189
9190 static int
9191 only_one_move(str)
9192      char *str;
9193 {
9194     while (*str && isspace(*str)) ++str;
9195     while (*str && !isspace(*str)) ++str;
9196     if (!*str) return 1;
9197     while (*str && isspace(*str)) ++str;
9198     if (!*str) return 1;
9199     return 0;
9200 }
9201
9202 void
9203 DisplayAnalysis()
9204 {
9205     char buf[MSG_SIZ];
9206     double nps;
9207     static char *xtra[] = { "", " (--)", " (++)" };
9208     int h, m, s, cs;
9209   
9210     if (programStats.time == 0) {
9211         programStats.time = 1;
9212     }
9213   
9214     if (programStats.got_only_move) {
9215         strcpy(buf, programStats.movelist);
9216     } else {
9217         nps = (((double)programStats.nodes) /
9218                (((double)programStats.time)/100.0));
9219
9220         cs = programStats.time % 100;
9221         s = programStats.time / 100;
9222         h = (s / (60*60));
9223         s = s - h*60*60;
9224         m = (s/60);
9225         s = s - m*60;
9226
9227         if (programStats.moves_left > 0 && appData.periodicUpdates) {
9228           if (programStats.move_name[0] != NULLCHAR) {
9229             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9230                     programStats.depth,
9231                     programStats.nr_moves-programStats.moves_left,
9232                     programStats.nr_moves, programStats.move_name,
9233                     ((float)programStats.score)/100.0, programStats.movelist,
9234                     only_one_move(programStats.movelist)?
9235                     xtra[programStats.got_fail] : "",
9236                     programStats.nodes, (int)nps, h, m, s, cs);
9237           } else {
9238             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9239                     programStats.depth,
9240                     programStats.nr_moves-programStats.moves_left,
9241                     programStats.nr_moves, ((float)programStats.score)/100.0,
9242                     programStats.movelist,
9243                     only_one_move(programStats.movelist)?
9244                     xtra[programStats.got_fail] : "",
9245                     programStats.nodes, (int)nps, h, m, s, cs);
9246           }
9247         } else {
9248             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
9249                     programStats.depth,
9250                     ((float)programStats.score)/100.0,
9251                     programStats.movelist,
9252                     only_one_move(programStats.movelist)?
9253                     xtra[programStats.got_fail] : "",
9254                     programStats.nodes, (int)nps, h, m, s, cs);
9255         }
9256     }
9257     DisplayAnalysisText(buf);
9258 }
9259
9260 void
9261 DisplayComment(moveNumber, text)
9262      int moveNumber;
9263      char *text;
9264 {
9265     char title[MSG_SIZ];
9266
9267     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
9268         strcpy(title, "Comment");
9269     } else {
9270         sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
9271                 WhiteOnMove(moveNumber) ? " " : ".. ",
9272                 parseList[moveNumber]);
9273     }
9274
9275     CommentPopUp(title, text);
9276 }
9277
9278 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
9279  * might be busy thinking or pondering.  It can be omitted if your
9280  * gnuchess is configured to stop thinking immediately on any user
9281  * input.  However, that gnuchess feature depends on the FIONREAD
9282  * ioctl, which does not work properly on some flavors of Unix.
9283  */
9284 void
9285 Attention(cps)
9286      ChessProgramState *cps;
9287 {
9288 #if ATTENTION
9289     if (!cps->useSigint) return;
9290     if (appData.noChessProgram || (cps->pr == NoProc)) return;
9291     switch (gameMode) {
9292       case MachinePlaysWhite:
9293       case MachinePlaysBlack:
9294       case TwoMachinesPlay:
9295       case IcsPlayingWhite:
9296       case IcsPlayingBlack:
9297       case AnalyzeMode:
9298       case AnalyzeFile:
9299         /* Skip if we know it isn't thinking */
9300         if (!cps->maybeThinking) return;
9301         if (appData.debugMode)
9302           fprintf(debugFP, "Interrupting %s\n", cps->which);
9303         InterruptChildProcess(cps->pr);
9304         cps->maybeThinking = FALSE;
9305         break;
9306       default:
9307         break;
9308     }
9309 #endif /*ATTENTION*/
9310 }
9311
9312 int
9313 CheckFlags()
9314 {
9315     if (whiteTimeRemaining <= 0) {
9316         if (!whiteFlag) {
9317             whiteFlag = TRUE;
9318             if (appData.icsActive) {
9319                 if (appData.autoCallFlag &&
9320                     gameMode == IcsPlayingBlack && !blackFlag) {
9321                   SendToICS(ics_prefix);
9322                   SendToICS("flag\n");
9323                 }
9324             } else {
9325                 if (blackFlag) {
9326                     DisplayTitle("Both flags fell");
9327                 } else {
9328                     DisplayTitle("White's flag fell");
9329                     if (appData.autoCallFlag) {
9330                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
9331                         return TRUE;
9332                     }
9333                 }
9334             }
9335         }
9336     }
9337     if (blackTimeRemaining <= 0) {
9338         if (!blackFlag) {
9339             blackFlag = TRUE;
9340             if (appData.icsActive) {
9341                 if (appData.autoCallFlag &&
9342                     gameMode == IcsPlayingWhite && !whiteFlag) {
9343                   SendToICS(ics_prefix);
9344                   SendToICS("flag\n");
9345                 }
9346             } else {
9347                 if (whiteFlag) {
9348                     DisplayTitle("Both flags fell");
9349                 } else {
9350                     DisplayTitle("Black's flag fell");
9351                     if (appData.autoCallFlag) {
9352                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
9353                         return TRUE;
9354                     }
9355                 }
9356             }
9357         }
9358     }
9359     return FALSE;
9360 }
9361
9362 void
9363 CheckTimeControl()
9364 {
9365     if (!appData.clockMode || appData.icsActive ||
9366         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
9367
9368     if (timeIncrement >= 0) {
9369         if (WhiteOnMove(forwardMostMove)) {
9370             blackTimeRemaining += timeIncrement;
9371         } else {
9372             whiteTimeRemaining += timeIncrement;
9373         }
9374     }
9375     /*
9376      * add time to clocks when time control is achieved
9377      */
9378     if (movesPerSession) {
9379       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
9380       case 0:
9381         /* White made time control */
9382         whiteTimeRemaining += timeControl;
9383         break;
9384       case 1:
9385         /* Black made time control */
9386         blackTimeRemaining += timeControl;
9387         break;
9388       default:
9389         break;
9390       }
9391     }
9392 }
9393
9394 void
9395 DisplayBothClocks()
9396 {
9397     int wom = gameMode == EditPosition ?
9398       !blackPlaysFirst : WhiteOnMove(currentMove);
9399     DisplayWhiteClock(whiteTimeRemaining, wom);
9400     DisplayBlackClock(blackTimeRemaining, !wom);
9401 }
9402
9403
9404 /* Timekeeping seems to be a portability nightmare.  I think everyone
9405    has ftime(), but I'm really not sure, so I'm including some ifdefs
9406    to use other calls if you don't.  Clocks will be less accurate if
9407    you have neither ftime nor gettimeofday.
9408 */
9409
9410 /* Get the current time as a TimeMark */
9411 void
9412 GetTimeMark(tm)
9413      TimeMark *tm;
9414 {
9415 #if HAVE_GETTIMEOFDAY
9416
9417     struct timeval timeVal;
9418     struct timezone timeZone;
9419
9420     gettimeofday(&timeVal, &timeZone);
9421     tm->sec = (long) timeVal.tv_sec; 
9422     tm->ms = (int) (timeVal.tv_usec / 1000L);
9423
9424 #else /*!HAVE_GETTIMEOFDAY*/
9425 #if HAVE_FTIME
9426
9427 #include <sys/timeb.h>
9428     struct timeb timeB;
9429
9430     ftime(&timeB);
9431     tm->sec = (long) timeB.time;
9432     tm->ms = (int) timeB.millitm;
9433
9434 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
9435     tm->sec = (long) time(NULL);
9436     tm->ms = 0;
9437 #endif
9438 #endif
9439 }
9440
9441 /* Return the difference in milliseconds between two
9442    time marks.  We assume the difference will fit in a long!
9443 */
9444 long
9445 SubtractTimeMarks(tm2, tm1)
9446      TimeMark *tm2, *tm1;
9447 {
9448     return 1000L*(tm2->sec - tm1->sec) +
9449            (long) (tm2->ms - tm1->ms);
9450 }
9451
9452
9453 /*
9454  * Code to manage the game clocks.
9455  *
9456  * In tournament play, black starts the clock and then white makes a move.
9457  * We give the human user a slight advantage if he is playing white---the
9458  * clocks don't run until he makes his first move, so it takes zero time.
9459  * Also, we don't account for network lag, so we could get out of sync
9460  * with GNU Chess's clock -- but then, referees are always right.  
9461  */
9462
9463 static TimeMark tickStartTM;
9464 static long intendedTickLength;
9465
9466 long
9467 NextTickLength(timeRemaining)
9468      long timeRemaining;
9469 {
9470     long nominalTickLength, nextTickLength;
9471
9472     if (timeRemaining > 0L && timeRemaining <= 10000L)
9473       nominalTickLength = 100L;
9474     else
9475       nominalTickLength = 1000L;
9476     nextTickLength = timeRemaining % nominalTickLength;
9477     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
9478
9479     return nextTickLength;
9480 }
9481
9482 /* Stop clocks and reset to a fresh time control */
9483 void
9484 ResetClocks() 
9485 {
9486     (void) StopClockTimer();
9487     if (appData.icsActive) {
9488         whiteTimeRemaining = blackTimeRemaining = 0;
9489     } else {
9490         whiteTimeRemaining = blackTimeRemaining = timeControl;
9491     }
9492     if (whiteFlag || blackFlag) {
9493         DisplayTitle("");
9494         whiteFlag = blackFlag = FALSE;
9495     }
9496     DisplayBothClocks();
9497 }
9498
9499 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
9500
9501 /* Decrement running clock by amount of time that has passed */
9502 void
9503 DecrementClocks()
9504 {
9505     long timeRemaining;
9506     long lastTickLength, fudge;
9507     TimeMark now;
9508
9509     if (!appData.clockMode) return;
9510     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
9511         
9512     GetTimeMark(&now);
9513
9514     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9515
9516     /* Fudge if we woke up a little too soon */
9517     fudge = intendedTickLength - lastTickLength;
9518     if (fudge < 0 || fudge > FUDGE) fudge = 0;
9519
9520     if (WhiteOnMove(forwardMostMove)) {
9521         timeRemaining = whiteTimeRemaining -= lastTickLength;
9522         DisplayWhiteClock(whiteTimeRemaining - fudge,
9523                           WhiteOnMove(currentMove));
9524     } else {
9525         timeRemaining = blackTimeRemaining -= lastTickLength;
9526         DisplayBlackClock(blackTimeRemaining - fudge,
9527                           !WhiteOnMove(currentMove));
9528     }
9529
9530     if (CheckFlags()) return;
9531         
9532     tickStartTM = now;
9533     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
9534     StartClockTimer(intendedTickLength);
9535
9536     /* if the time remaining has fallen below the alarm threshold, sound the
9537      * alarm. if the alarm has sounded and (due to a takeback or time control
9538      * with increment) the time remaining has increased to a level above the
9539      * threshold, reset the alarm so it can sound again. 
9540      */
9541     
9542     if (appData.icsActive && appData.icsAlarm) {
9543
9544         /* make sure we are dealing with the user's clock */
9545         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
9546                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
9547            )) return;
9548
9549         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
9550             alarmSounded = FALSE;
9551         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
9552             PlayAlarmSound();
9553             alarmSounded = TRUE;
9554         }
9555     }
9556 }
9557
9558
9559 /* A player has just moved, so stop the previously running
9560    clock and (if in clock mode) start the other one.
9561    We redisplay both clocks in case we're in ICS mode, because
9562    ICS gives us an update to both clocks after every move.
9563    Note that this routine is called *after* forwardMostMove
9564    is updated, so the last fractional tick must be subtracted
9565    from the color that is *not* on move now.
9566 */
9567 void
9568 SwitchClocks()
9569 {
9570     long lastTickLength;
9571     TimeMark now;
9572     int flagged = FALSE;
9573
9574     GetTimeMark(&now);
9575
9576     if (StopClockTimer() && appData.clockMode) {
9577         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9578         if (WhiteOnMove(forwardMostMove)) {
9579             blackTimeRemaining -= lastTickLength;
9580         } else {
9581             whiteTimeRemaining -= lastTickLength;
9582         }
9583         flagged = CheckFlags();
9584     }
9585     CheckTimeControl();
9586
9587     if (flagged || !appData.clockMode) return;
9588
9589     switch (gameMode) {
9590       case MachinePlaysBlack:
9591       case MachinePlaysWhite:
9592       case BeginningOfGame:
9593         if (pausing) return;
9594         break;
9595
9596       case EditGame:
9597       case PlayFromGameFile:
9598       case IcsExamining:
9599         return;
9600
9601       default:
9602         break;
9603     }
9604
9605     tickStartTM = now;
9606     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9607       whiteTimeRemaining : blackTimeRemaining);
9608     StartClockTimer(intendedTickLength);
9609 }
9610         
9611
9612 /* Stop both clocks */
9613 void
9614 StopClocks()
9615 {       
9616     long lastTickLength;
9617     TimeMark now;
9618
9619     if (!StopClockTimer()) return;
9620     if (!appData.clockMode) return;
9621
9622     GetTimeMark(&now);
9623
9624     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
9625     if (WhiteOnMove(forwardMostMove)) {
9626         whiteTimeRemaining -= lastTickLength;
9627         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
9628     } else {
9629         blackTimeRemaining -= lastTickLength;
9630         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
9631     }
9632     CheckFlags();
9633 }
9634         
9635 /* Start clock of player on move.  Time may have been reset, so
9636    if clock is already running, stop and restart it. */
9637 void
9638 StartClocks()
9639 {
9640     (void) StopClockTimer(); /* in case it was running already */
9641     DisplayBothClocks();
9642     if (CheckFlags()) return;
9643
9644     if (!appData.clockMode) return;
9645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
9646
9647     GetTimeMark(&tickStartTM);
9648     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
9649       whiteTimeRemaining : blackTimeRemaining);
9650     StartClockTimer(intendedTickLength);
9651 }
9652
9653 char *
9654 TimeString(ms)
9655      long ms;
9656 {
9657     long second, minute, hour, day;
9658     char *sign = "";
9659     static char buf[32];
9660     
9661     if (ms > 0 && ms <= 9900) {
9662       /* convert milliseconds to tenths, rounding up */
9663       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
9664
9665       sprintf(buf, " %03.1f ", tenths/10.0);
9666       return buf;
9667     }
9668
9669     /* convert milliseconds to seconds, rounding up */
9670     /* use floating point to avoid strangeness of integer division
9671        with negative dividends on many machines */
9672     second = (long) floor(((double) (ms + 999L)) / 1000.0);
9673
9674     if (second < 0) {
9675         sign = "-";
9676         second = -second;
9677     }
9678     
9679     day = second / (60 * 60 * 24);
9680     second = second % (60 * 60 * 24);
9681     hour = second / (60 * 60);
9682     second = second % (60 * 60);
9683     minute = second / 60;
9684     second = second % 60;
9685     
9686     if (day > 0)
9687       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
9688               sign, day, hour, minute, second);
9689     else if (hour > 0)
9690       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
9691     else
9692       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
9693     
9694     return buf;
9695 }
9696
9697
9698 /*
9699  * This is necessary because some C libraries aren't ANSI C compliant yet.
9700  */
9701 char *
9702 StrStr(string, match)
9703      char *string, *match;
9704 {
9705     int i, length;
9706     
9707     length = strlen(match);
9708     
9709     for (i = strlen(string) - length; i >= 0; i--, string++)
9710       if (!strncmp(match, string, length))
9711         return string;
9712     
9713     return NULL;
9714 }
9715
9716 char *
9717 StrCaseStr(string, match)
9718      char *string, *match;
9719 {
9720     int i, j, length;
9721     
9722     length = strlen(match);
9723     
9724     for (i = strlen(string) - length; i >= 0; i--, string++) {
9725         for (j = 0; j < length; j++) {
9726             if (ToLower(match[j]) != ToLower(string[j]))
9727               break;
9728         }
9729         if (j == length) return string;
9730     }
9731
9732     return NULL;
9733 }
9734
9735 #ifndef _amigados
9736 int
9737 StrCaseCmp(s1, s2)
9738      char *s1, *s2;
9739 {
9740     char c1, c2;
9741     
9742     for (;;) {
9743         c1 = ToLower(*s1++);
9744         c2 = ToLower(*s2++);
9745         if (c1 > c2) return 1;
9746         if (c1 < c2) return -1;
9747         if (c1 == NULLCHAR) return 0;
9748     }
9749 }
9750
9751
9752 int
9753 ToLower(c)
9754      int c;
9755 {
9756     return isupper(c) ? tolower(c) : c;
9757 }
9758
9759
9760 int
9761 ToUpper(c)
9762      int c;
9763 {
9764     return islower(c) ? toupper(c) : c;
9765 }
9766 #endif /* !_amigados    */
9767
9768 char *
9769 StrSave(s)
9770      char *s;
9771 {
9772     char *ret;
9773
9774     if ((ret = (char *) malloc(strlen(s) + 1))) {
9775         strcpy(ret, s);
9776     }
9777     return ret;
9778 }
9779
9780 char *
9781 StrSavePtr(s, savePtr)
9782      char *s, **savePtr;
9783 {
9784     if (*savePtr) {
9785         free(*savePtr);
9786     }
9787     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
9788         strcpy(*savePtr, s);
9789     }
9790     return(*savePtr);
9791 }
9792
9793 char *
9794 PGNDate()
9795 {
9796     time_t clock;
9797     struct tm *tm;
9798     char buf[MSG_SIZ];
9799
9800     clock = time((time_t *)NULL);
9801     tm = localtime(&clock);
9802     sprintf(buf, "%04d.%02d.%02d",
9803             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
9804     return StrSave(buf);
9805 }
9806
9807
9808 char *
9809 PositionToFEN(move)
9810      int move;
9811 {
9812     int i, j, fromX, fromY, toX, toY;
9813     int whiteToPlay;
9814     char buf[128];
9815     char *p, *q;
9816     int emptycount;
9817
9818     whiteToPlay = (gameMode == EditPosition) ?
9819       !blackPlaysFirst : (move % 2 == 0);
9820     p = buf;
9821
9822     /* Piece placement data */
9823     for (i = BOARD_SIZE - 1; i >= 0; i--) {
9824         emptycount = 0;
9825         for (j = 0; j < BOARD_SIZE; j++) {
9826             if (boards[move][i][j] == EmptySquare) {
9827                 emptycount++;
9828             } else {
9829                 if (emptycount > 0) {
9830                     *p++ = '0' + emptycount;
9831                     emptycount = 0;
9832                 }
9833                 *p++ = PieceToChar(boards[move][i][j]);
9834             }
9835         }
9836         if (emptycount > 0) {
9837             *p++ = '0' + emptycount;
9838             emptycount = 0;
9839         }
9840         *p++ = '/';
9841     }
9842     *(p - 1) = ' ';
9843
9844     /* Active color */
9845     *p++ = whiteToPlay ? 'w' : 'b';
9846     *p++ = ' ';
9847
9848     /* !!We don't keep track of castling availability, so fake it */
9849     q = p;
9850     if (boards[move][0][4] == WhiteKing) {
9851         if (boards[move][0][7] == WhiteRook) *p++ = 'K';
9852         if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
9853     }
9854     if (boards[move][7][4] == BlackKing) {
9855         if (boards[move][7][7] == BlackRook) *p++ = 'k';
9856         if (boards[move][7][0] == BlackRook) *p++ = 'q';
9857     }       
9858     if (q == p) *p++ = '-';
9859     *p++ = ' ';
9860
9861     /* En passant target square */
9862     if (move > backwardMostMove) {
9863         fromX = moveList[move - 1][0] - 'a';
9864         fromY = moveList[move - 1][1] - '1';
9865         toX = moveList[move - 1][2] - 'a';
9866         toY = moveList[move - 1][3] - '1';
9867         if (fromY == (whiteToPlay ? 6 : 1) &&
9868             toY == (whiteToPlay ? 4 : 3) &&
9869             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
9870             fromX == toX) {
9871             /* 2-square pawn move just happened */
9872             *p++ = toX + 'a';
9873             *p++ = whiteToPlay ? '6' : '3';
9874         } else {
9875             *p++ = '-';
9876         }
9877     } else {
9878         *p++ = '-';
9879     }
9880
9881     /* !!We don't keep track of halfmove clock for 50-move rule */
9882     strcpy(p, " 0 ");
9883     p += 3;
9884
9885     /* Fullmove number */
9886     sprintf(p, "%d", (move / 2) + 1);
9887     
9888     return StrSave(buf);
9889 }
9890
9891 Boolean
9892 ParseFEN(board, blackPlaysFirst, fen)
9893      Board board;
9894      int *blackPlaysFirst;
9895      char *fen;
9896 {
9897     int i, j;
9898     char *p;
9899     int emptycount;
9900
9901     p = fen;
9902
9903     /* Piece placement data */
9904     for (i = BOARD_SIZE - 1; i >= 0; i--) {
9905         j = 0;
9906         for (;;) {
9907             if (*p == '/' || *p == ' ') {
9908                 if (*p == '/') p++;
9909                 emptycount = BOARD_SIZE - j;
9910                 while (emptycount--) board[i][j++] = EmptySquare;
9911                 break;
9912             } else if (isdigit(*p)) {
9913                 emptycount = *p++ - '0';
9914                 if (j + emptycount > BOARD_SIZE) return FALSE;
9915                 while (emptycount--) board[i][j++] = EmptySquare;
9916             } else if (isalpha(*p)) {
9917                 if (j >= BOARD_SIZE) return FALSE;
9918                 board[i][j++] = CharToPiece(*p++);
9919             } else {
9920                 return FALSE;
9921             }
9922         }
9923     }
9924     while (*p == '/' || *p == ' ') p++;
9925
9926     /* Active color */
9927     switch (*p) {
9928       case 'w':
9929         *blackPlaysFirst = FALSE;
9930         break;
9931       case 'b': 
9932         *blackPlaysFirst = TRUE;
9933         break;
9934       default:
9935         return FALSE;
9936     }
9937     /* !!We ignore the rest of the FEN notation */
9938     return TRUE;
9939 }
9940       
9941 void
9942 EditPositionPasteFEN(char *fen)
9943 {
9944   if (fen != NULL) {
9945     Board initial_position;
9946
9947     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
9948       DisplayError("Bad FEN position in clipboard", 0);
9949       return ;
9950     } else {
9951       int savedBlackPlaysFirst = blackPlaysFirst;
9952       EditPositionEvent();
9953       blackPlaysFirst = savedBlackPlaysFirst;
9954       CopyBoard(boards[0], initial_position);
9955       EditPositionDone();
9956       DisplayBothClocks();
9957       DrawPosition(FALSE, boards[currentMove]);
9958     }
9959   }
9960 }