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