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