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