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