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