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