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