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