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