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