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