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