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