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