fix for bug #27642: Clock jumps strangely in engine mode
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
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, char *castle, char *ep));
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 int 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, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 #if 0
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
305 {
306     size_t  dst_len;
307
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     dst_len = strlen(dst);
313
314     assert( count > dst_len ); /* Buffer size must be greater than current length */
315
316     safeStrCpy( dst + dst_len, src, count - dst_len );
317
318     return dst;
319 }
320 #endif
321
322 /* Some compiler can't cast u64 to double
323  * This function do the job for us:
324
325  * We use the highest bit for cast, this only
326  * works if the highest bit is not
327  * in use (This should not happen)
328  *
329  * We used this for all compiler
330  */
331 double
332 u64ToDouble(u64 value)
333 {
334   double r;
335   u64 tmp = value & u64Const(0x7fffffffffffffff);
336   r = (double)(s64)tmp;
337   if (value & u64Const(0x8000000000000000))
338        r +=  9.2233720368547758080e18; /* 2^63 */
339  return r;
340 }
341
342 /* Fake up flags for now, as we aren't keeping track of castling
343    availability yet. [HGM] Change of logic: the flag now only
344    indicates the type of castlings allowed by the rule of the game.
345    The actual rights themselves are maintained in the array
346    castlingRights, as part of the game history, and are not probed
347    by this function.
348  */
349 int
350 PosFlags(index)
351 {
352   int flags = F_ALL_CASTLE_OK;
353   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354   switch (gameInfo.variant) {
355   case VariantSuicide:
356     flags &= ~F_ALL_CASTLE_OK;
357   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358     flags |= F_IGNORE_CHECK;
359   case VariantLosers:
360     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361     break;
362   case VariantAtomic:
363     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
364     break;
365   case VariantKriegspiel:
366     flags |= F_KRIEGSPIEL_CAPTURE;
367     break;
368   case VariantCapaRandom: 
369   case VariantFischeRandom:
370     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371   case VariantNoCastle:
372   case VariantShatranj:
373   case VariantCourier:
374     flags &= ~F_ALL_CASTLE_OK;
375     break;
376   default:
377     break;
378   }
379   return flags;
380 }
381
382 FILE *gameFileFP, *debugFP;
383
384 /* 
385     [AS] Note: sometimes, the sscanf() function is used to parse the input
386     into a fixed-size buffer. Because of this, we must be prepared to
387     receive strings as long as the size of the input buffer, which is currently
388     set to 4K for Windows and 8K for the rest.
389     So, we must either allocate sufficiently large buffers here, or
390     reduce the size of the input buffer in the input reading part.
391 */
392
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
396
397 ChessProgramState first, second;
398
399 /* premove variables */
400 int premoveToX = 0;
401 int premoveToY = 0;
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
405 int gotPremove = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
408
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
411
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
439 int movesPerSession;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
444 int matchGame = 0;
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
448
449 /* animateTraining preserves the state of appData.animate
450  * when Training mode is activated. This allows the
451  * response to be animated when appData.animate == TRUE and
452  * appData.animateDragging == TRUE.
453  */
454 Boolean animateTraining;
455
456 GameInfo gameInfo;
457
458 AppData appData;
459
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char  epStatus[MAX_MOVES];
463 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int   initialRulePlies, FENrulePlies;
468 char  FENepStatus;
469 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 int loadFlag = 0; 
471 int shuffleOpenings;
472 int mute; // mute all sounds
473
474 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499         BlackKing, BlackBishop, BlackKnight, BlackRook }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509
510 #if (BOARD_SIZE>=10)
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
516 };
517
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
523 };
524
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
527         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
529         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
530 };
531
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
534         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
536         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
537 };
538
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
541         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
543         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 };
545
546 #ifdef GOTHIC
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
549         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
551         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
552 };
553 #else // !GOTHIC
554 #define GothicArray CapablancaArray
555 #endif // !GOTHIC
556
557 #ifdef FALCON
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
560         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
562         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
563 };
564 #else // !FALCON
565 #define FalconArray CapablancaArray
566 #endif // !FALCON
567
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
574
575 #if (BOARD_SIZE>=12)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 };
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
585
586
587 Board initialPosition;
588
589
590 /* Convert str to a rating. Checks for special cases of "----",
591
592    "++++", etc. Also strips ()'s */
593 int
594 string_to_rating(str)
595   char *str;
596 {
597   while(*str && !isdigit(*str)) ++str;
598   if (!*str)
599     return 0;   /* One of the special "no rating" cases */
600   else
601     return atoi(str);
602 }
603
604 void
605 ClearProgramStats()
606 {
607     /* Init programStats */
608     programStats.movelist[0] = 0;
609     programStats.depth = 0;
610     programStats.nr_moves = 0;
611     programStats.moves_left = 0;
612     programStats.nodes = 0;
613     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
614     programStats.score = 0;
615     programStats.got_only_move = 0;
616     programStats.got_fail = 0;
617     programStats.line_is_book = 0;
618 }
619
620 void
621 InitBackEnd1()
622 {
623     int matched, min, sec;
624
625     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626
627     GetTimeMark(&programStartTime);
628     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
629
630     ClearProgramStats();
631     programStats.ok_to_send = 1;
632     programStats.seen_stat = 0;
633
634     /*
635      * Initialize game list
636      */
637     ListNew(&gameList);
638
639
640     /*
641      * Internet chess server status
642      */
643     if (appData.icsActive) {
644         appData.matchMode = FALSE;
645         appData.matchGames = 0;
646 #if ZIPPY       
647         appData.noChessProgram = !appData.zippyPlay;
648 #else
649         appData.zippyPlay = FALSE;
650         appData.zippyTalk = FALSE;
651         appData.noChessProgram = TRUE;
652 #endif
653         if (*appData.icsHelper != NULLCHAR) {
654             appData.useTelnet = TRUE;
655             appData.telnetProgram = appData.icsHelper;
656         }
657     } else {
658         appData.zippyTalk = appData.zippyPlay = FALSE;
659     }
660
661     /* [AS] Initialize pv info list [HGM] and game state */
662     {
663         int i, j;
664
665         for( i=0; i<MAX_MOVES; i++ ) {
666             pvInfoList[i].depth = -1;
667             epStatus[i]=EP_NONE;
668             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669         }
670     }
671
672     /*
673      * Parse timeControl resource
674      */
675     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676                           appData.movesPerSession)) {
677         char buf[MSG_SIZ];
678         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679         DisplayFatalError(buf, 0, 2);
680     }
681
682     /*
683      * Parse searchTime resource
684      */
685     if (*appData.searchTime != NULLCHAR) {
686         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687         if (matched == 1) {
688             searchTime = min * 60;
689         } else if (matched == 2) {
690             searchTime = min * 60 + sec;
691         } else {
692             char buf[MSG_SIZ];
693             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694             DisplayFatalError(buf, 0, 2);
695         }
696     }
697
698     /* [AS] Adjudication threshold */
699     adjudicateLossThreshold = appData.adjudicateLossThreshold;
700     
701     first.which = "first";
702     second.which = "second";
703     first.maybeThinking = second.maybeThinking = FALSE;
704     first.pr = second.pr = NoProc;
705     first.isr = second.isr = NULL;
706     first.sendTime = second.sendTime = 2;
707     first.sendDrawOffers = 1;
708     if (appData.firstPlaysBlack) {
709         first.twoMachinesColor = "black\n";
710         second.twoMachinesColor = "white\n";
711     } else {
712         first.twoMachinesColor = "white\n";
713         second.twoMachinesColor = "black\n";
714     }
715     first.program = appData.firstChessProgram;
716     second.program = appData.secondChessProgram;
717     first.host = appData.firstHost;
718     second.host = appData.secondHost;
719     first.dir = appData.firstDirectory;
720     second.dir = appData.secondDirectory;
721     first.other = &second;
722     second.other = &first;
723     first.initString = appData.initString;
724     second.initString = appData.secondInitString;
725     first.computerString = appData.firstComputerString;
726     second.computerString = appData.secondComputerString;
727     first.useSigint = second.useSigint = TRUE;
728     first.useSigterm = second.useSigterm = TRUE;
729     first.reuse = appData.reuseFirst;
730     second.reuse = appData.reuseSecond;
731     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
732     second.nps = appData.secondNPS;
733     first.useSetboard = second.useSetboard = FALSE;
734     first.useSAN = second.useSAN = FALSE;
735     first.usePing = second.usePing = FALSE;
736     first.lastPing = second.lastPing = 0;
737     first.lastPong = second.lastPong = 0;
738     first.usePlayother = second.usePlayother = FALSE;
739     first.useColors = second.useColors = TRUE;
740     first.useUsermove = second.useUsermove = FALSE;
741     first.sendICS = second.sendICS = FALSE;
742     first.sendName = second.sendName = appData.icsActive;
743     first.sdKludge = second.sdKludge = FALSE;
744     first.stKludge = second.stKludge = FALSE;
745     TidyProgramName(first.program, first.host, first.tidy);
746     TidyProgramName(second.program, second.host, second.tidy);
747     first.matchWins = second.matchWins = 0;
748     strcpy(first.variants, appData.variant);
749     strcpy(second.variants, appData.variant);
750     first.analysisSupport = second.analysisSupport = 2; /* detect */
751     first.analyzing = second.analyzing = FALSE;
752     first.initDone = second.initDone = FALSE;
753
754     /* New features added by Tord: */
755     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757     /* End of new features added by Tord. */
758     first.fenOverride  = appData.fenOverride1;
759     second.fenOverride = appData.fenOverride2;
760
761     /* [HGM] time odds: set factor for each machine */
762     first.timeOdds  = appData.firstTimeOdds;
763     second.timeOdds = appData.secondTimeOdds;
764     { int norm = 1;
765         if(appData.timeOddsMode) {
766             norm = first.timeOdds;
767             if(norm > second.timeOdds) norm = second.timeOdds;
768         }
769         first.timeOdds /= norm;
770         second.timeOdds /= norm;
771     }
772
773     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774     first.accumulateTC = appData.firstAccumulateTC;
775     second.accumulateTC = appData.secondAccumulateTC;
776     first.maxNrOfSessions = second.maxNrOfSessions = 1;
777
778     /* [HGM] debug */
779     first.debug = second.debug = FALSE;
780     first.supportsNPS = second.supportsNPS = UNKNOWN;
781
782     /* [HGM] options */
783     first.optionSettings  = appData.firstOptions;
784     second.optionSettings = appData.secondOptions;
785
786     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788     first.isUCI = appData.firstIsUCI; /* [AS] */
789     second.isUCI = appData.secondIsUCI; /* [AS] */
790     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792
793     if (appData.firstProtocolVersion > PROTOVER ||
794         appData.firstProtocolVersion < 1) {
795       char buf[MSG_SIZ];
796       sprintf(buf, _("protocol version %d not supported"),
797               appData.firstProtocolVersion);
798       DisplayFatalError(buf, 0, 2);
799     } else {
800       first.protocolVersion = appData.firstProtocolVersion;
801     }
802
803     if (appData.secondProtocolVersion > PROTOVER ||
804         appData.secondProtocolVersion < 1) {
805       char buf[MSG_SIZ];
806       sprintf(buf, _("protocol version %d not supported"),
807               appData.secondProtocolVersion);
808       DisplayFatalError(buf, 0, 2);
809     } else {
810       second.protocolVersion = appData.secondProtocolVersion;
811     }
812
813     if (appData.icsActive) {
814         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
815     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816         appData.clockMode = FALSE;
817         first.sendTime = second.sendTime = 0;
818     }
819     
820 #if ZIPPY
821     /* Override some settings from environment variables, for backward
822        compatibility.  Unfortunately it's not feasible to have the env
823        vars just set defaults, at least in xboard.  Ugh.
824     */
825     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826       ZippyInit();
827     }
828 #endif
829     
830     if (appData.noChessProgram) {
831         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832         sprintf(programVersion, "%s", PACKAGE_STRING);
833     } else {
834 #if 0
835         char *p, *q;
836         q = first.program;
837         while (*q != ' ' && *q != NULLCHAR) q++;
838         p = q;
839         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841         sprintf(programVersion, "%s + ", PACKAGE_STRING);
842         strncat(programVersion, p, q - p);
843 #else
844         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 #endif
848     }
849
850     if (!appData.icsActive) {
851       char buf[MSG_SIZ];
852       /* Check for variants that are supported only in ICS mode,
853          or not at all.  Some that are accepted here nevertheless
854          have bugs; see comments below.
855       */
856       VariantClass variant = StringToVariant(appData.variant);
857       switch (variant) {
858       case VariantBughouse:     /* need four players and two boards */
859       case VariantKriegspiel:   /* need to hide pieces and move details */
860       /* case VariantFischeRandom: (Fabien: moved below) */
861         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862         DisplayFatalError(buf, 0, 2);
863         return;
864
865       case VariantUnknown:
866       case VariantLoadable:
867       case Variant29:
868       case Variant30:
869       case Variant31:
870       case Variant32:
871       case Variant33:
872       case Variant34:
873       case Variant35:
874       case Variant36:
875       default:
876         sprintf(buf, _("Unknown variant name %s"), appData.variant);
877         DisplayFatalError(buf, 0, 2);
878         return;
879
880       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
881       case VariantFairy:      /* [HGM] TestLegality definitely off! */
882       case VariantGothic:     /* [HGM] should work */
883       case VariantCapablanca: /* [HGM] should work */
884       case VariantCourier:    /* [HGM] initial forced moves not implemented */
885       case VariantShogi:      /* [HGM] drops not tested for legality */
886       case VariantKnightmate: /* [HGM] should work */
887       case VariantCylinder:   /* [HGM] untested */
888       case VariantFalcon:     /* [HGM] untested */
889       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890                                  offboard interposition not understood */
891       case VariantNormal:     /* definitely works! */
892       case VariantWildCastle: /* pieces not automatically shuffled */
893       case VariantNoCastle:   /* pieces not automatically shuffled */
894       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895       case VariantLosers:     /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantSuicide:    /* should work except for win condition,
898                                  and doesn't know captures are mandatory */
899       case VariantGiveaway:   /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantTwoKings:   /* should work */
902       case VariantAtomic:     /* should work except for win condition */
903       case Variant3Check:     /* should work except for win condition */
904       case VariantShatranj:   /* should work except for all win conditions */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013 #if 0
1014     int matched, min, sec;
1015
1016     matched = sscanf(tc, "%d:%d", &min, &sec);
1017     if (matched == 1) {
1018         timeControl = min * 60 * 1000;
1019     } else if (matched == 2) {
1020         timeControl = (min * 60 + sec) * 1000;
1021     } else {
1022         return FALSE;
1023     }
1024 #else
1025     long tc1;
1026     long tc2;
1027     char buf[MSG_SIZ];
1028
1029     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1030     if(ti > 0) {
1031         if(mps)
1032              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033         else sprintf(buf, "+%s+%d", tc, ti);
1034     } else {
1035         if(mps)
1036              sprintf(buf, "+%d/%s", mps, tc);
1037         else sprintf(buf, "+%s", tc);
1038     }
1039     fullTimeControlString = StrSave(buf);
1040
1041     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042         return FALSE;
1043     }
1044
1045     if( *tc == '/' ) {
1046         /* Parse second time control */
1047         tc++;
1048
1049         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1050             return FALSE;
1051         }
1052
1053         if( tc2 == 0 ) {
1054             return FALSE;
1055         }
1056
1057         timeControl_2 = tc2 * 1000;
1058     }
1059     else {
1060         timeControl_2 = 0;
1061     }
1062
1063     if( tc1 == 0 ) {
1064         return FALSE;
1065     }
1066
1067     timeControl = tc1 * 1000;
1068 #endif
1069
1070     if (ti >= 0) {
1071         timeIncrement = ti * 1000;  /* convert to ms */
1072         movesPerSession = 0;
1073     } else {
1074         timeIncrement = 0;
1075         movesPerSession = mps;
1076     }
1077     return TRUE;
1078 }
1079
1080 void
1081 InitBackEnd2()
1082 {
1083     if (appData.debugMode) {
1084         fprintf(debugFP, "%s\n", programVersion);
1085     }
1086
1087     if (appData.matchGames > 0) {
1088         appData.matchMode = TRUE;
1089     } else if (appData.matchMode) {
1090         appData.matchGames = 1;
1091     }
1092     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093         appData.matchGames = appData.sameColorGames;
1094     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1097     }
1098     Reset(TRUE, FALSE);
1099     if (appData.noChessProgram || first.protocolVersion == 1) {
1100       InitBackEnd3();
1101     } else {
1102       /* kludge: allow timeout for initial "feature" commands */
1103       FreezeUI();
1104       DisplayMessage("", _("Starting chess program"));
1105       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106     }
1107 }
1108
1109 void
1110 InitBackEnd3 P((void))
1111 {
1112     GameMode initialMode;
1113     char buf[MSG_SIZ];
1114     int err;
1115
1116     InitChessProgram(&first, startedFromSetupPosition);
1117
1118
1119     if (appData.icsActive) {
1120 #ifdef WIN32
1121         /* [DM] Make a console window if needed [HGM] merged ifs */
1122         ConsoleCreate(); 
1123 #endif
1124         err = establish();
1125         if (err != 0) {
1126             if (*appData.icsCommPort != NULLCHAR) {
1127                 sprintf(buf, _("Could not open comm port %s"),  
1128                         appData.icsCommPort);
1129             } else {
1130                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1131                         appData.icsHost, appData.icsPort);
1132             }
1133             DisplayFatalError(buf, err, 1);
1134             return;
1135         }
1136         SetICSMode();
1137         telnetISR =
1138           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139         fromUserISR =
1140           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153     
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile; 
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) { int i;
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1240             }
1241         }
1242         if (initialMode == AnalyzeMode) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249             return;
1250           }
1251           AnalyzeModeEvent();
1252         } else if (initialMode == AnalyzeFile) {
1253           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254           ShowThinkingEvent();
1255           AnalyzeFileEvent();
1256           AnalysisPeriodicEvent(1);
1257         } else if (initialMode == MachinePlaysWhite) {
1258           if (appData.noChessProgram) {
1259             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260                               0, 2);
1261             return;
1262           }
1263           if (appData.icsActive) {
1264             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265                               0, 2);
1266             return;
1267           }
1268           MachineWhiteEvent();
1269         } else if (initialMode == MachinePlaysBlack) {
1270           if (appData.noChessProgram) {
1271             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272                               0, 2);
1273             return;
1274           }
1275           if (appData.icsActive) {
1276             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277                               0, 2);
1278             return;
1279           }
1280           MachineBlackEvent();
1281         } else if (initialMode == TwoMachinesPlay) {
1282           if (appData.noChessProgram) {
1283             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284                               0, 2);
1285             return;
1286           }
1287           if (appData.icsActive) {
1288             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289                               0, 2);
1290             return;
1291           }
1292           TwoMachinesEvent();
1293         } else if (initialMode == EditGame) {
1294           EditGameEvent();
1295         } else if (initialMode == EditPosition) {
1296           EditPositionEvent();
1297         } else if (initialMode == Training) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1300             return;
1301           }
1302           TrainingEvent();
1303         }
1304     }
1305 }
1306
1307 /*
1308  * Establish will establish a contact to a remote host.port.
1309  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310  *  used to talk to the host.
1311  * Returns 0 if okay, error code if not.
1312  */
1313 int
1314 establish()
1315 {
1316     char buf[MSG_SIZ];
1317
1318     if (*appData.icsCommPort != NULLCHAR) {
1319         /* Talk to the host through a serial comm port */
1320         return OpenCommPort(appData.icsCommPort, &icsPR);
1321
1322     } else if (*appData.gateway != NULLCHAR) {
1323         if (*appData.remoteShell == NULLCHAR) {
1324             /* Use the rcmd protocol to run telnet program on a gateway host */
1325             snprintf(buf, sizeof(buf), "%s %s %s",
1326                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1327             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1328
1329         } else {
1330             /* Use the rsh program to run telnet program on a gateway host */
1331             if (*appData.remoteUser == NULLCHAR) {
1332                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333                         appData.gateway, appData.telnetProgram,
1334                         appData.icsHost, appData.icsPort);
1335             } else {
1336                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337                         appData.remoteShell, appData.gateway, 
1338                         appData.remoteUser, appData.telnetProgram,
1339                         appData.icsHost, appData.icsPort);
1340             }
1341             return StartChildProcess(buf, "", &icsPR);
1342
1343         }
1344     } else if (appData.useTelnet) {
1345         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1346
1347     } else {
1348         /* TCP socket interface differs somewhat between
1349            Unix and NT; handle details in the front end.
1350            */
1351         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352     }
1353 }
1354
1355 void
1356 show_bytes(fp, buf, count)
1357      FILE *fp;
1358      char *buf;
1359      int count;
1360 {
1361     while (count--) {
1362         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363             fprintf(fp, "\\%03o", *buf & 0xff);
1364         } else {
1365             putc(*buf, fp);
1366         }
1367         buf++;
1368     }
1369     fflush(fp);
1370 }
1371
1372 /* Returns an errno value */
1373 int
1374 OutputMaybeTelnet(pr, message, count, outError)
1375      ProcRef pr;
1376      char *message;
1377      int count;
1378      int *outError;
1379 {
1380     char buf[8192], *p, *q, *buflim;
1381     int left, newcount, outcount;
1382
1383     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384         *appData.gateway != NULLCHAR) {
1385         if (appData.debugMode) {
1386             fprintf(debugFP, ">ICS: ");
1387             show_bytes(debugFP, message, count);
1388             fprintf(debugFP, "\n");
1389         }
1390         return OutputToProcess(pr, message, count, outError);
1391     }
1392
1393     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1394     p = message;
1395     q = buf;
1396     left = count;
1397     newcount = 0;
1398     while (left) {
1399         if (q >= buflim) {
1400             if (appData.debugMode) {
1401                 fprintf(debugFP, ">ICS: ");
1402                 show_bytes(debugFP, buf, newcount);
1403                 fprintf(debugFP, "\n");
1404             }
1405             outcount = OutputToProcess(pr, buf, newcount, outError);
1406             if (outcount < newcount) return -1; /* to be sure */
1407             q = buf;
1408             newcount = 0;
1409         }
1410         if (*p == '\n') {
1411             *q++ = '\r';
1412             newcount++;
1413         } else if (((unsigned char) *p) == TN_IAC) {
1414             *q++ = (char) TN_IAC;
1415             newcount ++;
1416         }
1417         *q++ = *p++;
1418         newcount++;
1419         left--;
1420     }
1421     if (appData.debugMode) {
1422         fprintf(debugFP, ">ICS: ");
1423         show_bytes(debugFP, buf, newcount);
1424         fprintf(debugFP, "\n");
1425     }
1426     outcount = OutputToProcess(pr, buf, newcount, outError);
1427     if (outcount < newcount) return -1; /* to be sure */
1428     return count;
1429 }
1430
1431 void
1432 read_from_player(isr, closure, message, count, error)
1433      InputSourceRef isr;
1434      VOIDSTAR closure;
1435      char *message;
1436      int count;
1437      int error;
1438 {
1439     int outError, outCount;
1440     static int gotEof = 0;
1441
1442     /* Pass data read from player on to ICS */
1443     if (count > 0) {
1444         gotEof = 0;
1445         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446         if (outCount < count) {
1447             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448         }
1449     } else if (count < 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452     } else if (gotEof++ > 0) {
1453         RemoveInputSource(isr);
1454         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1455     }
1456 }
1457
1458 void
1459 KeepAlive()
1460 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461     SendToICS("date\n");
1462     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1463 }
1464
1465 void
1466 SendToICS(s)
1467      char *s;
1468 {
1469     int count, outCount, outError;
1470
1471     if (icsPR == NULL) return;
1472
1473     count = strlen(s);
1474     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475     if (outCount < count) {
1476         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477     }
1478 }
1479
1480 /* This is used for sending logon scripts to the ICS. Sending
1481    without a delay causes problems when using timestamp on ICC
1482    (at least on my machine). */
1483 void
1484 SendToICSDelayed(s,msdelay)
1485      char *s;
1486      long msdelay;
1487 {
1488     int count, outCount, outError;
1489
1490     if (icsPR == NULL) return;
1491
1492     count = strlen(s);
1493     if (appData.debugMode) {
1494         fprintf(debugFP, ">ICS: ");
1495         show_bytes(debugFP, s, count);
1496         fprintf(debugFP, "\n");
1497     }
1498     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499                                       msdelay);
1500     if (outCount < count) {
1501         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1502     }
1503 }
1504
1505
1506 /* Remove all highlighting escape sequences in s
1507    Also deletes any suffix starting with '(' 
1508    */
1509 char *
1510 StripHighlightAndTitle(s)
1511      char *s;
1512 {
1513     static char retbuf[MSG_SIZ];
1514     char *p = retbuf;
1515
1516     while (*s != NULLCHAR) {
1517         while (*s == '\033') {
1518             while (*s != NULLCHAR && !isalpha(*s)) s++;
1519             if (*s != NULLCHAR) s++;
1520         }
1521         while (*s != NULLCHAR && *s != '\033') {
1522             if (*s == '(' || *s == '[') {
1523                 *p = NULLCHAR;
1524                 return retbuf;
1525             }
1526             *p++ = *s++;
1527         }
1528     }
1529     *p = NULLCHAR;
1530     return retbuf;
1531 }
1532
1533 /* Remove all highlighting escape sequences in s */
1534 char *
1535 StripHighlight(s)
1536      char *s;
1537 {
1538     static char retbuf[MSG_SIZ];
1539     char *p = retbuf;
1540
1541     while (*s != NULLCHAR) {
1542         while (*s == '\033') {
1543             while (*s != NULLCHAR && !isalpha(*s)) s++;
1544             if (*s != NULLCHAR) s++;
1545         }
1546         while (*s != NULLCHAR && *s != '\033') {
1547             *p++ = *s++;
1548         }
1549     }
1550     *p = NULLCHAR;
1551     return retbuf;
1552 }
1553
1554 char *variantNames[] = VARIANT_NAMES;
1555 char *
1556 VariantName(v)
1557      VariantClass v;
1558 {
1559     return variantNames[v];
1560 }
1561
1562
1563 /* Identify a variant from the strings the chess servers use or the
1564    PGN Variant tag names we use. */
1565 VariantClass
1566 StringToVariant(e)
1567      char *e;
1568 {
1569     char *p;
1570     int wnum = -1;
1571     VariantClass v = VariantNormal;
1572     int i, found = FALSE;
1573     char buf[MSG_SIZ];
1574
1575     if (!e) return v;
1576
1577     /* [HGM] skip over optional board-size prefixes */
1578     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580         while( *e++ != '_');
1581     }
1582
1583     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584       if (StrCaseStr(e, variantNames[i])) {
1585         v = (VariantClass) i;
1586         found = TRUE;
1587         break;
1588       }
1589     }
1590
1591     if (!found) {
1592       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593           || StrCaseStr(e, "wild/fr") 
1594           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595         v = VariantFischeRandom;
1596       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597                  (i = 1, p = StrCaseStr(e, "w"))) {
1598         p += i;
1599         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1600         if (isdigit(*p)) {
1601           wnum = atoi(p);
1602         } else {
1603           wnum = -1;
1604         }
1605         switch (wnum) {
1606         case 0: /* FICS only, actually */
1607         case 1:
1608           /* Castling legal even if K starts on d-file */
1609           v = VariantWildCastle;
1610           break;
1611         case 2:
1612         case 3:
1613         case 4:
1614           /* Castling illegal even if K & R happen to start in
1615              normal positions. */
1616           v = VariantNoCastle;
1617           break;
1618         case 5:
1619         case 7:
1620         case 8:
1621         case 10:
1622         case 11:
1623         case 12:
1624         case 13:
1625         case 14:
1626         case 15:
1627         case 18:
1628         case 19:
1629           /* Castling legal iff K & R start in normal positions */
1630           v = VariantNormal;
1631           break;
1632         case 6:
1633         case 20:
1634         case 21:
1635           /* Special wilds for position setup; unclear what to do here */
1636           v = VariantLoadable;
1637           break;
1638         case 9:
1639           /* Bizarre ICC game */
1640           v = VariantTwoKings;
1641           break;
1642         case 16:
1643           v = VariantKriegspiel;
1644           break;
1645         case 17:
1646           v = VariantLosers;
1647           break;
1648         case 22:
1649           v = VariantFischeRandom;
1650           break;
1651         case 23:
1652           v = VariantCrazyhouse;
1653           break;
1654         case 24:
1655           v = VariantBughouse;
1656           break;
1657         case 25:
1658           v = Variant3Check;
1659           break;
1660         case 26:
1661           /* Not quite the same as FICS suicide! */
1662           v = VariantGiveaway;
1663           break;
1664         case 27:
1665           v = VariantAtomic;
1666           break;
1667         case 28:
1668           v = VariantShatranj;
1669           break;
1670
1671         /* Temporary names for future ICC types.  The name *will* change in 
1672            the next xboard/WinBoard release after ICC defines it. */
1673         case 29:
1674           v = Variant29;
1675           break;
1676         case 30:
1677           v = Variant30;
1678           break;
1679         case 31:
1680           v = Variant31;
1681           break;
1682         case 32:
1683           v = Variant32;
1684           break;
1685         case 33:
1686           v = Variant33;
1687           break;
1688         case 34:
1689           v = Variant34;
1690           break;
1691         case 35:
1692           v = Variant35;
1693           break;
1694         case 36:
1695           v = Variant36;
1696           break;
1697         case 37:
1698           v = VariantShogi;
1699           break;
1700         case 38:
1701           v = VariantXiangqi;
1702           break;
1703         case 39:
1704           v = VariantCourier;
1705           break;
1706         case 40:
1707           v = VariantGothic;
1708           break;
1709         case 41:
1710           v = VariantCapablanca;
1711           break;
1712         case 42:
1713           v = VariantKnightmate;
1714           break;
1715         case 43:
1716           v = VariantFairy;
1717           break;
1718         case 44:
1719           v = VariantCylinder;
1720           break;
1721         case 45:
1722           v = VariantFalcon;
1723           break;
1724         case 46:
1725           v = VariantCapaRandom;
1726           break;
1727         case 47:
1728           v = VariantBerolina;
1729           break;
1730         case 48:
1731           v = VariantJanus;
1732           break;
1733         case 49:
1734           v = VariantSuper;
1735           break;
1736         case 50:
1737           v = VariantGreat;
1738           break;
1739         case -1:
1740           /* Found "wild" or "w" in the string but no number;
1741              must assume it's normal chess. */
1742           v = VariantNormal;
1743           break;
1744         default:
1745           sprintf(buf, _("Unknown wild type %d"), wnum);
1746           DisplayError(buf, 0);
1747           v = VariantUnknown;
1748           break;
1749         }
1750       }
1751     }
1752     if (appData.debugMode) {
1753       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754               e, wnum, VariantName(v));
1755     }
1756     return v;
1757 }
1758
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1761
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763    advance *index beyond it, and set leftover_start to the new value of
1764    *index; else return FALSE.  If pattern contains the character '*', it
1765    matches any sequence of characters not containing '\r', '\n', or the
1766    character following the '*' (if any), and the matched sequence(s) are
1767    copied into star_match.
1768    */
1769 int
1770 looking_at(buf, index, pattern)
1771      char *buf;
1772      int *index;
1773      char *pattern;
1774 {
1775     char *bufp = &buf[*index], *patternp = pattern;
1776     int star_count = 0;
1777     char *matchp = star_match[0];
1778     
1779     for (;;) {
1780         if (*patternp == NULLCHAR) {
1781             *index = leftover_start = bufp - buf;
1782             *matchp = NULLCHAR;
1783             return TRUE;
1784         }
1785         if (*bufp == NULLCHAR) return FALSE;
1786         if (*patternp == '*') {
1787             if (*bufp == *(patternp + 1)) {
1788                 *matchp = NULLCHAR;
1789                 matchp = star_match[++star_count];
1790                 patternp += 2;
1791                 bufp++;
1792                 continue;
1793             } else if (*bufp == '\n' || *bufp == '\r') {
1794                 patternp++;
1795                 if (*patternp == NULLCHAR)
1796                   continue;
1797                 else
1798                   return FALSE;
1799             } else {
1800                 *matchp++ = *bufp++;
1801                 continue;
1802             }
1803         }
1804         if (*patternp != *bufp) return FALSE;
1805         patternp++;
1806         bufp++;
1807     }
1808 }
1809
1810 void
1811 SendToPlayer(data, length)
1812      char *data;
1813      int length;
1814 {
1815     int error, outCount;
1816     outCount = OutputToProcess(NoProc, data, length, &error);
1817     if (outCount < length) {
1818         DisplayFatalError(_("Error writing to display"), error, 1);
1819     }
1820 }
1821
1822 void
1823 PackHolding(packed, holding)
1824      char packed[];
1825      char *holding;
1826 {
1827     char *p = holding;
1828     char *q = packed;
1829     int runlength = 0;
1830     int curr = 9999;
1831     do {
1832         if (*p == curr) {
1833             runlength++;
1834         } else {
1835             switch (runlength) {
1836               case 0:
1837                 break;
1838               case 1:
1839                 *q++ = curr;
1840                 break;
1841               case 2:
1842                 *q++ = curr;
1843                 *q++ = curr;
1844                 break;
1845               default:
1846                 sprintf(q, "%d", runlength);
1847                 while (*q) q++;
1848                 *q++ = curr;
1849                 break;
1850             }
1851             runlength = 1;
1852             curr = *p;
1853         }
1854     } while (*p++);
1855     *q = NULLCHAR;
1856 }
1857
1858 /* Telnet protocol requests from the front end */
1859 void
1860 TelnetRequest(ddww, option)
1861      unsigned char ddww, option;
1862 {
1863     unsigned char msg[3];
1864     int outCount, outError;
1865
1866     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867
1868     if (appData.debugMode) {
1869         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1870         switch (ddww) {
1871           case TN_DO:
1872             ddwwStr = "DO";
1873             break;
1874           case TN_DONT:
1875             ddwwStr = "DONT";
1876             break;
1877           case TN_WILL:
1878             ddwwStr = "WILL";
1879             break;
1880           case TN_WONT:
1881             ddwwStr = "WONT";
1882             break;
1883           default:
1884             ddwwStr = buf1;
1885             sprintf(buf1, "%d", ddww);
1886             break;
1887         }
1888         switch (option) {
1889           case TN_ECHO:
1890             optionStr = "ECHO";
1891             break;
1892           default:
1893             optionStr = buf2;
1894             sprintf(buf2, "%d", option);
1895             break;
1896         }
1897         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1898     }
1899     msg[0] = TN_IAC;
1900     msg[1] = ddww;
1901     msg[2] = option;
1902     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903     if (outCount < 3) {
1904         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1905     }
1906 }
1907
1908 void
1909 DoEcho()
1910 {
1911     if (!appData.icsActive) return;
1912     TelnetRequest(TN_DO, TN_ECHO);
1913 }
1914
1915 void
1916 DontEcho()
1917 {
1918     if (!appData.icsActive) return;
1919     TelnetRequest(TN_DONT, TN_ECHO);
1920 }
1921
1922 void
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 {
1925     /* put the holdings sent to us by the server on the board holdings area */
1926     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1927     char p;
1928     ChessSquare piece;
1929
1930     if(gameInfo.holdingsWidth < 2)  return;
1931
1932     if( (int)lowestPiece >= BlackPawn ) {
1933         holdingsColumn = 0;
1934         countsColumn = 1;
1935         holdingsStartRow = BOARD_HEIGHT-1;
1936         direction = -1;
1937     } else {
1938         holdingsColumn = BOARD_WIDTH-1;
1939         countsColumn = BOARD_WIDTH-2;
1940         holdingsStartRow = 0;
1941         direction = 1;
1942     }
1943
1944     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945         board[i][holdingsColumn] = EmptySquare;
1946         board[i][countsColumn]   = (ChessSquare) 0;
1947     }
1948     while( (p=*holdings++) != NULLCHAR ) {
1949         piece = CharToPiece( ToUpper(p) );
1950         if(piece == EmptySquare) continue;
1951         /*j = (int) piece - (int) WhitePawn;*/
1952         j = PieceToNumber(piece);
1953         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954         if(j < 0) continue;               /* should not happen */
1955         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957         board[holdingsStartRow+j*direction][countsColumn]++;
1958     }
1959
1960 }
1961
1962
1963 void
1964 VariantSwitch(Board board, VariantClass newVariant)
1965 {
1966    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969
1970    startedFromPositionFile = FALSE;
1971    if(gameInfo.variant == newVariant) return;
1972
1973    /* [HGM] This routine is called each time an assignment is made to
1974     * gameInfo.variant during a game, to make sure the board sizes
1975     * are set to match the new variant. If that means adding or deleting
1976     * holdings, we shift the playing board accordingly
1977     * This kludge is needed because in ICS observe mode, we get boards
1978     * of an ongoing game without knowing the variant, and learn about the
1979     * latter only later. This can be because of the move list we requested,
1980     * in which case the game history is refilled from the beginning anyway,
1981     * but also when receiving holdings of a crazyhouse game. In the latter
1982     * case we want to add those holdings to the already received position.
1983     */
1984
1985
1986   if (appData.debugMode) {
1987     fprintf(debugFP, "Switch board from %s to %s\n",
1988                VariantName(gameInfo.variant), VariantName(newVariant));
1989     setbuf(debugFP, NULL);
1990   }
1991     shuffleOpenings = 0;       /* [HGM] shuffle */
1992     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993     switch(newVariant) {
1994             case VariantShogi:
1995               newWidth = 9;  newHeight = 9;
1996               gameInfo.holdingsSize = 7;
1997             case VariantBughouse:
1998             case VariantCrazyhouse:
1999               newHoldingsWidth = 2; break;
2000             default:
2001               newHoldingsWidth = gameInfo.holdingsSize = 0;
2002     }
2003
2004     if(newWidth  != gameInfo.boardWidth  ||
2005        newHeight != gameInfo.boardHeight ||
2006        newHoldingsWidth != gameInfo.holdingsWidth ) {
2007
2008         /* shift position to new playing area, if needed */
2009         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010            for(i=0; i<BOARD_HEIGHT; i++) 
2011                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013                                                      board[i][j];
2014            for(i=0; i<newHeight; i++) {
2015                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017            }
2018         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019            for(i=0; i<BOARD_HEIGHT; i++)
2020                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022                                                  board[i][j];
2023         }
2024
2025         gameInfo.boardWidth  = newWidth;
2026         gameInfo.boardHeight = newHeight;
2027         gameInfo.holdingsWidth = newHoldingsWidth;
2028         gameInfo.variant = newVariant;
2029         InitDrawingSizes(-2, 0);
2030
2031         /* [HGM] The following should definitely be solved in a better way */
2032 #if 0
2033         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035         saveEP = epStatus[0];
2036 #endif
2037         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2038 #if 0
2039         epStatus[0] = saveEP;
2040         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2042 #endif
2043     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044
2045     forwardMostMove = oldForwardMostMove;
2046     backwardMostMove = oldBackwardMostMove;
2047     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2048 }
2049
2050 static int loggedOn = FALSE;
2051
2052 /*-- Game start info cache: --*/
2053 int gs_gamenum;
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2060
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2063
2064 void
2065 read_from_ics(isr, closure, data, count, error)
2066      InputSourceRef isr;
2067      VOIDSTAR closure;
2068      char *data;
2069      int count;
2070      int error;
2071 {
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2081     
2082     static int started = STARTED_NONE;
2083     static char parse[20000];
2084     static int parse_pos = 0;
2085     static char buf[BUF_SIZE + 1];
2086     static int firstTime = TRUE, intfSet = FALSE;
2087     static ColorClass prevColor = ColorNormal;
2088     static int savingComment = FALSE;
2089     char str[500];
2090     int i, oldi;
2091     int buf_len;
2092     int next_out;
2093     int tkind;
2094     int backup;    /* [DM] For zippy color lines */
2095     char *p;
2096     char talker[MSG_SIZ]; // [HGM] chat
2097     int channel;
2098
2099     if (appData.debugMode) {
2100       if (!error) {
2101         fprintf(debugFP, "<ICS: ");
2102         show_bytes(debugFP, data, count);
2103         fprintf(debugFP, "\n");
2104       }
2105     }
2106
2107     if (appData.debugMode) { int f = forwardMostMove;
2108         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2110     }
2111     if (count > 0) {
2112         /* If last read ended with a partial line that we couldn't parse,
2113            prepend it to the new read and try again. */
2114         if (leftover_len > 0) {
2115             for (i=0; i<leftover_len; i++)
2116               buf[i] = buf[leftover_start + i];
2117         }
2118
2119         /* Copy in new characters, removing nulls and \r's */
2120         buf_len = leftover_len;
2121         for (i = 0; i < count; i++) {
2122             if (data[i] != NULLCHAR && data[i] != '\r')
2123               buf[buf_len++] = data[i];
2124             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2125                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2126                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2128             }
2129         }
2130
2131         buf[buf_len] = NULLCHAR;
2132         next_out = leftover_len;
2133         leftover_start = 0;
2134         
2135         i = 0;
2136         while (i < buf_len) {
2137             /* Deal with part of the TELNET option negotiation
2138                protocol.  We refuse to do anything beyond the
2139                defaults, except that we allow the WILL ECHO option,
2140                which ICS uses to turn off password echoing when we are
2141                directly connected to it.  We reject this option
2142                if localLineEditing mode is on (always on in xboard)
2143                and we are talking to port 23, which might be a real
2144                telnet server that will try to keep WILL ECHO on permanently.
2145              */
2146             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148                 unsigned char option;
2149                 oldi = i;
2150                 switch ((unsigned char) buf[++i]) {
2151                   case TN_WILL:
2152                     if (appData.debugMode)
2153                       fprintf(debugFP, "\n<WILL ");
2154                     switch (option = (unsigned char) buf[++i]) {
2155                       case TN_ECHO:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "ECHO ");
2158                         /* Reply only if this is a change, according
2159                            to the protocol rules. */
2160                         if (remoteEchoOption) break;
2161                         if (appData.localLineEditing &&
2162                             atoi(appData.icsPort) == TN_PORT) {
2163                             TelnetRequest(TN_DONT, TN_ECHO);
2164                         } else {
2165                             EchoOff();
2166                             TelnetRequest(TN_DO, TN_ECHO);
2167                             remoteEchoOption = TRUE;
2168                         }
2169                         break;
2170                       default:
2171                         if (appData.debugMode)
2172                           fprintf(debugFP, "%d ", option);
2173                         /* Whatever this is, we don't want it. */
2174                         TelnetRequest(TN_DONT, option);
2175                         break;
2176                     }
2177                     break;
2178                   case TN_WONT:
2179                     if (appData.debugMode)
2180                       fprintf(debugFP, "\n<WONT ");
2181                     switch (option = (unsigned char) buf[++i]) {
2182                       case TN_ECHO:
2183                         if (appData.debugMode)
2184                           fprintf(debugFP, "ECHO ");
2185                         /* Reply only if this is a change, according
2186                            to the protocol rules. */
2187                         if (!remoteEchoOption) break;
2188                         EchoOn();
2189                         TelnetRequest(TN_DONT, TN_ECHO);
2190                         remoteEchoOption = FALSE;
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", (unsigned char) option);
2195                         /* Whatever this is, it must already be turned
2196                            off, because we never agree to turn on
2197                            anything non-default, so according to the
2198                            protocol rules, we don't reply. */
2199                         break;
2200                     }
2201                     break;
2202                   case TN_DO:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<DO ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       default:
2207                         /* Whatever this is, we refuse to do it. */
2208                         if (appData.debugMode)
2209                           fprintf(debugFP, "%d ", option);
2210                         TelnetRequest(TN_WONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_DONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<DONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       default:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "%d ", option);
2221                         /* Whatever this is, we are already not doing
2222                            it, because we never agree to do anything
2223                            non-default, so according to the protocol
2224                            rules, we don't reply. */
2225                         break;
2226                     }
2227                     break;
2228                   case TN_IAC:
2229                     if (appData.debugMode)
2230                       fprintf(debugFP, "\n<IAC ");
2231                     /* Doubled IAC; pass it through */
2232                     i--;
2233                     break;
2234                   default:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237                     /* Drop all other telnet commands on the floor */
2238                     break;
2239                 }
2240                 if (oldi > next_out)
2241                   SendToPlayer(&buf[next_out], oldi - next_out);
2242                 if (++i > next_out)
2243                   next_out = i;
2244                 continue;
2245             }
2246                 
2247             /* OK, this at least will *usually* work */
2248             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2249                 loggedOn = TRUE;
2250             }
2251             
2252             if (loggedOn && !intfSet) {
2253                 if (ics_type == ICS_ICC) {
2254                   sprintf(str,
2255                           "/set-quietly interface %s\n/set-quietly style 12\n",
2256                           programVersion);
2257
2258                 } else if (ics_type == ICS_CHESSNET) {
2259                   sprintf(str, "/style 12\n");
2260                 } else {
2261                   strcpy(str, "alias $ @\n$set interface ");
2262                   strcat(str, programVersion);
2263                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2264 #ifdef WIN32
2265                   strcat(str, "$iset nohighlight 1\n");
2266 #endif
2267                   strcat(str, "$iset lock 1\n$style 12\n");
2268                 }
2269                 SendToICS(str);
2270                 intfSet = TRUE;
2271             }
2272
2273             if (started == STARTED_COMMENT) {
2274                 /* Accumulate characters in comment */
2275                 parse[parse_pos++] = buf[i];
2276                 if (buf[i] == '\n') {
2277                     parse[parse_pos] = NULLCHAR;
2278                     if(chattingPartner>=0) {
2279                         char mess[MSG_SIZ];
2280                         sprintf(mess, "%s%s", talker, parse);
2281                         OutputChatMessage(chattingPartner, mess);
2282                         chattingPartner = -1;
2283                     } else
2284                     if(!suppressKibitz) // [HGM] kibitz
2285                         AppendComment(forwardMostMove, StripHighlight(parse));
2286                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287                         int nrDigit = 0, nrAlph = 0, i;
2288                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290                         parse[parse_pos] = NULLCHAR;
2291                         // try to be smart: if it does not look like search info, it should go to
2292                         // ICS interaction window after all, not to engine-output window.
2293                         for(i=0; i<parse_pos; i++) { // count letters and digits
2294                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2296                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2297                         }
2298                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299                             int depth=0; float score;
2300                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302                                 pvInfoList[forwardMostMove-1].depth = depth;
2303                                 pvInfoList[forwardMostMove-1].score = 100*score;
2304                             }
2305                             OutputKibitz(suppressKibitz, parse);
2306                         } else {
2307                             char tmp[MSG_SIZ];
2308                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309                             SendToPlayer(tmp, strlen(tmp));
2310                         }
2311                     }
2312                     started = STARTED_NONE;
2313                 } else {
2314                     /* Don't match patterns against characters in chatter */
2315                     i++;
2316                     continue;
2317                 }
2318             }
2319             if (started == STARTED_CHATTER) {
2320                 if (buf[i] != '\n') {
2321                     /* Don't match patterns against characters in chatter */
2322                     i++;
2323                     continue;
2324                 }
2325                 started = STARTED_NONE;
2326             }
2327
2328             /* Kludge to deal with rcmd protocol */
2329             if (firstTime && looking_at(buf, &i, "\001*")) {
2330                 DisplayFatalError(&buf[1], 0, 1);
2331                 continue;
2332             } else {
2333                 firstTime = FALSE;
2334             }
2335
2336             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2337                 ics_type = ICS_ICC;
2338                 ics_prefix = "/";
2339                 if (appData.debugMode)
2340                   fprintf(debugFP, "ics_type %d\n", ics_type);
2341                 continue;
2342             }
2343             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344                 ics_type = ICS_FICS;
2345                 ics_prefix = "$";
2346                 if (appData.debugMode)
2347                   fprintf(debugFP, "ics_type %d\n", ics_type);
2348                 continue;
2349             }
2350             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351                 ics_type = ICS_CHESSNET;
2352                 ics_prefix = "/";
2353                 if (appData.debugMode)
2354                   fprintf(debugFP, "ics_type %d\n", ics_type);
2355                 continue;
2356             }
2357
2358             if (!loggedOn &&
2359                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2361                  looking_at(buf, &i, "will be \"*\""))) {
2362               strcpy(ics_handle, star_match[0]);
2363               continue;
2364             }
2365
2366             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2367               char buf[MSG_SIZ];
2368               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369               DisplayIcsInteractionTitle(buf);
2370               have_set_title = TRUE;
2371             }
2372
2373             /* skip finger notes */
2374             if (started == STARTED_NONE &&
2375                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376                  (buf[i] == '1' && buf[i+1] == '0')) &&
2377                 buf[i+2] == ':' && buf[i+3] == ' ') {
2378               started = STARTED_CHATTER;
2379               i += 3;
2380               continue;
2381             }
2382
2383             /* skip formula vars */
2384             if (started == STARTED_NONE &&
2385                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386               started = STARTED_CHATTER;
2387               i += 3;
2388               continue;
2389             }
2390
2391             oldi = i;
2392             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393             if (appData.autoKibitz && started == STARTED_NONE && 
2394                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2395                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396                 if(looking_at(buf, &i, "* kibitzes: ") &&
2397                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2398                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2399                         suppressKibitz = TRUE;
2400                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401                                 && (gameMode == IcsPlayingWhite)) ||
2402                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2404                             started = STARTED_CHATTER; // own kibitz we simply discard
2405                         else {
2406                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407                             parse_pos = 0; parse[0] = NULLCHAR;
2408                             savingComment = TRUE;
2409                             suppressKibitz = gameMode != IcsObserving ? 2 :
2410                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2411                         } 
2412                         continue;
2413                 } else
2414                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415                     started = STARTED_CHATTER;
2416                     suppressKibitz = TRUE;
2417                 }
2418             } // [HGM] kibitz: end of patch
2419
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2421
2422             // [HGM] chat: intercept tells by users for which we have an open chat window
2423             channel = -1;
2424             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2425                                            looking_at(buf, &i, "* whispers:") ||
2426                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2428                 int p;
2429                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430                 chattingPartner = -1;
2431
2432                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433                 for(p=0; p<MAX_CHAT; p++) {
2434                     if(channel == atoi(chatPartner[p])) {
2435                     talker[0] = '['; strcat(talker, "]");
2436                     chattingPartner = p; break;
2437                     }
2438                 } else
2439                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440                 for(p=0; p<MAX_CHAT; p++) {
2441                     if(!strcmp("WHISPER", chatPartner[p])) {
2442                         talker[0] = '['; strcat(talker, "]");
2443                         chattingPartner = p; break;
2444                     }
2445                 }
2446                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2448                     talker[0] = 0;
2449                     chattingPartner = p; break;
2450                 }
2451                 if(chattingPartner<0) i = oldi; else {
2452                     started = STARTED_COMMENT;
2453                     parse_pos = 0; parse[0] = NULLCHAR;
2454                     savingComment = TRUE;
2455                     suppressKibitz = TRUE;
2456                 }
2457             } // [HGM] chat: end of patch
2458
2459             if (appData.zippyTalk || appData.zippyPlay) {
2460                 /* [DM] Backup address for color zippy lines */
2461                 backup = i;
2462 #if ZIPPY
2463        #ifdef WIN32
2464                if (loggedOn == TRUE)
2465                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2467        #else
2468                 if (ZippyControl(buf, &i) ||
2469                     ZippyConverse(buf, &i) ||
2470                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2471                       loggedOn = TRUE;
2472                       if (!appData.colorize) continue;
2473                 }
2474        #endif
2475 #endif
2476             } // [DM] 'else { ' deleted
2477                 if (
2478                     /* Regular tells and says */
2479                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2481                     looking_at(buf, &i, "* says: ") ||
2482                     /* Don't color "message" or "messages" output */
2483                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484                     looking_at(buf, &i, "*. * at *:*: ") ||
2485                     looking_at(buf, &i, "--* (*:*): ") ||
2486                     /* Message notifications (same color as tells) */
2487                     looking_at(buf, &i, "* has left a message ") ||
2488                     looking_at(buf, &i, "* just sent you a message:\n") ||
2489                     /* Whispers and kibitzes */
2490                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491                     looking_at(buf, &i, "* kibitzes: ") ||
2492                     /* Channel tells */
2493                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2494
2495                   if (tkind == 1 && strchr(star_match[0], ':')) {
2496                       /* Avoid "tells you:" spoofs in channels */
2497                      tkind = 3;
2498                   }
2499                   if (star_match[0][0] == NULLCHAR ||
2500                       strchr(star_match[0], ' ') ||
2501                       (tkind == 3 && strchr(star_match[1], ' '))) {
2502                     /* Reject bogus matches */
2503                     i = oldi;
2504                   } else {
2505                     if (appData.colorize) {
2506                       if (oldi > next_out) {
2507                         SendToPlayer(&buf[next_out], oldi - next_out);
2508                         next_out = oldi;
2509                       }
2510                       switch (tkind) {
2511                       case 1:
2512                         Colorize(ColorTell, FALSE);
2513                         curColor = ColorTell;
2514                         break;
2515                       case 2:
2516                         Colorize(ColorKibitz, FALSE);
2517                         curColor = ColorKibitz;
2518                         break;
2519                       case 3:
2520                         p = strrchr(star_match[1], '(');
2521                         if (p == NULL) {
2522                           p = star_match[1];
2523                         } else {
2524                           p++;
2525                         }
2526                         if (atoi(p) == 1) {
2527                           Colorize(ColorChannel1, FALSE);
2528                           curColor = ColorChannel1;
2529                         } else {
2530                           Colorize(ColorChannel, FALSE);
2531                           curColor = ColorChannel;
2532                         }
2533                         break;
2534                       case 5:
2535                         curColor = ColorNormal;
2536                         break;
2537                       }
2538                     }
2539                     if (started == STARTED_NONE && appData.autoComment &&
2540                         (gameMode == IcsObserving ||
2541                          gameMode == IcsPlayingWhite ||
2542                          gameMode == IcsPlayingBlack)) {
2543                       parse_pos = i - oldi;
2544                       memcpy(parse, &buf[oldi], parse_pos);
2545                       parse[parse_pos] = NULLCHAR;
2546                       started = STARTED_COMMENT;
2547                       savingComment = TRUE;
2548                     } else {
2549                       started = STARTED_CHATTER;
2550                       savingComment = FALSE;
2551                     }
2552                     loggedOn = TRUE;
2553                     continue;
2554                   }
2555                 }
2556
2557                 if (looking_at(buf, &i, "* s-shouts: ") ||
2558                     looking_at(buf, &i, "* c-shouts: ")) {
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorSShout, FALSE);
2565                         curColor = ColorSShout;
2566                     }
2567                     loggedOn = TRUE;
2568                     started = STARTED_CHATTER;
2569                     continue;
2570                 }
2571
2572                 if (looking_at(buf, &i, "--->")) {
2573                     loggedOn = TRUE;
2574                     continue;
2575                 }
2576
2577                 if (looking_at(buf, &i, "* shouts: ") ||
2578                     looking_at(buf, &i, "--> ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorShout, FALSE);
2585                         curColor = ColorShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at( buf, &i, "Challenge:")) {
2593                     if (appData.colorize) {
2594                         if (oldi > next_out) {
2595                             SendToPlayer(&buf[next_out], oldi - next_out);
2596                             next_out = oldi;
2597                         }
2598                         Colorize(ColorChallenge, FALSE);
2599                         curColor = ColorChallenge;
2600                     }
2601                     loggedOn = TRUE;
2602                     continue;
2603                 }
2604
2605                 if (looking_at(buf, &i, "* offers you") ||
2606                     looking_at(buf, &i, "* offers to be") ||
2607                     looking_at(buf, &i, "* would like to") ||
2608                     looking_at(buf, &i, "* requests to") ||
2609                     looking_at(buf, &i, "Your opponent offers") ||
2610                     looking_at(buf, &i, "Your opponent requests")) {
2611
2612                     if (appData.colorize) {
2613                         if (oldi > next_out) {
2614                             SendToPlayer(&buf[next_out], oldi - next_out);
2615                             next_out = oldi;
2616                         }
2617                         Colorize(ColorRequest, FALSE);
2618                         curColor = ColorRequest;
2619                     }
2620                     continue;
2621                 }
2622
2623                 if (looking_at(buf, &i, "* (*) seeking")) {
2624                     if (appData.colorize) {
2625                         if (oldi > next_out) {
2626                             SendToPlayer(&buf[next_out], oldi - next_out);
2627                             next_out = oldi;
2628                         }
2629                         Colorize(ColorSeek, FALSE);
2630                         curColor = ColorSeek;
2631                     }
2632                     continue;
2633             }
2634
2635             if (looking_at(buf, &i, "\\   ")) {
2636                 if (prevColor != ColorNormal) {
2637                     if (oldi > next_out) {
2638                         SendToPlayer(&buf[next_out], oldi - next_out);
2639                         next_out = oldi;
2640                     }
2641                     Colorize(prevColor, TRUE);
2642                     curColor = prevColor;
2643                 }
2644                 if (savingComment) {
2645                     parse_pos = i - oldi;
2646                     memcpy(parse, &buf[oldi], parse_pos);
2647                     parse[parse_pos] = NULLCHAR;
2648                     started = STARTED_COMMENT;
2649                 } else {
2650                     started = STARTED_CHATTER;
2651                 }
2652                 continue;
2653             }
2654
2655             if (looking_at(buf, &i, "Black Strength :") ||
2656                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657                 looking_at(buf, &i, "<10>") ||
2658                 looking_at(buf, &i, "#@#")) {
2659                 /* Wrong board style */
2660                 loggedOn = TRUE;
2661                 SendToICS(ics_prefix);
2662                 SendToICS("set style 12\n");
2663                 SendToICS(ics_prefix);
2664                 SendToICS("refresh\n");
2665                 continue;
2666             }
2667             
2668             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2669                 ICSInitScript();
2670                 have_sent_ICS_logon = 1;
2671                 continue;
2672             }
2673               
2674             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2675                 (looking_at(buf, &i, "\n<12> ") ||
2676                  looking_at(buf, &i, "<12> "))) {
2677                 loggedOn = TRUE;
2678                 if (oldi > next_out) {
2679                     SendToPlayer(&buf[next_out], oldi - next_out);
2680                 }
2681                 next_out = i;
2682                 started = STARTED_BOARD;
2683                 parse_pos = 0;
2684                 continue;
2685             }
2686
2687             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688                 looking_at(buf, &i, "<b1> ")) {
2689                 if (oldi > next_out) {
2690                     SendToPlayer(&buf[next_out], oldi - next_out);
2691                 }
2692                 next_out = i;
2693                 started = STARTED_HOLDINGS;
2694                 parse_pos = 0;
2695                 continue;
2696             }
2697
2698             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2699                 loggedOn = TRUE;
2700                 /* Header for a move list -- first line */
2701
2702                 switch (ics_getting_history) {
2703                   case H_FALSE:
2704                     switch (gameMode) {
2705                       case IcsIdle:
2706                       case BeginningOfGame:
2707                         /* User typed "moves" or "oldmoves" while we
2708                            were idle.  Pretend we asked for these
2709                            moves and soak them up so user can step
2710                            through them and/or save them.
2711                            */
2712                         Reset(FALSE, TRUE);
2713                         gameMode = IcsObserving;
2714                         ModeHighlight();
2715                         ics_gamenum = -1;
2716                         ics_getting_history = H_GOT_UNREQ_HEADER;
2717                         break;
2718                       case EditGame: /*?*/
2719                       case EditPosition: /*?*/
2720                         /* Should above feature work in these modes too? */
2721                         /* For now it doesn't */
2722                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2723                         break;
2724                       default:
2725                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2726                         break;
2727                     }
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Is this the right one? */
2731                     if (gameInfo.white && gameInfo.black &&
2732                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2733                         strcmp(gameInfo.black, star_match[2]) == 0) {
2734                         /* All is well */
2735                         ics_getting_history = H_GOT_REQ_HEADER;
2736                     }
2737                     break;
2738                   case H_GOT_REQ_HEADER:
2739                   case H_GOT_UNREQ_HEADER:
2740                   case H_GOT_UNWANTED_HEADER:
2741                   case H_GETTING_MOVES:
2742                     /* Should not happen */
2743                     DisplayError(_("Error gathering move list: two headers"), 0);
2744                     ics_getting_history = H_FALSE;
2745                     break;
2746                 }
2747
2748                 /* Save player ratings into gameInfo if needed */
2749                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751                     (gameInfo.whiteRating == -1 ||
2752                      gameInfo.blackRating == -1)) {
2753
2754                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2755                     gameInfo.blackRating = string_to_rating(star_match[3]);
2756                     if (appData.debugMode)
2757                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2758                               gameInfo.whiteRating, gameInfo.blackRating);
2759                 }
2760                 continue;
2761             }
2762
2763             if (looking_at(buf, &i,
2764               "* * match, initial time: * minute*, increment: * second")) {
2765                 /* Header for a move list -- second line */
2766                 /* Initial board will follow if this is a wild game */
2767                 if (gameInfo.event != NULL) free(gameInfo.event);
2768                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769                 gameInfo.event = StrSave(str);
2770                 /* [HGM] we switched variant. Translate boards if needed. */
2771                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2772                 continue;
2773             }
2774
2775             if (looking_at(buf, &i, "Move  ")) {
2776                 /* Beginning of a move list */
2777                 switch (ics_getting_history) {
2778                   case H_FALSE:
2779                     /* Normally should not happen */
2780                     /* Maybe user hit reset while we were parsing */
2781                     break;
2782                   case H_REQUESTED:
2783                     /* Happens if we are ignoring a move list that is not
2784                      * the one we just requested.  Common if the user
2785                      * tries to observe two games without turning off
2786                      * getMoveList */
2787                     break;
2788                   case H_GETTING_MOVES:
2789                     /* Should not happen */
2790                     DisplayError(_("Error gathering move list: nested"), 0);
2791                     ics_getting_history = H_FALSE;
2792                     break;
2793                   case H_GOT_REQ_HEADER:
2794                     ics_getting_history = H_GETTING_MOVES;
2795                     started = STARTED_MOVES;
2796                     parse_pos = 0;
2797                     if (oldi > next_out) {
2798                         SendToPlayer(&buf[next_out], oldi - next_out);
2799                     }
2800                     break;
2801                   case H_GOT_UNREQ_HEADER:
2802                     ics_getting_history = H_GETTING_MOVES;
2803                     started = STARTED_MOVES_NOHIDE;
2804                     parse_pos = 0;
2805                     break;
2806                   case H_GOT_UNWANTED_HEADER:
2807                     ics_getting_history = H_FALSE;
2808                     break;
2809                 }
2810                 continue;
2811             }                           
2812             
2813             if (looking_at(buf, &i, "% ") ||
2814                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816                 savingComment = FALSE;
2817                 switch (started) {
2818                   case STARTED_MOVES:
2819                   case STARTED_MOVES_NOHIDE:
2820                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821                     parse[parse_pos + i - oldi] = NULLCHAR;
2822                     ParseGameHistory(parse);
2823 #if ZIPPY
2824                     if (appData.zippyPlay && first.initDone) {
2825                         FeedMovesToProgram(&first, forwardMostMove);
2826                         if (gameMode == IcsPlayingWhite) {
2827                             if (WhiteOnMove(forwardMostMove)) {
2828                                 if (first.sendTime) {
2829                                   if (first.useColors) {
2830                                     SendToProgram("black\n", &first); 
2831                                   }
2832                                   SendTimeRemaining(&first, TRUE);
2833                                 }
2834 #if 0
2835                                 if (first.useColors) {
2836                                   SendToProgram("white\ngo\n", &first);
2837                                 } else {
2838                                   SendToProgram("go\n", &first);
2839                                 }
2840 #else
2841                                 if (first.useColors) {
2842                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843                                 }
2844                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845 #endif
2846                                 first.maybeThinking = TRUE;
2847                             } else {
2848                                 if (first.usePlayother) {
2849                                   if (first.sendTime) {
2850                                     SendTimeRemaining(&first, TRUE);
2851                                   }
2852                                   SendToProgram("playother\n", &first);
2853                                   firstMove = FALSE;
2854                                 } else {
2855                                   firstMove = TRUE;
2856                                 }
2857                             }
2858                         } else if (gameMode == IcsPlayingBlack) {
2859                             if (!WhiteOnMove(forwardMostMove)) {
2860                                 if (first.sendTime) {
2861                                   if (first.useColors) {
2862                                     SendToProgram("white\n", &first);
2863                                   }
2864                                   SendTimeRemaining(&first, FALSE);
2865                                 }
2866 #if 0
2867                                 if (first.useColors) {
2868                                   SendToProgram("black\ngo\n", &first);
2869                                 } else {
2870                                   SendToProgram("go\n", &first);
2871                                 }
2872 #else
2873                                 if (first.useColors) {
2874                                   SendToProgram("black\n", &first);
2875                                 }
2876                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2877 #endif
2878                                 first.maybeThinking = TRUE;
2879                             } else {
2880                                 if (first.usePlayother) {
2881                                   if (first.sendTime) {
2882                                     SendTimeRemaining(&first, FALSE);
2883                                   }
2884                                   SendToProgram("playother\n", &first);
2885                                   firstMove = FALSE;
2886                                 } else {
2887                                   firstMove = TRUE;
2888                                 }
2889                             }
2890                         }                       
2891                     }
2892 #endif
2893                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2894                         /* Moves came from oldmoves or moves command
2895                            while we weren't doing anything else.
2896                            */
2897                         currentMove = forwardMostMove;
2898                         ClearHighlights();/*!!could figure this out*/
2899                         flipView = appData.flipView;
2900                         DrawPosition(FALSE, boards[currentMove]);
2901                         DisplayBothClocks();
2902                         sprintf(str, "%s vs. %s",
2903                                 gameInfo.white, gameInfo.black);
2904                         DisplayTitle(str);
2905                         gameMode = IcsIdle;
2906                     } else {
2907                         /* Moves were history of an active game */
2908                         if (gameInfo.resultDetails != NULL) {
2909                             free(gameInfo.resultDetails);
2910                             gameInfo.resultDetails = NULL;
2911                         }
2912                     }
2913                     HistorySet(parseList, backwardMostMove,
2914                                forwardMostMove, currentMove-1);
2915                     DisplayMove(currentMove - 1);
2916                     if (started == STARTED_MOVES) next_out = i;
2917                     started = STARTED_NONE;
2918                     ics_getting_history = H_FALSE;
2919                     break;
2920
2921                   case STARTED_OBSERVE:
2922                     started = STARTED_NONE;
2923                     SendToICS(ics_prefix);
2924                     SendToICS("refresh\n");
2925                     break;
2926
2927                   default:
2928                     break;
2929                 }
2930                 if(bookHit) { // [HGM] book: simulate book reply
2931                     static char bookMove[MSG_SIZ]; // a bit generous?
2932
2933                     programStats.nodes = programStats.depth = programStats.time = 
2934                     programStats.score = programStats.got_only_move = 0;
2935                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2936
2937                     strcpy(bookMove, "move ");
2938                     strcat(bookMove, bookHit);
2939                     HandleMachineMove(bookMove, &first);
2940                 }
2941                 continue;
2942             }
2943             
2944             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945                  started == STARTED_HOLDINGS ||
2946                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947                 /* Accumulate characters in move list or board */
2948                 parse[parse_pos++] = buf[i];
2949             }
2950             
2951             /* Start of game messages.  Mostly we detect start of game
2952                when the first board image arrives.  On some versions
2953                of the ICS, though, we need to do a "refresh" after starting
2954                to observe in order to get the current board right away. */
2955             if (looking_at(buf, &i, "Adding game * to observation list")) {
2956                 started = STARTED_OBSERVE;
2957                 continue;
2958             }
2959
2960             /* Handle auto-observe */
2961             if (appData.autoObserve &&
2962                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2964                 char *player;
2965                 /* Choose the player that was highlighted, if any. */
2966                 if (star_match[0][0] == '\033' ||
2967                     star_match[1][0] != '\033') {
2968                     player = star_match[0];
2969                 } else {
2970                     player = star_match[2];
2971                 }
2972                 sprintf(str, "%sobserve %s\n",
2973                         ics_prefix, StripHighlightAndTitle(player));
2974                 SendToICS(str);
2975
2976                 /* Save ratings from notify string */
2977                 strcpy(player1Name, star_match[0]);
2978                 player1Rating = string_to_rating(star_match[1]);
2979                 strcpy(player2Name, star_match[2]);
2980                 player2Rating = string_to_rating(star_match[3]);
2981
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, 
2984                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2985                           player1Name, player1Rating,
2986                           player2Name, player2Rating);
2987
2988                 continue;
2989             }
2990
2991             /* Deal with automatic examine mode after a game,
2992                and with IcsObserving -> IcsExamining transition */
2993             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994                 looking_at(buf, &i, "has made you an examiner of game *")) {
2995
2996                 int gamenum = atoi(star_match[0]);
2997                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998                     gamenum == ics_gamenum) {
2999                     /* We were already playing or observing this game;
3000                        no need to refetch history */
3001                     gameMode = IcsExamining;
3002                     if (pausing) {
3003                         pauseExamForwardMostMove = forwardMostMove;
3004                     } else if (currentMove < forwardMostMove) {
3005                         ForwardInner(forwardMostMove);
3006                     }
3007                 } else {
3008                     /* I don't think this case really can happen */
3009                     SendToICS(ics_prefix);
3010                     SendToICS("refresh\n");
3011                 }
3012                 continue;
3013             }    
3014             
3015             /* Error messages */
3016 //          if (ics_user_moved) {
3017             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018                 if (looking_at(buf, &i, "Illegal move") ||
3019                     looking_at(buf, &i, "Not a legal move") ||
3020                     looking_at(buf, &i, "Your king is in check") ||
3021                     looking_at(buf, &i, "It isn't your turn") ||
3022                     looking_at(buf, &i, "It is not your move")) {
3023                     /* Illegal move */
3024                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025                         currentMove = --forwardMostMove;
3026                         DisplayMove(currentMove - 1); /* before DMError */
3027                         DrawPosition(FALSE, boards[currentMove]);
3028                         SwitchClocks();
3029                         DisplayBothClocks();
3030                     }
3031                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3032                     ics_user_moved = 0;
3033                     continue;
3034                 }
3035             }
3036
3037             if (looking_at(buf, &i, "still have time") ||
3038                 looking_at(buf, &i, "not out of time") ||
3039                 looking_at(buf, &i, "either player is out of time") ||
3040                 looking_at(buf, &i, "has timeseal; checking")) {
3041                 /* We must have called his flag a little too soon */
3042                 whiteFlag = blackFlag = FALSE;
3043                 continue;
3044             }
3045
3046             if (looking_at(buf, &i, "added * seconds to") ||
3047                 looking_at(buf, &i, "seconds were added to")) {
3048                 /* Update the clocks */
3049                 SendToICS(ics_prefix);
3050                 SendToICS("refresh\n");
3051                 continue;
3052             }
3053
3054             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055                 ics_clock_paused = TRUE;
3056                 StopClocks();
3057                 continue;
3058             }
3059
3060             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061                 ics_clock_paused = FALSE;
3062                 StartClocks();
3063                 continue;
3064             }
3065
3066             /* Grab player ratings from the Creating: message.
3067                Note we have to check for the special case when
3068                the ICS inserts things like [white] or [black]. */
3069             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3071                 /* star_matches:
3072                    0    player 1 name (not necessarily white)
3073                    1    player 1 rating
3074                    2    empty, white, or black (IGNORED)
3075                    3    player 2 name (not necessarily black)
3076                    4    player 2 rating
3077                    
3078                    The names/ratings are sorted out when the game
3079                    actually starts (below).
3080                 */
3081                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082                 player1Rating = string_to_rating(star_match[1]);
3083                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084                 player2Rating = string_to_rating(star_match[4]);
3085
3086                 if (appData.debugMode)
3087                   fprintf(debugFP, 
3088                           "Ratings from 'Creating:' %s %d, %s %d\n",
3089                           player1Name, player1Rating,
3090                           player2Name, player2Rating);
3091
3092                 continue;
3093             }
3094             
3095             /* Improved generic start/end-of-game messages */
3096             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098                 /* If tkind == 0: */
3099                 /* star_match[0] is the game number */
3100                 /*           [1] is the white player's name */
3101                 /*           [2] is the black player's name */
3102                 /* For end-of-game: */
3103                 /*           [3] is the reason for the game end */
3104                 /*           [4] is a PGN end game-token, preceded by " " */
3105                 /* For start-of-game: */
3106                 /*           [3] begins with "Creating" or "Continuing" */
3107                 /*           [4] is " *" or empty (don't care). */
3108                 int gamenum = atoi(star_match[0]);
3109                 char *whitename, *blackname, *why, *endtoken;
3110                 ChessMove endtype = (ChessMove) 0;
3111
3112                 if (tkind == 0) {
3113                   whitename = star_match[1];
3114                   blackname = star_match[2];
3115                   why = star_match[3];
3116                   endtoken = star_match[4];
3117                 } else {
3118                   whitename = star_match[1];
3119                   blackname = star_match[3];
3120                   why = star_match[5];
3121                   endtoken = star_match[6];
3122                 }
3123
3124                 /* Game start messages */
3125                 if (strncmp(why, "Creating ", 9) == 0 ||
3126                     strncmp(why, "Continuing ", 11) == 0) {
3127                     gs_gamenum = gamenum;
3128                     strcpy(gs_kind, strchr(why, ' ') + 1);
3129 #if ZIPPY
3130                     if (appData.zippyPlay) {
3131                         ZippyGameStart(whitename, blackname);
3132                     }
3133 #endif /*ZIPPY*/
3134                     continue;
3135                 }
3136
3137                 /* Game end messages */
3138                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139                     ics_gamenum != gamenum) {
3140                     continue;
3141                 }
3142                 while (endtoken[0] == ' ') endtoken++;
3143                 switch (endtoken[0]) {
3144                   case '*':
3145                   default:
3146                     endtype = GameUnfinished;
3147                     break;
3148                   case '0':
3149                     endtype = BlackWins;
3150                     break;
3151                   case '1':
3152                     if (endtoken[1] == '/')
3153                       endtype = GameIsDrawn;
3154                     else
3155                       endtype = WhiteWins;
3156                     break;
3157                 }
3158                 GameEnds(endtype, why, GE_ICS);
3159 #if ZIPPY
3160                 if (appData.zippyPlay && first.initDone) {
3161                     ZippyGameEnd(endtype, why);
3162                     if (first.pr == NULL) {
3163                       /* Start the next process early so that we'll
3164                          be ready for the next challenge */
3165                       StartChessProgram(&first);
3166                     }
3167                     /* Send "new" early, in case this command takes
3168                        a long time to finish, so that we'll be ready
3169                        for the next challenge. */
3170                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3171                     Reset(TRUE, TRUE);
3172                 }
3173 #endif /*ZIPPY*/
3174                 continue;
3175             }
3176
3177             if (looking_at(buf, &i, "Removing game * from observation") ||
3178                 looking_at(buf, &i, "no longer observing game *") ||
3179                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180                 if (gameMode == IcsObserving &&
3181                     atoi(star_match[0]) == ics_gamenum)
3182                   {
3183                       /* icsEngineAnalyze */
3184                       if (appData.icsEngineAnalyze) {
3185                             ExitAnalyzeMode();
3186                             ModeHighlight();
3187                       }
3188                       StopClocks();
3189                       gameMode = IcsIdle;
3190                       ics_gamenum = -1;
3191                       ics_user_moved = FALSE;
3192                   }
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "no longer examining game *")) {
3197                 if (gameMode == IcsExamining &&
3198                     atoi(star_match[0]) == ics_gamenum)
3199                   {
3200                       gameMode = IcsIdle;
3201                       ics_gamenum = -1;
3202                       ics_user_moved = FALSE;
3203                   }
3204                 continue;
3205             }
3206
3207             /* Advance leftover_start past any newlines we find,
3208                so only partial lines can get reparsed */
3209             if (looking_at(buf, &i, "\n")) {
3210                 prevColor = curColor;
3211                 if (curColor != ColorNormal) {
3212                     if (oldi > next_out) {
3213                         SendToPlayer(&buf[next_out], oldi - next_out);
3214                         next_out = oldi;
3215                     }
3216                     Colorize(ColorNormal, FALSE);
3217                     curColor = ColorNormal;
3218                 }
3219                 if (started == STARTED_BOARD) {
3220                     started = STARTED_NONE;
3221                     parse[parse_pos] = NULLCHAR;
3222                     ParseBoard12(parse);
3223                     ics_user_moved = 0;
3224
3225                     /* Send premove here */
3226                     if (appData.premove) {
3227                       char str[MSG_SIZ];
3228                       if (currentMove == 0 &&
3229                           gameMode == IcsPlayingWhite &&
3230                           appData.premoveWhite) {
3231                         sprintf(str, "%s%s\n", ics_prefix,
3232                                 appData.premoveWhiteText);
3233                         if (appData.debugMode)
3234                           fprintf(debugFP, "Sending premove:\n");
3235                         SendToICS(str);
3236                       } else if (currentMove == 1 &&
3237                                  gameMode == IcsPlayingBlack &&
3238                                  appData.premoveBlack) {
3239                         sprintf(str, "%s%s\n", ics_prefix,
3240                                 appData.premoveBlackText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (gotPremove) {
3245                         gotPremove = 0;
3246                         ClearPremoveHighlights();
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                           UserMoveEvent(premoveFromX, premoveFromY, 
3250                                         premoveToX, premoveToY, 
3251                                         premovePromoChar);
3252                       }
3253                     }
3254
3255                     /* Usually suppress following prompt */
3256                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257                         if (looking_at(buf, &i, "*% ")) {
3258                             savingComment = FALSE;
3259                         }
3260                     }
3261                     next_out = i;
3262                 } else if (started == STARTED_HOLDINGS) {
3263                     int gamenum;
3264                     char new_piece[MSG_SIZ];
3265                     started = STARTED_NONE;
3266                     parse[parse_pos] = NULLCHAR;
3267                     if (appData.debugMode)
3268                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269                                                         parse, currentMove);
3270                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271                         gamenum == ics_gamenum) {
3272                         if (gameInfo.variant == VariantNormal) {
3273                           /* [HGM] We seem to switch variant during a game!
3274                            * Presumably no holdings were displayed, so we have
3275                            * to move the position two files to the right to
3276                            * create room for them!
3277                            */
3278                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279                           /* Get a move list just to see the header, which
3280                              will tell us whether this is really bug or zh */
3281                           if (ics_getting_history == H_FALSE) {
3282                             ics_getting_history = H_REQUESTED;
3283                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3284                             SendToICS(str);
3285                           }
3286                         }
3287                         new_piece[0] = NULLCHAR;
3288                         sscanf(parse, "game %d white [%s black [%s <- %s",
3289                                &gamenum, white_holding, black_holding,
3290                                new_piece);
3291                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3292                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3293                         /* [HGM] copy holdings to board holdings area */
3294                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3296 #if ZIPPY
3297                         if (appData.zippyPlay && first.initDone) {
3298                             ZippyHoldings(white_holding, black_holding,
3299                                           new_piece);
3300                         }
3301 #endif /*ZIPPY*/
3302                         if (tinyLayout || smallLayout) {
3303                             char wh[16], bh[16];
3304                             PackHolding(wh, white_holding);
3305                             PackHolding(bh, black_holding);
3306                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307                                     gameInfo.white, gameInfo.black);
3308                         } else {
3309                             sprintf(str, "%s [%s] vs. %s [%s]",
3310                                     gameInfo.white, white_holding,
3311                                     gameInfo.black, black_holding);
3312                         }
3313
3314                         DrawPosition(FALSE, boards[currentMove]);
3315                         DisplayTitle(str);
3316                     }
3317                     /* Suppress following prompt */
3318                     if (looking_at(buf, &i, "*% ")) {
3319                         savingComment = FALSE;
3320                     }
3321                     next_out = i;
3322                 }
3323                 continue;
3324             }
3325
3326             i++;                /* skip unparsed character and loop back */
3327         }
3328         
3329         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330             started != STARTED_HOLDINGS && i > next_out) {
3331             SendToPlayer(&buf[next_out], i - next_out);
3332             next_out = i;
3333         }
3334         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3335         
3336         leftover_len = buf_len - leftover_start;
3337         /* if buffer ends with something we couldn't parse,
3338            reparse it after appending the next read */
3339         
3340     } else if (count == 0) {
3341         RemoveInputSource(isr);
3342         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3343     } else {
3344         DisplayFatalError(_("Error reading from ICS"), error, 1);
3345     }
3346 }
3347
3348
3349 /* Board style 12 looks like this:
3350    
3351    <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
3352    
3353  * The "<12> " is stripped before it gets to this routine.  The two
3354  * trailing 0's (flip state and clock ticking) are later addition, and
3355  * some chess servers may not have them, or may have only the first.
3356  * Additional trailing fields may be added in the future.  
3357  */
3358
3359 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3360
3361 #define RELATION_OBSERVING_PLAYED    0
3362 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE      1
3364 #define RELATION_PLAYING_NOTMYMOVE  -1
3365 #define RELATION_EXAMINING           2
3366 #define RELATION_ISOLATED_BOARD     -3
3367 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3368
3369 void
3370 ParseBoard12(string)
3371      char *string;
3372
3373     GameMode newGameMode;
3374     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377     char to_play, board_chars[200];
3378     char move_str[500], str[500], elapsed_time[500];
3379     char black[32], white[32];
3380     Board board;
3381     int prevMove = currentMove;
3382     int ticking = 2;
3383     ChessMove moveType;
3384     int fromX, fromY, toX, toY;
3385     char promoChar;
3386     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387     char *bookHit = NULL; // [HGM] book
3388
3389     fromX = fromY = toX = toY = -1;
3390     
3391     newGame = FALSE;
3392
3393     if (appData.debugMode)
3394       fprintf(debugFP, _("Parsing board: %s\n"), string);
3395
3396     move_str[0] = NULLCHAR;
3397     elapsed_time[0] = NULLCHAR;
3398     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3399         int  i = 0, j;
3400         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401             if(string[i] == ' ') { ranks++; files = 0; }
3402             else files++;
3403             i++;
3404         }
3405         for(j = 0; j <i; j++) board_chars[j] = string[j];
3406         board_chars[i] = '\0';
3407         string += i + 1;
3408     }
3409     n = sscanf(string, PATTERN, &to_play, &double_push,
3410                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411                &gamenum, white, black, &relation, &basetime, &increment,
3412                &white_stren, &black_stren, &white_time, &black_time,
3413                &moveNum, str, elapsed_time, move_str, &ics_flip,
3414                &ticking);
3415
3416     if (n < 21) {
3417         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418         DisplayError(str, 0);
3419         return;
3420     }
3421
3422     /* Convert the move number to internal form */
3423     moveNum = (moveNum - 1) * 2;
3424     if (to_play == 'B') moveNum++;
3425     if (moveNum >= MAX_MOVES) {
3426       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3427                         0, 1);
3428       return;
3429     }
3430     
3431     switch (relation) {
3432       case RELATION_OBSERVING_PLAYED:
3433       case RELATION_OBSERVING_STATIC:
3434         if (gamenum == -1) {
3435             /* Old ICC buglet */
3436             relation = RELATION_OBSERVING_STATIC;
3437         }
3438         newGameMode = IcsObserving;
3439         break;
3440       case RELATION_PLAYING_MYMOVE:
3441       case RELATION_PLAYING_NOTMYMOVE:
3442         newGameMode =
3443           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444             IcsPlayingWhite : IcsPlayingBlack;
3445         break;
3446       case RELATION_EXAMINING:
3447         newGameMode = IcsExamining;
3448         break;
3449       case RELATION_ISOLATED_BOARD:
3450       default:
3451         /* Just display this board.  If user was doing something else,
3452            we will forget about it until the next board comes. */ 
3453         newGameMode = IcsIdle;
3454         break;
3455       case RELATION_STARTING_POSITION:
3456         newGameMode = gameMode;
3457         break;
3458     }
3459     
3460     /* Modify behavior for initial board display on move listing
3461        of wild games.
3462        */
3463     switch (ics_getting_history) {
3464       case H_FALSE:
3465       case H_REQUESTED:
3466         break;
3467       case H_GOT_REQ_HEADER:
3468       case H_GOT_UNREQ_HEADER:
3469         /* This is the initial position of the current game */
3470         gamenum = ics_gamenum;
3471         moveNum = 0;            /* old ICS bug workaround */
3472         if (to_play == 'B') {
3473           startedFromSetupPosition = TRUE;
3474           blackPlaysFirst = TRUE;
3475           moveNum = 1;
3476           if (forwardMostMove == 0) forwardMostMove = 1;
3477           if (backwardMostMove == 0) backwardMostMove = 1;
3478           if (currentMove == 0) currentMove = 1;
3479         }
3480         newGameMode = gameMode;
3481         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3482         break;
3483       case H_GOT_UNWANTED_HEADER:
3484         /* This is an initial board that we don't want */
3485         return;
3486       case H_GETTING_MOVES:
3487         /* Should not happen */
3488         DisplayError(_("Error gathering move list: extra board"), 0);
3489         ics_getting_history = H_FALSE;
3490         return;
3491     }
3492     
3493     /* Take action if this is the first board of a new game, or of a
3494        different game than is currently being displayed.  */
3495     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496         relation == RELATION_ISOLATED_BOARD) {
3497         
3498         /* Forget the old game and get the history (if any) of the new one */
3499         if (gameMode != BeginningOfGame) {
3500           Reset(FALSE, TRUE);
3501         }
3502         newGame = TRUE;
3503         if (appData.autoRaiseBoard) BoardToTop();
3504         prevMove = -3;
3505         if (gamenum == -1) {
3506             newGameMode = IcsIdle;
3507         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508                    appData.getMoveList) {
3509             /* Need to get game history */
3510             ics_getting_history = H_REQUESTED;
3511             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3512             SendToICS(str);
3513         }
3514         
3515         /* Initially flip the board to have black on the bottom if playing
3516            black or if the ICS flip flag is set, but let the user change
3517            it with the Flip View button. */
3518         flipView = appData.autoFlipView ? 
3519           (newGameMode == IcsPlayingBlack) || ics_flip :
3520           appData.flipView;
3521         
3522         /* Done with values from previous mode; copy in new ones */
3523         gameMode = newGameMode;
3524         ModeHighlight();
3525         ics_gamenum = gamenum;
3526         if (gamenum == gs_gamenum) {
3527             int klen = strlen(gs_kind);
3528             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529             sprintf(str, "ICS %s", gs_kind);
3530             gameInfo.event = StrSave(str);
3531         } else {
3532             gameInfo.event = StrSave("ICS game");
3533         }
3534         gameInfo.site = StrSave(appData.icsHost);
3535         gameInfo.date = PGNDate();
3536         gameInfo.round = StrSave("-");
3537         gameInfo.white = StrSave(white);
3538         gameInfo.black = StrSave(black);
3539         timeControl = basetime * 60 * 1000;
3540         timeControl_2 = 0;
3541         timeIncrement = increment * 1000;
3542         movesPerSession = 0;
3543         gameInfo.timeControl = TimeControlTagValue();
3544         VariantSwitch(board, StringToVariant(gameInfo.event) );
3545   if (appData.debugMode) {
3546     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548     setbuf(debugFP, NULL);
3549   }
3550
3551         gameInfo.outOfBook = NULL;
3552         
3553         /* Do we have the ratings? */
3554         if (strcmp(player1Name, white) == 0 &&
3555             strcmp(player2Name, black) == 0) {
3556             if (appData.debugMode)
3557               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558                       player1Rating, player2Rating);
3559             gameInfo.whiteRating = player1Rating;
3560             gameInfo.blackRating = player2Rating;
3561         } else if (strcmp(player2Name, white) == 0 &&
3562                    strcmp(player1Name, black) == 0) {
3563             if (appData.debugMode)
3564               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565                       player2Rating, player1Rating);
3566             gameInfo.whiteRating = player2Rating;
3567             gameInfo.blackRating = player1Rating;
3568         }
3569         player1Name[0] = player2Name[0] = NULLCHAR;
3570
3571         /* Silence shouts if requested */
3572         if (appData.quietPlay &&
3573             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574             SendToICS(ics_prefix);
3575             SendToICS("set shout 0\n");
3576         }
3577     }
3578     
3579     /* Deal with midgame name changes */
3580     if (!newGame) {
3581         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582             if (gameInfo.white) free(gameInfo.white);
3583             gameInfo.white = StrSave(white);
3584         }
3585         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586             if (gameInfo.black) free(gameInfo.black);
3587             gameInfo.black = StrSave(black);
3588         }
3589     }
3590     
3591     /* Throw away game result if anything actually changes in examine mode */
3592     if (gameMode == IcsExamining && !newGame) {
3593         gameInfo.result = GameUnfinished;
3594         if (gameInfo.resultDetails != NULL) {
3595             free(gameInfo.resultDetails);
3596             gameInfo.resultDetails = NULL;
3597         }
3598     }
3599     
3600     /* In pausing && IcsExamining mode, we ignore boards coming
3601        in if they are in a different variation than we are. */
3602     if (pauseExamInvalid) return;
3603     if (pausing && gameMode == IcsExamining) {
3604         if (moveNum <= pauseExamForwardMostMove) {
3605             pauseExamInvalid = TRUE;
3606             forwardMostMove = pauseExamForwardMostMove;
3607             return;
3608         }
3609     }
3610     
3611   if (appData.debugMode) {
3612     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3613   }
3614     /* Parse the board */
3615     for (k = 0; k < ranks; k++) {
3616       for (j = 0; j < files; j++)
3617         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618       if(gameInfo.holdingsWidth > 1) {
3619            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3621       }
3622     }
3623     CopyBoard(boards[moveNum], board);
3624     if (moveNum == 0) {
3625         startedFromSetupPosition =
3626           !CompareBoards(board, initialPosition);
3627         if(startedFromSetupPosition)
3628             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3629     }
3630
3631     /* [HGM] Set castling rights. Take the outermost Rooks,
3632        to make it also work for FRC opening positions. Note that board12
3633        is really defective for later FRC positions, as it has no way to
3634        indicate which Rook can castle if they are on the same side of King.
3635        For the initial position we grant rights to the outermost Rooks,
3636        and remember thos rights, and we then copy them on positions
3637        later in an FRC game. This means WB might not recognize castlings with
3638        Rooks that have moved back to their original position as illegal,
3639        but in ICS mode that is not its job anyway.
3640     */
3641     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3643
3644         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645             if(board[0][i] == WhiteRook) j = i;
3646         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648             if(board[0][i] == WhiteRook) j = i;
3649         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656
3657         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661             if(board[BOARD_HEIGHT-1][k] == bKing)
3662                 initialRights[5] = castlingRights[moveNum][5] = k;
3663     } else { int r;
3664         r = castlingRights[moveNum][0] = initialRights[0];
3665         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666         r = castlingRights[moveNum][1] = initialRights[1];
3667         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668         r = castlingRights[moveNum][3] = initialRights[3];
3669         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670         r = castlingRights[moveNum][4] = initialRights[4];
3671         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672         /* wildcastle kludge: always assume King has rights */
3673         r = castlingRights[moveNum][2] = initialRights[2];
3674         r = castlingRights[moveNum][5] = initialRights[5];
3675     }
3676     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3678
3679     
3680     if (ics_getting_history == H_GOT_REQ_HEADER ||
3681         ics_getting_history == H_GOT_UNREQ_HEADER) {
3682         /* This was an initial position from a move list, not
3683            the current position */
3684         return;
3685     }
3686     
3687     /* Update currentMove and known move number limits */
3688     newMove = newGame || moveNum > forwardMostMove;
3689
3690     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692         takeback = forwardMostMove - moveNum;
3693         for (i = 0; i < takeback; i++) {
3694              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695              SendToProgram("undo\n", &first);
3696         }
3697     }
3698
3699     if (newGame) {
3700         forwardMostMove = backwardMostMove = currentMove = moveNum;
3701         if (gameMode == IcsExamining && moveNum == 0) {
3702           /* Workaround for ICS limitation: we are not told the wild
3703              type when starting to examine a game.  But if we ask for
3704              the move list, the move list header will tell us */
3705             ics_getting_history = H_REQUESTED;
3706             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3707             SendToICS(str);
3708         }
3709     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711         forwardMostMove = moveNum;
3712         if (!pausing || currentMove > forwardMostMove)
3713           currentMove = forwardMostMove;
3714     } else {
3715         /* New part of history that is not contiguous with old part */ 
3716         if (pausing && gameMode == IcsExamining) {
3717             pauseExamInvalid = TRUE;
3718             forwardMostMove = pauseExamForwardMostMove;
3719             return;
3720         }
3721         forwardMostMove = backwardMostMove = currentMove = moveNum;
3722         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723             ics_getting_history = H_REQUESTED;
3724             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3725             SendToICS(str);
3726         }
3727     }
3728     
3729     /* Update the clocks */
3730     if (strchr(elapsed_time, '.')) {
3731       /* Time is in ms */
3732       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3734     } else {
3735       /* Time is in seconds */
3736       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3738     }
3739       
3740
3741 #if ZIPPY
3742     if (appData.zippyPlay && newGame &&
3743         gameMode != IcsObserving && gameMode != IcsIdle &&
3744         gameMode != IcsExamining)
3745       ZippyFirstBoard(moveNum, basetime, increment);
3746 #endif
3747     
3748     /* Put the move on the move list, first converting
3749        to canonical algebraic form. */
3750     if (moveNum > 0) {
3751   if (appData.debugMode) {
3752     if (appData.debugMode) { int f = forwardMostMove;
3753         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3755     }
3756     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757     fprintf(debugFP, "moveNum = %d\n", moveNum);
3758     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759     setbuf(debugFP, NULL);
3760   }
3761         if (moveNum <= backwardMostMove) {
3762             /* We don't know what the board looked like before
3763                this move.  Punt. */
3764             strcpy(parseList[moveNum - 1], move_str);
3765             strcat(parseList[moveNum - 1], " ");
3766             strcat(parseList[moveNum - 1], elapsed_time);
3767             moveList[moveNum - 1][0] = NULLCHAR;
3768         } else if (strcmp(move_str, "none") == 0) {
3769             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770             /* Again, we don't know what the board looked like;
3771                this is really the start of the game. */
3772             parseList[moveNum - 1][0] = NULLCHAR;
3773             moveList[moveNum - 1][0] = NULLCHAR;
3774             backwardMostMove = moveNum;
3775             startedFromSetupPosition = TRUE;
3776             fromX = fromY = toX = toY = -1;
3777         } else {
3778           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3779           //                 So we parse the long-algebraic move string in stead of the SAN move
3780           int valid; char buf[MSG_SIZ], *prom;
3781
3782           // str looks something like "Q/a1-a2"; kill the slash
3783           if(str[1] == '/') 
3784                 sprintf(buf, "%c%s", str[0], str+2);
3785           else  strcpy(buf, str); // might be castling
3786           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3787                 strcat(buf, prom); // long move lacks promo specification!
3788           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789                 if(appData.debugMode) 
3790                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791                 strcpy(move_str, buf);
3792           }
3793           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794                                 &fromX, &fromY, &toX, &toY, &promoChar)
3795                || ParseOneMove(buf, moveNum - 1, &moveType,
3796                                 &fromX, &fromY, &toX, &toY, &promoChar);
3797           // end of long SAN patch
3798           if (valid) {
3799             (void) CoordsToAlgebraic(boards[moveNum - 1],
3800                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3801                                      fromY, fromX, toY, toX, promoChar,
3802                                      parseList[moveNum-1]);
3803             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804                              castlingRights[moveNum]) ) {
3805               case MT_NONE:
3806               case MT_STALEMATE:
3807               default:
3808                 break;
3809               case MT_CHECK:
3810                 if(gameInfo.variant != VariantShogi)
3811                     strcat(parseList[moveNum - 1], "+");
3812                 break;
3813               case MT_CHECKMATE:
3814               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815                 strcat(parseList[moveNum - 1], "#");
3816                 break;
3817             }
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             /* currentMoveString is set as a side-effect of ParseOneMove */
3821             strcpy(moveList[moveNum - 1], currentMoveString);
3822             strcat(moveList[moveNum - 1], "\n");
3823           } else {
3824             /* Move from ICS was illegal!?  Punt. */
3825   if (appData.debugMode) {
3826     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3828   }
3829 #if 0
3830             if (appData.testLegality && appData.debugMode) {
3831                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832                 DisplayError(str, 0);
3833             }
3834 #endif
3835             strcpy(parseList[moveNum - 1], move_str);
3836             strcat(parseList[moveNum - 1], " ");
3837             strcat(parseList[moveNum - 1], elapsed_time);
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             fromX = fromY = toX = toY = -1;
3840           }
3841         }
3842   if (appData.debugMode) {
3843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844     setbuf(debugFP, NULL);
3845   }
3846
3847 #if ZIPPY
3848         /* Send move to chess program (BEFORE animating it). */
3849         if (appData.zippyPlay && !newGame && newMove && 
3850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851
3852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856                             move_str);
3857                     DisplayError(str, 0);
3858                 } else {
3859                     if (first.sendTime) {
3860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861                     }
3862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863                     if (firstMove && !bookHit) {
3864                         firstMove = FALSE;
3865                         if (first.useColors) {
3866                           SendToProgram(gameMode == IcsPlayingWhite ?
3867                                         "white\ngo\n" :
3868                                         "black\ngo\n", &first);
3869                         } else {
3870                           SendToProgram("go\n", &first);
3871                         }
3872                         first.maybeThinking = TRUE;
3873                     }
3874                 }
3875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876               if (moveList[moveNum - 1][0] == NULLCHAR) {
3877                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878                 DisplayError(str, 0);
3879               } else {
3880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881                 SendMoveToProgram(moveNum - 1, &first);
3882               }
3883             }
3884         }
3885 #endif
3886     }
3887
3888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889         /* If move comes from a remote source, animate it.  If it
3890            isn't remote, it will have already been animated. */
3891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893         }
3894         if (!pausing && appData.highlightLastMove) {
3895             SetHighlights(fromX, fromY, toX, toY);
3896         }
3897     }
3898     
3899     /* Start the clocks */
3900     whiteFlag = blackFlag = FALSE;
3901     appData.clockMode = !(basetime == 0 && increment == 0);
3902     if (ticking == 0) {
3903       ics_clock_paused = TRUE;
3904       StopClocks();
3905     } else if (ticking == 1) {
3906       ics_clock_paused = FALSE;
3907     }
3908     if (gameMode == IcsIdle ||
3909         relation == RELATION_OBSERVING_STATIC ||
3910         relation == RELATION_EXAMINING ||
3911         ics_clock_paused)
3912       DisplayBothClocks();
3913     else
3914       StartClocks();
3915     
3916     /* Display opponents and material strengths */
3917     if (gameInfo.variant != VariantBughouse &&
3918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919         if (tinyLayout || smallLayout) {
3920             if(gameInfo.variant == VariantNormal)
3921                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3923                     basetime, increment);
3924             else
3925                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3927                     basetime, increment, (int) gameInfo.variant);
3928         } else {
3929             if(gameInfo.variant == VariantNormal)
3930                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3931                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3932                     basetime, increment);
3933             else
3934                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3935                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3936                     basetime, increment, VariantName(gameInfo.variant));
3937         }
3938         DisplayTitle(str);
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3941   }
3942     }
3943
3944    
3945     /* Display the board */
3946     if (!pausing && !appData.noGUI) {
3947       
3948       if (appData.premove)
3949           if (!gotPremove || 
3950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952               ClearPremoveHighlights();
3953
3954       DrawPosition(FALSE, boards[currentMove]);
3955       DisplayMove(moveNum - 1);
3956       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3959         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3960       }
3961     }
3962
3963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 #if ZIPPY
3965     if(bookHit) { // [HGM] book: simulate book reply
3966         static char bookMove[MSG_SIZ]; // a bit generous?
3967
3968         programStats.nodes = programStats.depth = programStats.time = 
3969         programStats.score = programStats.got_only_move = 0;
3970         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971
3972         strcpy(bookMove, "move ");
3973         strcat(bookMove, bookHit);
3974         HandleMachineMove(bookMove, &first);
3975     }
3976 #endif
3977 }
3978
3979 void
3980 GetMoveListEvent()
3981 {
3982     char buf[MSG_SIZ];
3983     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984         ics_getting_history = H_REQUESTED;
3985         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3986         SendToICS(buf);
3987     }
3988 }
3989
3990 void
3991 AnalysisPeriodicEvent(force)
3992      int force;
3993 {
3994     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995          && !force) || !appData.periodicUpdates)
3996       return;
3997
3998     /* Send . command to Crafty to collect stats */
3999     SendToProgram(".\n", &first);
4000
4001     /* Don't send another until we get a response (this makes
4002        us stop sending to old Crafty's which don't understand
4003        the "." command (sending illegal cmds resets node count & time,
4004        which looks bad)) */
4005     programStats.ok_to_send = 0;
4006 }
4007
4008 void
4009 SendMoveToProgram(moveNum, cps)
4010      int moveNum;
4011      ChessProgramState *cps;
4012 {
4013     char buf[MSG_SIZ];
4014
4015     if (cps->useUsermove) {
4016       SendToProgram("usermove ", cps);
4017     }
4018     if (cps->useSAN) {
4019       char *space;
4020       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021         int len = space - parseList[moveNum];
4022         memcpy(buf, parseList[moveNum], len);
4023         buf[len++] = '\n';
4024         buf[len] = NULLCHAR;
4025       } else {
4026         sprintf(buf, "%s\n", parseList[moveNum]);
4027       }
4028       SendToProgram(buf, cps);
4029     } else {
4030       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031         AlphaRank(moveList[moveNum], 4);
4032         SendToProgram(moveList[moveNum], cps);
4033         AlphaRank(moveList[moveNum], 4); // and back
4034       } else
4035       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036        * the engine. It would be nice to have a better way to identify castle 
4037        * moves here. */
4038       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039                                                                          && cps->useOOCastle) {
4040         int fromX = moveList[moveNum][0] - AAA; 
4041         int fromY = moveList[moveNum][1] - ONE;
4042         int toX = moveList[moveNum][2] - AAA; 
4043         int toY = moveList[moveNum][3] - ONE;
4044         if((boards[moveNum][fromY][fromX] == WhiteKing 
4045             && boards[moveNum][toY][toX] == WhiteRook)
4046            || (boards[moveNum][fromY][fromX] == BlackKing 
4047                && boards[moveNum][toY][toX] == BlackRook)) {
4048           if(toX > fromX) SendToProgram("O-O\n", cps);
4049           else SendToProgram("O-O-O\n", cps);
4050         }
4051         else SendToProgram(moveList[moveNum], cps);
4052       }
4053       else SendToProgram(moveList[moveNum], cps);
4054       /* End of additions by Tord */
4055     }
4056
4057     /* [HGM] setting up the opening has brought engine in force mode! */
4058     /*       Send 'go' if we are in a mode where machine should play. */
4059     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060         (gameMode == TwoMachinesPlay   ||
4061 #ifdef ZIPPY
4062          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4063 #endif
4064          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065         SendToProgram("go\n", cps);
4066   if (appData.debugMode) {
4067     fprintf(debugFP, "(extra)\n");
4068   }
4069     }
4070     setboardSpoiledMachineBlack = 0;
4071 }
4072
4073 void
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4075      ChessMove moveType;
4076      int fromX, fromY, toX, toY;
4077 {
4078     char user_move[MSG_SIZ];
4079
4080     switch (moveType) {
4081       default:
4082         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083                 (int)moveType, fromX, fromY, toX, toY);
4084         DisplayError(user_move + strlen("say "), 0);
4085         break;
4086       case WhiteKingSideCastle:
4087       case BlackKingSideCastle:
4088       case WhiteQueenSideCastleWild:
4089       case BlackQueenSideCastleWild:
4090       /* PUSH Fabien */
4091       case WhiteHSideCastleFR:
4092       case BlackHSideCastleFR:
4093       /* POP Fabien */
4094         sprintf(user_move, "o-o\n");
4095         break;
4096       case WhiteQueenSideCastle:
4097       case BlackQueenSideCastle:
4098       case WhiteKingSideCastleWild:
4099       case BlackKingSideCastleWild:
4100       /* PUSH Fabien */
4101       case WhiteASideCastleFR:
4102       case BlackASideCastleFR:
4103       /* POP Fabien */
4104         sprintf(user_move, "o-o-o\n");
4105         break;
4106       case WhitePromotionQueen:
4107       case BlackPromotionQueen:
4108       case WhitePromotionRook:
4109       case BlackPromotionRook:
4110       case WhitePromotionBishop:
4111       case BlackPromotionBishop:
4112       case WhitePromotionKnight:
4113       case BlackPromotionKnight:
4114       case WhitePromotionKing:
4115       case BlackPromotionKing:
4116       case WhitePromotionChancellor:
4117       case BlackPromotionChancellor:
4118       case WhitePromotionArchbishop:
4119       case BlackPromotionArchbishop:
4120         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121             sprintf(user_move, "%c%c%c%c=%c\n",
4122                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123                 PieceToChar(WhiteFerz));
4124         else if(gameInfo.variant == VariantGreat)
4125             sprintf(user_move, "%c%c%c%c=%c\n",
4126                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127                 PieceToChar(WhiteMan));
4128         else
4129             sprintf(user_move, "%c%c%c%c=%c\n",
4130                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131                 PieceToChar(PromoPiece(moveType)));
4132         break;
4133       case WhiteDrop:
4134       case BlackDrop:
4135         sprintf(user_move, "%c@%c%c\n",
4136                 ToUpper(PieceToChar((ChessSquare) fromX)),
4137                 AAA + toX, ONE + toY);
4138         break;
4139       case NormalMove:
4140       case WhiteCapturesEnPassant:
4141       case BlackCapturesEnPassant:
4142       case IllegalMove:  /* could be a variant we don't quite understand */
4143         sprintf(user_move, "%c%c%c%c\n",
4144                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4145         break;
4146     }
4147     SendToICS(user_move);
4148     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4150 }
4151
4152 void
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4154      int rf, ff, rt, ft;
4155      char promoChar;
4156      char move[7];
4157 {
4158     if (rf == DROP_RANK) {
4159         sprintf(move, "%c@%c%c\n",
4160                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4161     } else {
4162         if (promoChar == 'x' || promoChar == NULLCHAR) {
4163             sprintf(move, "%c%c%c%c\n",
4164                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4165         } else {
4166             sprintf(move, "%c%c%c%c%c\n",
4167                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4168         }
4169     }
4170 }
4171
4172 void
4173 ProcessICSInitScript(f)
4174      FILE *f;
4175 {
4176     char buf[MSG_SIZ];
4177
4178     while (fgets(buf, MSG_SIZ, f)) {
4179         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4180     }
4181
4182     fclose(f);
4183 }
4184
4185
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4187 void
4188 AlphaRank(char *move, int n)
4189 {
4190 //    char *p = move, c; int x, y;
4191
4192     if (appData.debugMode) {
4193         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4194     }
4195
4196     if(move[1]=='*' && 
4197        move[2]>='0' && move[2]<='9' &&
4198        move[3]>='a' && move[3]<='x'    ) {
4199         move[1] = '@';
4200         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4201         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4202     } else
4203     if(move[0]>='0' && move[0]<='9' &&
4204        move[1]>='a' && move[1]<='x' &&
4205        move[2]>='0' && move[2]<='9' &&
4206        move[3]>='a' && move[3]<='x'    ) {
4207         /* input move, Shogi -> normal */
4208         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4209         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4211         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4212     } else
4213     if(move[1]=='@' &&
4214        move[3]>='0' && move[3]<='9' &&
4215        move[2]>='a' && move[2]<='x'    ) {
4216         move[1] = '*';
4217         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4219     } else
4220     if(
4221        move[0]>='a' && move[0]<='x' &&
4222        move[3]>='0' && move[3]<='9' &&
4223        move[2]>='a' && move[2]<='x'    ) {
4224          /* output move, normal -> Shogi */
4225         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4230     }
4231     if (appData.debugMode) {
4232         fprintf(debugFP, "   out = '%s'\n", move);
4233     }
4234 }
4235
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4237 Boolean
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4239      char *move;
4240      int moveNum;
4241      ChessMove *moveType;
4242      int *fromX, *fromY, *toX, *toY;
4243      char *promoChar;
4244 {       
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "move to parse: %s\n", move);
4247     }
4248     *moveType = yylexstr(moveNum, move);
4249
4250     switch (*moveType) {
4251       case WhitePromotionChancellor:
4252       case BlackPromotionChancellor:
4253       case WhitePromotionArchbishop:
4254       case BlackPromotionArchbishop:
4255       case WhitePromotionQueen:
4256       case BlackPromotionQueen:
4257       case WhitePromotionRook:
4258       case BlackPromotionRook:
4259       case WhitePromotionBishop:
4260       case BlackPromotionBishop:
4261       case WhitePromotionKnight:
4262       case BlackPromotionKnight:
4263       case WhitePromotionKing:
4264       case BlackPromotionKing:
4265       case NormalMove:
4266       case WhiteCapturesEnPassant:
4267       case BlackCapturesEnPassant:
4268       case WhiteKingSideCastle:
4269       case WhiteQueenSideCastle:
4270       case BlackKingSideCastle:
4271       case BlackQueenSideCastle:
4272       case WhiteKingSideCastleWild:
4273       case WhiteQueenSideCastleWild:
4274       case BlackKingSideCastleWild:
4275       case BlackQueenSideCastleWild:
4276       /* Code added by Tord: */
4277       case WhiteHSideCastleFR:
4278       case WhiteASideCastleFR:
4279       case BlackHSideCastleFR:
4280       case BlackASideCastleFR:
4281       /* End of code added by Tord */
4282       case IllegalMove:         /* bug or odd chess variant */
4283         *fromX = currentMoveString[0] - AAA;
4284         *fromY = currentMoveString[1] - ONE;
4285         *toX = currentMoveString[2] - AAA;
4286         *toY = currentMoveString[3] - ONE;
4287         *promoChar = currentMoveString[4];
4288         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290     if (appData.debugMode) {
4291         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4292     }
4293             *fromX = *fromY = *toX = *toY = 0;
4294             return FALSE;
4295         }
4296         if (appData.testLegality) {
4297           return (*moveType != IllegalMove);
4298         } else {
4299           return !(fromX == fromY && toX == toY);
4300         }
4301
4302       case WhiteDrop:
4303       case BlackDrop:
4304         *fromX = *moveType == WhiteDrop ?
4305           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306           (int) CharToPiece(ToLower(currentMoveString[0]));
4307         *fromY = DROP_RANK;
4308         *toX = currentMoveString[2] - AAA;
4309         *toY = currentMoveString[3] - ONE;
4310         *promoChar = NULLCHAR;
4311         return TRUE;
4312
4313       case AmbiguousMove:
4314       case ImpossibleMove:
4315       case (ChessMove) 0:       /* end of file */
4316       case ElapsedTime:
4317       case Comment:
4318       case PGNTag:
4319       case NAG:
4320       case WhiteWins:
4321       case BlackWins:
4322       case GameIsDrawn:
4323       default:
4324     if (appData.debugMode) {
4325         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4326     }
4327         /* bug? */
4328         *fromX = *fromY = *toX = *toY = 0;
4329         *promoChar = NULLCHAR;
4330         return FALSE;
4331     }
4332 }
4333
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4337 #define DARK 1
4338 #define LITE 2
4339 #define ANY 3
4340
4341 int squaresLeft[4];
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4344
4345 void GetPositionNumber()
4346 {       // sets global variable seed
4347         int i;
4348
4349         seed = appData.defaultFrcPosition;
4350         if(seed < 0) { // randomize based on time for negative FRC position numbers
4351                 for(i=0; i<50; i++) seed += random();
4352                 seed = random() ^ random() >> 8 ^ random() << 8;
4353                 if(seed<0) seed = -seed;
4354         }
4355 }
4356
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4359 {
4360         int i;
4361
4362         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364                         board[rank][i] = (ChessSquare) pieceType;
4365                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4366                         squaresLeft[ANY]--;
4367                         piecesLeft[pieceType]--; 
4368                         return i;
4369                 }
4370         }
4371         return -1;
4372 }
4373
4374
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4377 {
4378         int i;
4379
4380         i = seed % squaresLeft[shade];
4381         nrOfShuffles *= squaresLeft[shade];
4382         seed /= squaresLeft[shade];
4383         put(board, pieceType, rank, i, shade);
4384 }
4385
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4388 {
4389         int i, n=squaresLeft[ANY], j=n-1, k;
4390
4391         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392         i = seed % k;  // pick one
4393         nrOfShuffles *= k;
4394         seed /= k;
4395         while(i >= j) i -= j--;
4396         j = n - 1 - j; i += j;
4397         put(board, pieceType, rank, j, ANY);
4398         put(board, pieceType, rank, i, ANY);
4399 }
4400
4401 void SetUpShuffle(Board board, int number)
4402 {
4403         int i, p, first=1;
4404
4405         GetPositionNumber(); nrOfShuffles = 1;
4406
4407         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4409         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4410
4411         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4412
4413         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414             p = (int) board[0][i];
4415             if(p < (int) BlackPawn) piecesLeft[p] ++;
4416             board[0][i] = EmptySquare;
4417         }
4418
4419         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420             // shuffles restricted to allow normal castling put KRR first
4421             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428                 put(board, WhiteRook, 0, 0, ANY);
4429             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4430         }
4431
4432         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436                 while(piecesLeft[p] >= 2) {
4437                     AddOnePiece(board, p, 0, LITE);
4438                     AddOnePiece(board, p, 0, DARK);
4439                 }
4440                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4441             }
4442
4443         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445             // but we leave King and Rooks for last, to possibly obey FRC restriction
4446             if(p == (int)WhiteRook) continue;
4447             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4449         }
4450
4451         // now everything is placed, except perhaps King (Unicorn) and Rooks
4452
4453         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454             // Last King gets castling rights
4455             while(piecesLeft[(int)WhiteUnicorn]) {
4456                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4458             }
4459
4460             while(piecesLeft[(int)WhiteKing]) {
4461                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4463             }
4464
4465
4466         } else {
4467             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4468             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4469         }
4470
4471         // Only Rooks can be left; simply place them all
4472         while(piecesLeft[(int)WhiteRook]) {
4473                 i = put(board, WhiteRook, 0, 0, ANY);
4474                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4475                         if(first) {
4476                                 first=0;
4477                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4478                         }
4479                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4480                 }
4481         }
4482         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4484         }
4485
4486         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4487 }
4488
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /*       Basically a safe strcpy that uses the last character as King */
4492 {
4493     int result = FALSE; int NrPieces;
4494
4495     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4496                     && NrPieces >= 12 && !(NrPieces&1)) {
4497         int i; /* [HGM] Accept even length from 12 to 34 */
4498
4499         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500         for( i=0; i<NrPieces/2-1; i++ ) {
4501             table[i] = map[i];
4502             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4503         }
4504         table[(int) WhiteKing]  = map[NrPieces/2-1];
4505         table[(int) BlackKing]  = map[NrPieces-1];
4506
4507         result = TRUE;
4508     }
4509
4510     return result;
4511 }
4512
4513 void Prelude(Board board)
4514 {       // [HGM] superchess: random selection of exo-pieces
4515         int i, j, k; ChessSquare p; 
4516         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4517
4518         GetPositionNumber(); // use FRC position number
4519
4520         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521             SetCharTable(pieceToChar, appData.pieceToCharTable);
4522             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4523                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4524         }
4525
4526         j = seed%4;                 seed /= 4; 
4527         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4529         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4530         j = seed%3 + (seed%3 >= j); seed /= 3; 
4531         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4533         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4534         j = seed%3;                 seed /= 3; 
4535         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4537         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4538         j = seed%2 + (seed%2 >= j); seed /= 2; 
4539         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4541         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4542         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4543         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4544         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545         put(board, exoPieces[0],    0, 0, ANY);
4546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4547 }
4548
4549 void
4550 InitPosition(redraw)
4551      int redraw;
4552 {
4553     ChessSquare (* pieces)[BOARD_SIZE];
4554     int i, j, pawnRow, overrule,
4555     oldx = gameInfo.boardWidth,
4556     oldy = gameInfo.boardHeight,
4557     oldh = gameInfo.holdingsWidth,
4558     oldv = gameInfo.variant;
4559
4560     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4561
4562     /* [AS] Initialize pv info list [HGM] and game status */
4563     {
4564         for( i=0; i<MAX_MOVES; i++ ) {
4565             pvInfoList[i].depth = 0;
4566             epStatus[i]=EP_NONE;
4567             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4568         }
4569
4570         initialRulePlies = 0; /* 50-move counter start */
4571
4572         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4573         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4574     }
4575
4576     
4577     /* [HGM] logic here is completely changed. In stead of full positions */
4578     /* the initialized data only consist of the two backranks. The switch */
4579     /* selects which one we will use, which is than copied to the Board   */
4580     /* initialPosition, which for the rest is initialized by Pawns and    */
4581     /* empty squares. This initial position is then copied to boards[0],  */
4582     /* possibly after shuffling, so that it remains available.            */
4583
4584     gameInfo.holdingsWidth = 0; /* default board sizes */
4585     gameInfo.boardWidth    = 8;
4586     gameInfo.boardHeight   = 8;
4587     gameInfo.holdingsSize  = 0;
4588     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4589     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4590     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4591
4592     switch (gameInfo.variant) {
4593     case VariantFischeRandom:
4594       shuffleOpenings = TRUE;
4595     default:
4596       pieces = FIDEArray;
4597       break;
4598     case VariantShatranj:
4599       pieces = ShatranjArray;
4600       nrCastlingRights = 0;
4601       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4602       break;
4603     case VariantTwoKings:
4604       pieces = twoKingsArray;
4605       break;
4606     case VariantCapaRandom:
4607       shuffleOpenings = TRUE;
4608     case VariantCapablanca:
4609       pieces = CapablancaArray;
4610       gameInfo.boardWidth = 10;
4611       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4612       break;
4613     case VariantGothic:
4614       pieces = GothicArray;
4615       gameInfo.boardWidth = 10;
4616       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4617       break;
4618     case VariantJanus:
4619       pieces = JanusArray;
4620       gameInfo.boardWidth = 10;
4621       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4622       nrCastlingRights = 6;
4623         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4624         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4625         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4626         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4627         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4628         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4629       break;
4630     case VariantFalcon:
4631       pieces = FalconArray;
4632       gameInfo.boardWidth = 10;
4633       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4634       break;
4635     case VariantXiangqi:
4636       pieces = XiangqiArray;
4637       gameInfo.boardWidth  = 9;
4638       gameInfo.boardHeight = 10;
4639       nrCastlingRights = 0;
4640       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4641       break;
4642     case VariantShogi:
4643       pieces = ShogiArray;
4644       gameInfo.boardWidth  = 9;
4645       gameInfo.boardHeight = 9;
4646       gameInfo.holdingsSize = 7;
4647       nrCastlingRights = 0;
4648       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4649       break;
4650     case VariantCourier:
4651       pieces = CourierArray;
4652       gameInfo.boardWidth  = 12;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4655       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4656       break;
4657     case VariantKnightmate:
4658       pieces = KnightmateArray;
4659       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4660       break;
4661     case VariantFairy:
4662       pieces = fairyArray;
4663       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4664       break;
4665     case VariantGreat:
4666       pieces = GreatArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4669       gameInfo.holdingsSize = 8;
4670       break;
4671     case VariantSuper:
4672       pieces = FIDEArray;
4673       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4674       gameInfo.holdingsSize = 8;
4675       startedFromSetupPosition = TRUE;
4676       break;
4677     case VariantCrazyhouse:
4678     case VariantBughouse:
4679       pieces = FIDEArray;
4680       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4681       gameInfo.holdingsSize = 5;
4682       break;
4683     case VariantWildCastle:
4684       pieces = FIDEArray;
4685       /* !!?shuffle with kings guaranteed to be on d or e file */
4686       shuffleOpenings = 1;
4687       break;
4688     case VariantNoCastle:
4689       pieces = FIDEArray;
4690       nrCastlingRights = 0;
4691       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692       /* !!?unconstrained back-rank shuffle */
4693       shuffleOpenings = 1;
4694       break;
4695     }
4696
4697     overrule = 0;
4698     if(appData.NrFiles >= 0) {
4699         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4700         gameInfo.boardWidth = appData.NrFiles;
4701     }
4702     if(appData.NrRanks >= 0) {
4703         gameInfo.boardHeight = appData.NrRanks;
4704     }
4705     if(appData.holdingsSize >= 0) {
4706         i = appData.holdingsSize;
4707         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4708         gameInfo.holdingsSize = i;
4709     }
4710     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4711     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4712         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4713
4714     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4715     if(pawnRow < 1) pawnRow = 1;
4716
4717     /* User pieceToChar list overrules defaults */
4718     if(appData.pieceToCharTable != NULL)
4719         SetCharTable(pieceToChar, appData.pieceToCharTable);
4720
4721     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4722
4723         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4724             s = (ChessSquare) 0; /* account holding counts in guard band */
4725         for( i=0; i<BOARD_HEIGHT; i++ )
4726             initialPosition[i][j] = s;
4727
4728         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4729         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4730         initialPosition[pawnRow][j] = WhitePawn;
4731         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4732         if(gameInfo.variant == VariantXiangqi) {
4733             if(j&1) {
4734                 initialPosition[pawnRow][j] = 
4735                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4736                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4737                    initialPosition[2][j] = WhiteCannon;
4738                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4739                 }
4740             }
4741         }
4742         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4743     }
4744     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4745
4746             j=BOARD_LEFT+1;
4747             initialPosition[1][j] = WhiteBishop;
4748             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4749             j=BOARD_RGHT-2;
4750             initialPosition[1][j] = WhiteRook;
4751             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4752     }
4753
4754     if( nrCastlingRights == -1) {
4755         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4756         /*       This sets default castling rights from none to normal corners   */
4757         /* Variants with other castling rights must set them themselves above    */
4758         nrCastlingRights = 6;
4759        
4760         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4761         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4762         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4763         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4764         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4765         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4766      }
4767
4768      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4769      if(gameInfo.variant == VariantGreat) { // promotion commoners
4770         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4771         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4772         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4773         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4774      }
4775 #if 0
4776     if(gameInfo.variant == VariantFischeRandom) {
4777       if( appData.defaultFrcPosition < 0 ) {
4778         ShuffleFRC( initialPosition );
4779       }
4780       else {
4781         SetupFRC( initialPosition, appData.defaultFrcPosition );
4782       }
4783       startedFromSetupPosition = TRUE;
4784     } else 
4785 #else
4786   if (appData.debugMode) {
4787     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4788   }
4789     if(shuffleOpenings) {
4790         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4791         startedFromSetupPosition = TRUE;
4792     }
4793 #endif
4794     if(startedFromPositionFile) {
4795       /* [HGM] loadPos: use PositionFile for every new game */
4796       CopyBoard(initialPosition, filePosition);
4797       for(i=0; i<nrCastlingRights; i++)
4798           castlingRights[0][i] = initialRights[i] = fileRights[i];
4799       startedFromSetupPosition = TRUE;
4800     }
4801
4802     CopyBoard(boards[0], initialPosition);
4803
4804     if(oldx != gameInfo.boardWidth ||
4805        oldy != gameInfo.boardHeight ||
4806        oldh != gameInfo.holdingsWidth
4807 #ifdef GOTHIC
4808        || oldv == VariantGothic ||        // For licensing popups
4809        gameInfo.variant == VariantGothic
4810 #endif
4811 #ifdef FALCON
4812        || oldv == VariantFalcon ||
4813        gameInfo.variant == VariantFalcon
4814 #endif
4815                                          )
4816             InitDrawingSizes(-2 ,0);
4817
4818     if (redraw)
4819       DrawPosition(TRUE, boards[currentMove]);
4820 }
4821
4822 void
4823 SendBoard(cps, moveNum)
4824      ChessProgramState *cps;
4825      int moveNum;
4826 {
4827     char message[MSG_SIZ];
4828     
4829     if (cps->useSetboard) {
4830       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4831       sprintf(message, "setboard %s\n", fen);
4832       SendToProgram(message, cps);
4833       free(fen);
4834
4835     } else {
4836       ChessSquare *bp;
4837       int i, j;
4838       /* Kludge to set black to move, avoiding the troublesome and now
4839        * deprecated "black" command.
4840        */
4841       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4842
4843       SendToProgram("edit\n", cps);
4844       SendToProgram("#\n", cps);
4845       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846         bp = &boards[moveNum][i][BOARD_LEFT];
4847         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848           if ((int) *bp < (int) BlackPawn) {
4849             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4850                     AAA + j, ONE + i);
4851             if(message[0] == '+' || message[0] == '~') {
4852                 sprintf(message, "%c%c%c+\n",
4853                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4854                         AAA + j, ONE + i);
4855             }
4856             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4857                 message[1] = BOARD_RGHT   - 1 - j + '1';
4858                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4859             }
4860             SendToProgram(message, cps);
4861           }
4862         }
4863       }
4864     
4865       SendToProgram("c\n", cps);
4866       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867         bp = &boards[moveNum][i][BOARD_LEFT];
4868         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869           if (((int) *bp != (int) EmptySquare)
4870               && ((int) *bp >= (int) BlackPawn)) {
4871             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4872                     AAA + j, ONE + i);
4873             if(message[0] == '+' || message[0] == '~') {
4874                 sprintf(message, "%c%c%c+\n",
4875                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4876                         AAA + j, ONE + i);
4877             }
4878             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879                 message[1] = BOARD_RGHT   - 1 - j + '1';
4880                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4881             }
4882             SendToProgram(message, cps);
4883           }
4884         }
4885       }
4886     
4887       SendToProgram(".\n", cps);
4888     }
4889     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4890 }
4891
4892 int
4893 IsPromotion(fromX, fromY, toX, toY)
4894      int fromX, fromY, toX, toY;
4895 {
4896     /* [HGM] add Shogi promotions */
4897     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4898     ChessSquare piece;
4899
4900     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4901       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4902    /* [HGM] Note to self: line above also weeds out drops */
4903     piece = boards[currentMove][fromY][fromX];
4904     if(gameInfo.variant == VariantShogi) {
4905         promotionZoneSize = 3;
4906         highestPromotingPiece = (int)WhiteKing;
4907         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4908            and if in normal chess we then allow promotion to King, why not
4909            allow promotion of other piece in Shogi?                         */
4910     }
4911     if((int)piece >= BlackPawn) {
4912         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4913              return FALSE;
4914         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4915     } else {
4916         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4917            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4918     }
4919     return ( (int)piece <= highestPromotingPiece );
4920 }
4921
4922 int
4923 InPalace(row, column)
4924      int row, column;
4925 {   /* [HGM] for Xiangqi */
4926     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4927          column < (BOARD_WIDTH + 4)/2 &&
4928          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4929     return FALSE;
4930 }
4931
4932 int
4933 PieceForSquare (x, y)
4934      int x;
4935      int y;
4936 {
4937   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4938      return -1;
4939   else
4940      return boards[currentMove][y][x];
4941 }
4942
4943 int
4944 OKToStartUserMove(x, y)
4945      int x, y;
4946 {
4947     ChessSquare from_piece;
4948     int white_piece;
4949
4950     if (matchMode) return FALSE;
4951     if (gameMode == EditPosition) return TRUE;
4952
4953     if (x >= 0 && y >= 0)
4954       from_piece = boards[currentMove][y][x];
4955     else
4956       from_piece = EmptySquare;
4957
4958     if (from_piece == EmptySquare) return FALSE;
4959
4960     white_piece = (int)from_piece >= (int)WhitePawn &&
4961       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4962
4963     switch (gameMode) {
4964       case PlayFromGameFile:
4965       case AnalyzeFile:
4966       case TwoMachinesPlay:
4967       case EndOfGame:
4968         return FALSE;
4969
4970       case IcsObserving:
4971       case IcsIdle:
4972         return FALSE;
4973
4974       case MachinePlaysWhite:
4975       case IcsPlayingBlack:
4976         if (appData.zippyPlay) return FALSE;
4977         if (white_piece) {
4978             DisplayMoveError(_("You are playing Black"));
4979             return FALSE;
4980         }
4981         break;
4982
4983       case MachinePlaysBlack:
4984       case IcsPlayingWhite:
4985         if (appData.zippyPlay) return FALSE;
4986         if (!white_piece) {
4987             DisplayMoveError(_("You are playing White"));
4988             return FALSE;
4989         }
4990         break;
4991
4992       case EditGame:
4993         if (!white_piece && WhiteOnMove(currentMove)) {
4994             DisplayMoveError(_("It is White's turn"));
4995             return FALSE;
4996         }           
4997         if (white_piece && !WhiteOnMove(currentMove)) {
4998             DisplayMoveError(_("It is Black's turn"));
4999             return FALSE;
5000         }           
5001         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5002             /* Editing correspondence game history */
5003             /* Could disallow this or prompt for confirmation */
5004             cmailOldMove = -1;
5005         }
5006         if (currentMove < forwardMostMove) {
5007             /* Discarding moves */
5008             /* Could prompt for confirmation here,
5009                but I don't think that's such a good idea */
5010             forwardMostMove = currentMove;
5011         }
5012         break;
5013
5014       case BeginningOfGame:
5015         if (appData.icsActive) return FALSE;
5016         if (!appData.noChessProgram) {
5017             if (!white_piece) {
5018                 DisplayMoveError(_("You are playing White"));
5019                 return FALSE;
5020             }
5021         }
5022         break;
5023         
5024       case Training:
5025         if (!white_piece && WhiteOnMove(currentMove)) {
5026             DisplayMoveError(_("It is White's turn"));
5027             return FALSE;
5028         }           
5029         if (white_piece && !WhiteOnMove(currentMove)) {
5030             DisplayMoveError(_("It is Black's turn"));
5031             return FALSE;
5032         }           
5033         break;
5034
5035       default:
5036       case IcsExamining:
5037         break;
5038     }
5039     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5040         && gameMode != AnalyzeFile && gameMode != Training) {
5041         DisplayMoveError(_("Displayed position is not current"));
5042         return FALSE;
5043     }
5044     return TRUE;
5045 }
5046
5047 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5048 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5049 int lastLoadGameUseList = FALSE;
5050 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5051 ChessMove lastLoadGameStart = (ChessMove) 0;
5052
5053 ChessMove
5054 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5055      int fromX, fromY, toX, toY;
5056      int promoChar;
5057      Boolean captureOwn;
5058 {
5059     ChessMove moveType;
5060     ChessSquare pdown, pup;
5061
5062     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5063
5064     /* [HGM] suppress all moves into holdings area and guard band */
5065     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5066             return ImpossibleMove;
5067
5068     /* [HGM] <sameColor> moved to here from winboard.c */
5069     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5070     pdown = boards[currentMove][fromY][fromX];
5071     pup = boards[currentMove][toY][toX];
5072     if (    gameMode != EditPosition && !captureOwn &&
5073             (WhitePawn <= pdown && pdown < BlackPawn &&
5074              WhitePawn <= pup && pup < BlackPawn  ||
5075              BlackPawn <= pdown && pdown < EmptySquare &&
5076              BlackPawn <= pup && pup < EmptySquare 
5077             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5078                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5079                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5080                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5081                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5082         )           )
5083          return Comment;
5084
5085     /* Check if the user is playing in turn.  This is complicated because we
5086        let the user "pick up" a piece before it is his turn.  So the piece he
5087        tried to pick up may have been captured by the time he puts it down!
5088        Therefore we use the color the user is supposed to be playing in this
5089        test, not the color of the piece that is currently on the starting
5090        square---except in EditGame mode, where the user is playing both
5091        sides; fortunately there the capture race can't happen.  (It can
5092        now happen in IcsExamining mode, but that's just too bad.  The user
5093        will get a somewhat confusing message in that case.)
5094        */
5095
5096     switch (gameMode) {
5097       case PlayFromGameFile:
5098       case AnalyzeFile:
5099       case TwoMachinesPlay:
5100       case EndOfGame:
5101       case IcsObserving:
5102       case IcsIdle:
5103         /* We switched into a game mode where moves are not accepted,
5104            perhaps while the mouse button was down. */
5105         return ImpossibleMove;
5106
5107       case MachinePlaysWhite:
5108         /* User is moving for Black */
5109         if (WhiteOnMove(currentMove)) {
5110             DisplayMoveError(_("It is White's turn"));
5111             return ImpossibleMove;
5112         }
5113         break;
5114
5115       case MachinePlaysBlack:
5116         /* User is moving for White */
5117         if (!WhiteOnMove(currentMove)) {
5118             DisplayMoveError(_("It is Black's turn"));
5119             return ImpossibleMove;
5120         }
5121         break;
5122
5123       case EditGame:
5124       case IcsExamining:
5125       case BeginningOfGame:
5126       case AnalyzeMode:
5127       case Training:
5128         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5129             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5130             /* User is moving for Black */
5131             if (WhiteOnMove(currentMove)) {
5132                 DisplayMoveError(_("It is White's turn"));
5133                 return ImpossibleMove;
5134             }
5135         } else {
5136             /* User is moving for White */
5137             if (!WhiteOnMove(currentMove)) {
5138                 DisplayMoveError(_("It is Black's turn"));
5139                 return ImpossibleMove;
5140             }
5141         }
5142         break;
5143
5144       case IcsPlayingBlack:
5145         /* User is moving for Black */
5146         if (WhiteOnMove(currentMove)) {
5147             if (!appData.premove) {
5148                 DisplayMoveError(_("It is White's turn"));
5149             } else if (toX >= 0 && toY >= 0) {
5150                 premoveToX = toX;
5151                 premoveToY = toY;
5152                 premoveFromX = fromX;
5153                 premoveFromY = fromY;
5154                 premovePromoChar = promoChar;
5155                 gotPremove = 1;
5156                 if (appData.debugMode) 
5157                     fprintf(debugFP, "Got premove: fromX %d,"
5158                             "fromY %d, toX %d, toY %d\n",
5159                             fromX, fromY, toX, toY);
5160             }
5161             return ImpossibleMove;
5162         }
5163         break;
5164
5165       case IcsPlayingWhite:
5166         /* User is moving for White */
5167         if (!WhiteOnMove(currentMove)) {
5168             if (!appData.premove) {
5169                 DisplayMoveError(_("It is Black's turn"));
5170             } else if (toX >= 0 && toY >= 0) {
5171                 premoveToX = toX;
5172                 premoveToY = toY;
5173                 premoveFromX = fromX;
5174                 premoveFromY = fromY;
5175                 premovePromoChar = promoChar;
5176                 gotPremove = 1;
5177                 if (appData.debugMode) 
5178                     fprintf(debugFP, "Got premove: fromX %d,"
5179                             "fromY %d, toX %d, toY %d\n",
5180                             fromX, fromY, toX, toY);
5181             }
5182             return ImpossibleMove;
5183         }
5184         break;
5185
5186       default:
5187         break;
5188
5189       case EditPosition:
5190         /* EditPosition, empty square, or different color piece;
5191            click-click move is possible */
5192         if (toX == -2 || toY == -2) {
5193             boards[0][fromY][fromX] = EmptySquare;
5194             return AmbiguousMove;
5195         } else if (toX >= 0 && toY >= 0) {
5196             boards[0][toY][toX] = boards[0][fromY][fromX];
5197             boards[0][fromY][fromX] = EmptySquare;
5198             return AmbiguousMove;
5199         }
5200         return ImpossibleMove;
5201     }
5202
5203     /* [HGM] If move started in holdings, it means a drop */
5204     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5205          if( pup != EmptySquare ) return ImpossibleMove;
5206          if(appData.testLegality) {
5207              /* it would be more logical if LegalityTest() also figured out
5208               * which drops are legal. For now we forbid pawns on back rank.
5209               * Shogi is on its own here...
5210               */
5211              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5212                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5213                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5214          }
5215          return WhiteDrop; /* Not needed to specify white or black yet */
5216     }
5217
5218     userOfferedDraw = FALSE;
5219         
5220     /* [HGM] always test for legality, to get promotion info */
5221     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5222                           epStatus[currentMove], castlingRights[currentMove],
5223                                          fromY, fromX, toY, toX, promoChar);
5224     /* [HGM] but possibly ignore an IllegalMove result */
5225     if (appData.testLegality) {
5226         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5227             DisplayMoveError(_("Illegal move"));
5228             return ImpossibleMove;
5229         }
5230     }
5231 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5232     return moveType;
5233     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5234        function is made into one that returns an OK move type if FinishMove
5235        should be called. This to give the calling driver routine the
5236        opportunity to finish the userMove input with a promotion popup,
5237        without bothering the user with this for invalid or illegal moves */
5238
5239 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5240 }
5241
5242 /* Common tail of UserMoveEvent and DropMenuEvent */
5243 int
5244 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5245      ChessMove moveType;
5246      int fromX, fromY, toX, toY;
5247      /*char*/int promoChar;
5248 {
5249     char *bookHit = 0;
5250 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5251     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5252         // [HGM] superchess: suppress promotions to non-available piece
5253         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5254         if(WhiteOnMove(currentMove)) {
5255             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5256         } else {
5257             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5258         }
5259     }
5260
5261     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5262        move type in caller when we know the move is a legal promotion */
5263     if(moveType == NormalMove && promoChar)
5264         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5265 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5266     /* [HGM] convert drag-and-drop piece drops to standard form */
5267     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5268          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5269            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5270                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5271 //         fromX = boards[currentMove][fromY][fromX];
5272            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5273            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5274            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5275            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5276          fromY = DROP_RANK;
5277     }
5278
5279     /* [HGM] <popupFix> The following if has been moved here from
5280        UserMoveEvent(). Because it seemed to belon here (why not allow
5281        piece drops in training games?), and because it can only be
5282        performed after it is known to what we promote. */
5283     if (gameMode == Training) {
5284       /* compare the move played on the board to the next move in the
5285        * game. If they match, display the move and the opponent's response. 
5286        * If they don't match, display an error message.
5287        */
5288       int saveAnimate;
5289       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5290       CopyBoard(testBoard, boards[currentMove]);
5291       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5292
5293       if (CompareBoards(testBoard, boards[currentMove+1])) {
5294         ForwardInner(currentMove+1);
5295
5296         /* Autoplay the opponent's response.
5297          * if appData.animate was TRUE when Training mode was entered,
5298          * the response will be animated.
5299          */
5300         saveAnimate = appData.animate;
5301         appData.animate = animateTraining;
5302         ForwardInner(currentMove+1);
5303         appData.animate = saveAnimate;
5304
5305         /* check for the end of the game */
5306         if (currentMove >= forwardMostMove) {
5307           gameMode = PlayFromGameFile;
5308           ModeHighlight();
5309           SetTrainingModeOff();
5310           DisplayInformation(_("End of game"));
5311         }
5312       } else {
5313         DisplayError(_("Incorrect move"), 0);
5314       }
5315       return 1;
5316     }
5317
5318   /* Ok, now we know that the move is good, so we can kill
5319      the previous line in Analysis Mode */
5320   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5321     forwardMostMove = currentMove;
5322   }
5323
5324   /* If we need the chess program but it's dead, restart it */
5325   ResurrectChessProgram();
5326
5327   /* A user move restarts a paused game*/
5328   if (pausing)
5329     PauseEvent();
5330
5331   thinkOutput[0] = NULLCHAR;
5332
5333   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5334
5335   if (gameMode == BeginningOfGame) {
5336     if (appData.noChessProgram) {
5337       gameMode = EditGame;
5338       SetGameInfo();
5339     } else {
5340       char buf[MSG_SIZ];
5341       gameMode = MachinePlaysBlack;
5342       StartClocks();
5343       SetGameInfo();
5344       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5345       DisplayTitle(buf);
5346       if (first.sendName) {
5347         sprintf(buf, "name %s\n", gameInfo.white);
5348         SendToProgram(buf, &first);
5349       }
5350       StartClocks();
5351     }
5352     ModeHighlight();
5353   }
5354 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5355   /* Relay move to ICS or chess engine */
5356   if (appData.icsActive) {
5357     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5358         gameMode == IcsExamining) {
5359       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5360       ics_user_moved = 1;
5361     }
5362   } else {
5363     if (first.sendTime && (gameMode == BeginningOfGame ||
5364                            gameMode == MachinePlaysWhite ||
5365                            gameMode == MachinePlaysBlack)) {
5366       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5367     }
5368     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5369          // [HGM] book: if program might be playing, let it use book
5370         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5371         first.maybeThinking = TRUE;
5372     } else SendMoveToProgram(forwardMostMove-1, &first);
5373     if (currentMove == cmailOldMove + 1) {
5374       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5375     }
5376   }
5377
5378   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5379
5380   switch (gameMode) {
5381   case EditGame:
5382     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5383                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5384     case MT_NONE:
5385     case MT_CHECK:
5386       break;
5387     case MT_CHECKMATE:
5388     case MT_STAINMATE:
5389       if (WhiteOnMove(currentMove)) {
5390         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5391       } else {
5392         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5393       }
5394       break;
5395     case MT_STALEMATE:
5396       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5397       break;
5398     }
5399     break;
5400     
5401   case MachinePlaysBlack:
5402   case MachinePlaysWhite:
5403     /* disable certain menu options while machine is thinking */
5404     SetMachineThinkingEnables();
5405     break;
5406
5407   default:
5408     break;
5409   }
5410
5411   if(bookHit) { // [HGM] book: simulate book reply
5412         static char bookMove[MSG_SIZ]; // a bit generous?
5413
5414         programStats.nodes = programStats.depth = programStats.time = 
5415         programStats.score = programStats.got_only_move = 0;
5416         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5417
5418         strcpy(bookMove, "move ");
5419         strcat(bookMove, bookHit);
5420         HandleMachineMove(bookMove, &first);
5421   }
5422   return 1;
5423 }
5424
5425 void
5426 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5427      int fromX, fromY, toX, toY;
5428      int promoChar;
5429 {
5430     /* [HGM] This routine was added to allow calling of its two logical
5431        parts from other modules in the old way. Before, UserMoveEvent()
5432        automatically called FinishMove() if the move was OK, and returned
5433        otherwise. I separated the two, in order to make it possible to
5434        slip a promotion popup in between. But that it always needs two
5435        calls, to the first part, (now called UserMoveTest() ), and to
5436        FinishMove if the first part succeeded. Calls that do not need
5437        to do anything in between, can call this routine the old way. 
5438     */
5439     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5440 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5441     if(moveType == AmbiguousMove)
5442         DrawPosition(FALSE, boards[currentMove]);
5443     else if(moveType != ImpossibleMove && moveType != Comment)
5444         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5445 }
5446
5447 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5448 {
5449 //    char * hint = lastHint;
5450     FrontEndProgramStats stats;
5451
5452     stats.which = cps == &first ? 0 : 1;
5453     stats.depth = cpstats->depth;
5454     stats.nodes = cpstats->nodes;
5455     stats.score = cpstats->score;
5456     stats.time = cpstats->time;
5457     stats.pv = cpstats->movelist;
5458     stats.hint = lastHint;
5459     stats.an_move_index = 0;
5460     stats.an_move_count = 0;
5461
5462     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5463         stats.hint = cpstats->move_name;
5464         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5465         stats.an_move_count = cpstats->nr_moves;
5466     }
5467
5468     SetProgramStats( &stats );
5469 }
5470
5471 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5472 {   // [HGM] book: this routine intercepts moves to simulate book replies
5473     char *bookHit = NULL;
5474
5475     //first determine if the incoming move brings opponent into his book
5476     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5477         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5478     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5479     if(bookHit != NULL && !cps->bookSuspend) {
5480         // make sure opponent is not going to reply after receiving move to book position
5481         SendToProgram("force\n", cps);
5482         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5483     }
5484     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5485     // now arrange restart after book miss
5486     if(bookHit) {
5487         // after a book hit we never send 'go', and the code after the call to this routine
5488         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5489         char buf[MSG_SIZ];
5490         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5491         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5492         SendToProgram(buf, cps);
5493         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5494     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5495         SendToProgram("go\n", cps);
5496         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5497     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5498         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5499             SendToProgram("go\n", cps); 
5500         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5501     }
5502     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5503 }
5504
5505 char *savedMessage;
5506 ChessProgramState *savedState;
5507 void DeferredBookMove(void)
5508 {
5509         if(savedState->lastPing != savedState->lastPong)
5510                     ScheduleDelayedEvent(DeferredBookMove, 10);
5511         else
5512         HandleMachineMove(savedMessage, savedState);
5513 }
5514
5515 void
5516 HandleMachineMove(message, cps)
5517      char *message;
5518      ChessProgramState *cps;
5519 {
5520     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5521     char realname[MSG_SIZ];
5522     int fromX, fromY, toX, toY;
5523     ChessMove moveType;
5524     char promoChar;
5525     char *p;
5526     int machineWhite;
5527     char *bookHit;
5528
5529 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5530     /*
5531      * Kludge to ignore BEL characters
5532      */
5533     while (*message == '\007') message++;
5534
5535     /*
5536      * [HGM] engine debug message: ignore lines starting with '#' character
5537      */
5538     if(cps->debug && *message == '#') return;
5539
5540     /*
5541      * Look for book output
5542      */
5543     if (cps == &first && bookRequested) {
5544         if (message[0] == '\t' || message[0] == ' ') {
5545             /* Part of the book output is here; append it */
5546             strcat(bookOutput, message);
5547             strcat(bookOutput, "  \n");
5548             return;
5549         } else if (bookOutput[0] != NULLCHAR) {
5550             /* All of book output has arrived; display it */
5551             char *p = bookOutput;
5552             while (*p != NULLCHAR) {
5553                 if (*p == '\t') *p = ' ';
5554                 p++;
5555             }
5556             DisplayInformation(bookOutput);
5557             bookRequested = FALSE;
5558             /* Fall through to parse the current output */
5559         }
5560     }
5561
5562     /*
5563      * Look for machine move.
5564      */
5565     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5566         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5567     {
5568         /* This method is only useful on engines that support ping */
5569         if (cps->lastPing != cps->lastPong) {
5570           if (gameMode == BeginningOfGame) {
5571             /* Extra move from before last new; ignore */
5572             if (appData.debugMode) {
5573                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5574             }
5575           } else {
5576             if (appData.debugMode) {
5577                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5578                         cps->which, gameMode);
5579             }
5580
5581             SendToProgram("undo\n", cps);
5582           }
5583           return;
5584         }
5585
5586         switch (gameMode) {
5587           case BeginningOfGame:
5588             /* Extra move from before last reset; ignore */
5589             if (appData.debugMode) {
5590                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5591             }
5592             return;
5593
5594           case EndOfGame:
5595           case IcsIdle:
5596           default:
5597             /* Extra move after we tried to stop.  The mode test is
5598                not a reliable way of detecting this problem, but it's
5599                the best we can do on engines that don't support ping.
5600             */
5601             if (appData.debugMode) {
5602                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5603                         cps->which, gameMode);
5604             }
5605             SendToProgram("undo\n", cps);
5606             return;
5607
5608           case MachinePlaysWhite:
5609           case IcsPlayingWhite:
5610             machineWhite = TRUE;
5611             break;
5612
5613           case MachinePlaysBlack:
5614           case IcsPlayingBlack:
5615             machineWhite = FALSE;
5616             break;
5617
5618           case TwoMachinesPlay:
5619             machineWhite = (cps->twoMachinesColor[0] == 'w');
5620             break;
5621         }
5622         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5623             if (appData.debugMode) {
5624                 fprintf(debugFP,
5625                         "Ignoring move out of turn by %s, gameMode %d"
5626                         ", forwardMost %d\n",
5627                         cps->which, gameMode, forwardMostMove);
5628             }
5629             return;
5630         }
5631
5632     if (appData.debugMode) { int f = forwardMostMove;
5633         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5634                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5635     }
5636         if(cps->alphaRank) AlphaRank(machineMove, 4);
5637         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5638                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5639             /* Machine move could not be parsed; ignore it. */
5640             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5641                     machineMove, cps->which);
5642             DisplayError(buf1, 0);
5643             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5644                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5645             if (gameMode == TwoMachinesPlay) {
5646               GameEnds(machineWhite ? BlackWins : WhiteWins,
5647                        buf1, GE_XBOARD);
5648             }
5649             return;
5650         }
5651
5652         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5653         /* So we have to redo legality test with true e.p. status here,  */
5654         /* to make sure an illegal e.p. capture does not slip through,   */
5655         /* to cause a forfeit on a justified illegal-move complaint      */
5656         /* of the opponent.                                              */
5657         if( gameMode==TwoMachinesPlay && appData.testLegality
5658             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5659                                                               ) {
5660            ChessMove moveType;
5661            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5662                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5663                              fromY, fromX, toY, toX, promoChar);
5664             if (appData.debugMode) {
5665                 int i;
5666                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5667                     castlingRights[forwardMostMove][i], castlingRank[i]);
5668                 fprintf(debugFP, "castling rights\n");
5669             }
5670             if(moveType == IllegalMove) {
5671                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5672                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5673                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5674                            buf1, GE_XBOARD);
5675                 return;
5676            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5677            /* [HGM] Kludge to handle engines that send FRC-style castling
5678               when they shouldn't (like TSCP-Gothic) */
5679            switch(moveType) {
5680              case WhiteASideCastleFR:
5681              case BlackASideCastleFR:
5682                toX+=2;
5683                currentMoveString[2]++;
5684                break;
5685              case WhiteHSideCastleFR:
5686              case BlackHSideCastleFR:
5687                toX--;
5688                currentMoveString[2]--;
5689                break;
5690              default: ; // nothing to do, but suppresses warning of pedantic compilers
5691            }
5692         }
5693         hintRequested = FALSE;
5694         lastHint[0] = NULLCHAR;
5695         bookRequested = FALSE;
5696         /* Program may be pondering now */
5697         cps->maybeThinking = TRUE;
5698         if (cps->sendTime == 2) cps->sendTime = 1;
5699         if (cps->offeredDraw) cps->offeredDraw--;
5700
5701 #if ZIPPY
5702         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5703             first.initDone) {
5704           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5705           ics_user_moved = 1;
5706           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5707                 char buf[3*MSG_SIZ];
5708
5709                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5710                         programStats.score / 100.,
5711                         programStats.depth,
5712                         programStats.time / 100.,
5713                         (unsigned int)programStats.nodes,
5714                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5715                         programStats.movelist);
5716                 SendToICS(buf);
5717 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5718           }
5719         }
5720 #endif
5721         /* currentMoveString is set as a side-effect of ParseOneMove */
5722         strcpy(machineMove, currentMoveString);
5723         strcat(machineMove, "\n");
5724         strcpy(moveList[forwardMostMove], machineMove);
5725
5726         /* [AS] Save move info and clear stats for next move */
5727         pvInfoList[ forwardMostMove ].score = programStats.score;
5728         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5729         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5730         ClearProgramStats();
5731         thinkOutput[0] = NULLCHAR;
5732         hiddenThinkOutputState = 0;
5733
5734         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5735
5736         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5737         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5738             int count = 0;
5739
5740             while( count < adjudicateLossPlies ) {
5741                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5742
5743                 if( count & 1 ) {
5744                     score = -score; /* Flip score for winning side */
5745                 }
5746
5747                 if( score > adjudicateLossThreshold ) {
5748                     break;
5749                 }
5750
5751                 count++;
5752             }
5753
5754             if( count >= adjudicateLossPlies ) {
5755                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5756
5757                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5758                     "Xboard adjudication", 
5759                     GE_XBOARD );
5760
5761                 return;
5762             }
5763         }
5764
5765         if( gameMode == TwoMachinesPlay ) {
5766           // [HGM] some adjudications useful with buggy engines
5767             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5768           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5769
5770
5771             if( appData.testLegality )
5772             {   /* [HGM] Some more adjudications for obstinate engines */
5773                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5774                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5775                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5776                 static int moveCount = 6;
5777                 ChessMove result;
5778                 char *reason = NULL;
5779
5780                 /* Count what is on board. */
5781                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5782                 {   ChessSquare p = boards[forwardMostMove][i][j];
5783                     int m=i;
5784
5785                     switch((int) p)
5786                     {   /* count B,N,R and other of each side */
5787                         case WhiteKing:
5788                         case BlackKing:
5789                              NrK++; break; // [HGM] atomic: count Kings
5790                         case WhiteKnight:
5791                              NrWN++; break;
5792                         case WhiteBishop:
5793                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5794                              bishopsColor |= 1 << ((i^j)&1);
5795                              NrWB++; break;
5796                         case BlackKnight:
5797                              NrBN++; break;
5798                         case BlackBishop:
5799                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5800                              bishopsColor |= 1 << ((i^j)&1);
5801                              NrBB++; break;
5802                         case WhiteRook:
5803                              NrWR++; break;
5804                         case BlackRook:
5805                              NrBR++; break;
5806                         case WhiteQueen:
5807                              NrWQ++; break;
5808                         case BlackQueen:
5809                              NrBQ++; break;
5810                         case EmptySquare: 
5811                              break;
5812                         case BlackPawn:
5813                              m = 7-i;
5814                         case WhitePawn:
5815                              PawnAdvance += m; NrPawns++;
5816                     }
5817                     NrPieces += (p != EmptySquare);
5818                     NrW += ((int)p < (int)BlackPawn);
5819                     if(gameInfo.variant == VariantXiangqi && 
5820                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5821                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5822                         NrW -= ((int)p < (int)BlackPawn);
5823                     }
5824                 }
5825
5826                 /* Some material-based adjudications that have to be made before stalemate test */
5827                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5828                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5829                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5830                      if(appData.checkMates) {
5831                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5832                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5833                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5834                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5835                          return;
5836                      }
5837                 }
5838
5839                 /* Bare King in Shatranj (loses) or Losers (wins) */
5840                 if( NrW == 1 || NrPieces - NrW == 1) {
5841                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5842                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5843                      if(appData.checkMates) {
5844                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5845                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5847                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5848                          return;
5849                      }
5850                   } else
5851                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5852                   {    /* bare King */
5853                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5854                         if(appData.checkMates) {
5855                             /* but only adjudicate if adjudication enabled */
5856                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5857                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5859                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5860                             return;
5861                         }
5862                   }
5863                 } else bare = 1;
5864
5865
5866             // don't wait for engine to announce game end if we can judge ourselves
5867             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5868                                        castlingRights[forwardMostMove]) ) {
5869               case MT_CHECK:
5870                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5871                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5872                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5873                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5874                             checkCnt++;
5875                         if(checkCnt >= 2) {
5876                             reason = "Xboard adjudication: 3rd check";
5877                             epStatus[forwardMostMove] = EP_CHECKMATE;
5878                             break;
5879                         }
5880                     }
5881                 }
5882               case MT_NONE:
5883               default:
5884                 break;
5885               case MT_STALEMATE:
5886               case MT_STAINMATE:
5887                 reason = "Xboard adjudication: Stalemate";
5888                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5889                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5890                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5891                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5892                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5893                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5894                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5895                                                                         EP_CHECKMATE : EP_WINS);
5896                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5897                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5898                 }
5899                 break;
5900               case MT_CHECKMATE:
5901                 reason = "Xboard adjudication: Checkmate";
5902                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5903                 break;
5904             }
5905
5906                 switch(i = epStatus[forwardMostMove]) {
5907                     case EP_STALEMATE:
5908                         result = GameIsDrawn; break;
5909                     case EP_CHECKMATE:
5910                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5911                     case EP_WINS:
5912                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5913                     default:
5914                         result = (ChessMove) 0;
5915                 }
5916                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5917                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5918                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5919                     GameEnds( result, reason, GE_XBOARD );
5920                     return;
5921                 }
5922
5923                 /* Next absolutely insufficient mating material. */
5924                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5925                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5926                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5927                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5928                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5929
5930                      /* always flag draws, for judging claims */
5931                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5932
5933                      if(appData.materialDraws) {
5934                          /* but only adjudicate them if adjudication enabled */
5935                          SendToProgram("force\n", cps->other); // suppress reply
5936                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5937                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5938                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5939                          return;
5940                      }
5941                 }
5942
5943                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5944                 if(NrPieces == 4 && 
5945                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5946                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5947                    || NrWN==2 || NrBN==2     /* KNNK */
5948                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5949                   ) ) {
5950                      if(--moveCount < 0 && appData.trivialDraws)
5951                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5952                           SendToProgram("force\n", cps->other); // suppress reply
5953                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5954                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5955                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5956                           return;
5957                      }
5958                 } else moveCount = 6;
5959             }
5960           }
5961 #if 1
5962     if (appData.debugMode) { int i;
5963       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5964               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5965               appData.drawRepeats);
5966       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5967            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5968
5969     }
5970 #endif
5971                 /* Check for rep-draws */
5972                 count = 0;
5973                 for(k = forwardMostMove-2;
5974                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5975                         epStatus[k] < EP_UNKNOWN &&
5976                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5977                     k-=2)
5978                 {   int rights=0;
5979 #if 0
5980     if (appData.debugMode) {
5981       fprintf(debugFP, " loop\n");
5982     }
5983 #endif
5984                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5985 #if 0
5986     if (appData.debugMode) {
5987       fprintf(debugFP, "match\n");
5988     }
5989 #endif
5990                         /* compare castling rights */
5991                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5992                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5993                                 rights++; /* King lost rights, while rook still had them */
5994                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5995                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5996                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5997                                    rights++; /* but at least one rook lost them */
5998                         }
5999                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6000                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6001                                 rights++; 
6002                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6003                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6004                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6005                                    rights++;
6006                         }
6007 #if 0
6008     if (appData.debugMode) {
6009       for(i=0; i<nrCastlingRights; i++)
6010       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6011     }
6012
6013     if (appData.debugMode) {
6014       fprintf(debugFP, " %d %d\n", rights, k);
6015     }
6016 #endif
6017                         if( rights == 0 && ++count > appData.drawRepeats-2
6018                             && appData.drawRepeats > 1) {
6019                              /* adjudicate after user-specified nr of repeats */
6020                              SendToProgram("force\n", cps->other); // suppress reply
6021                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6022                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6024                                 // [HGM] xiangqi: check for forbidden perpetuals
6025                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6026                                 for(m=forwardMostMove; m>k; m-=2) {
6027                                     if(MateTest(boards[m], PosFlags(m), 
6028                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6029                                         ourPerpetual = 0; // the current mover did not always check
6030                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6031                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6032                                         hisPerpetual = 0; // the opponent did not always check
6033                                 }
6034                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6035                                                                         ourPerpetual, hisPerpetual);
6036                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6037                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6038                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6039                                     return;
6040                                 }
6041                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6042                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6043                                 // Now check for perpetual chases
6044                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6045                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6046                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6047                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6048                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6049                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6050                                         return;
6051                                     }
6052                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6053                                         break; // Abort repetition-checking loop.
6054                                 }
6055                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6056                              }
6057                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6058                              return;
6059                         }
6060                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6061                              epStatus[forwardMostMove] = EP_REP_DRAW;
6062                     }
6063                 }
6064
6065                 /* Now we test for 50-move draws. Determine ply count */
6066                 count = forwardMostMove;
6067                 /* look for last irreversble move */
6068                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6069                     count--;
6070                 /* if we hit starting position, add initial plies */
6071                 if( count == backwardMostMove )
6072                     count -= initialRulePlies;
6073                 count = forwardMostMove - count; 
6074                 if( count >= 100)
6075                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6076                          /* this is used to judge if draw claims are legal */
6077                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6078                          SendToProgram("force\n", cps->other); // suppress reply
6079                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6080                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6082                          return;
6083                 }
6084
6085                 /* if draw offer is pending, treat it as a draw claim
6086                  * when draw condition present, to allow engines a way to
6087                  * claim draws before making their move to avoid a race
6088                  * condition occurring after their move
6089                  */
6090                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6091                          char *p = NULL;
6092                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6093                              p = "Draw claim: 50-move rule";
6094                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6095                              p = "Draw claim: 3-fold repetition";
6096                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6097                              p = "Draw claim: insufficient mating material";
6098                          if( p != NULL ) {
6099                              SendToProgram("force\n", cps->other); // suppress reply
6100                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6101                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6102                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103                              return;
6104                          }
6105                 }
6106
6107
6108                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6109                     SendToProgram("force\n", cps->other); // suppress reply
6110                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6111                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112
6113                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6114
6115                     return;
6116                 }
6117         }
6118
6119         bookHit = NULL;
6120         if (gameMode == TwoMachinesPlay) {
6121             /* [HGM] relaying draw offers moved to after reception of move */
6122             /* and interpreting offer as claim if it brings draw condition */
6123             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6124                 SendToProgram("draw\n", cps->other);
6125             }
6126             if (cps->other->sendTime) {
6127                 SendTimeRemaining(cps->other,
6128                                   cps->other->twoMachinesColor[0] == 'w');
6129             }
6130             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6131             if (firstMove && !bookHit) {
6132                 firstMove = FALSE;
6133                 if (cps->other->useColors) {
6134                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6135                 }
6136                 SendToProgram("go\n", cps->other);
6137             }
6138             cps->other->maybeThinking = TRUE;
6139         }
6140
6141         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6142         
6143         if (!pausing && appData.ringBellAfterMoves) {
6144             RingBell();
6145         }
6146
6147         /* 
6148          * Reenable menu items that were disabled while
6149          * machine was thinking
6150          */
6151         if (gameMode != TwoMachinesPlay)
6152             SetUserThinkingEnables();
6153
6154         // [HGM] book: after book hit opponent has received move and is now in force mode
6155         // force the book reply into it, and then fake that it outputted this move by jumping
6156         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6157         if(bookHit) {
6158                 static char bookMove[MSG_SIZ]; // a bit generous?
6159
6160                 strcpy(bookMove, "move ");
6161                 strcat(bookMove, bookHit);
6162                 message = bookMove;
6163                 cps = cps->other;
6164                 programStats.nodes = programStats.depth = programStats.time = 
6165                 programStats.score = programStats.got_only_move = 0;
6166                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6167
6168                 if(cps->lastPing != cps->lastPong) {
6169                     savedMessage = message; // args for deferred call
6170                     savedState = cps;
6171                     ScheduleDelayedEvent(DeferredBookMove, 10);
6172                     return;
6173                 }
6174                 goto FakeBookMove;
6175         }
6176
6177         return;
6178     }
6179
6180     /* Set special modes for chess engines.  Later something general
6181      *  could be added here; for now there is just one kludge feature,
6182      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6183      *  when "xboard" is given as an interactive command.
6184      */
6185     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6186         cps->useSigint = FALSE;
6187         cps->useSigterm = FALSE;
6188     }
6189     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6190       ParseFeatures(message+8, cps);
6191       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6192     }
6193
6194     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6195      * want this, I was asked to put it in, and obliged.
6196      */
6197     if (!strncmp(message, "setboard ", 9)) {
6198         Board initial_position; int i;
6199
6200         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6201
6202         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6203             DisplayError(_("Bad FEN received from engine"), 0);
6204             return ;
6205         } else {
6206            Reset(FALSE, FALSE);
6207            CopyBoard(boards[0], initial_position);
6208            initialRulePlies = FENrulePlies;
6209            epStatus[0] = FENepStatus;
6210            for( i=0; i<nrCastlingRights; i++ )
6211                 castlingRights[0][i] = FENcastlingRights[i];
6212            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6213            else gameMode = MachinePlaysBlack;                 
6214            DrawPosition(FALSE, boards[currentMove]);
6215         }
6216         return;
6217     }
6218
6219     /*
6220      * Look for communication commands
6221      */
6222     if (!strncmp(message, "telluser ", 9)) {
6223         DisplayNote(message + 9);
6224         return;
6225     }
6226     if (!strncmp(message, "tellusererror ", 14)) {
6227         DisplayError(message + 14, 0);
6228         return;
6229     }
6230     if (!strncmp(message, "tellopponent ", 13)) {
6231       if (appData.icsActive) {
6232         if (loggedOn) {
6233           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6234           SendToICS(buf1);
6235         }
6236       } else {
6237         DisplayNote(message + 13);
6238       }
6239       return;
6240     }
6241     if (!strncmp(message, "tellothers ", 11)) {
6242       if (appData.icsActive) {
6243         if (loggedOn) {
6244           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6245           SendToICS(buf1);
6246         }
6247       }
6248       return;
6249     }
6250     if (!strncmp(message, "tellall ", 8)) {
6251       if (appData.icsActive) {
6252         if (loggedOn) {
6253           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6254           SendToICS(buf1);
6255         }
6256       } else {
6257         DisplayNote(message + 8);
6258       }
6259       return;
6260     }
6261     if (strncmp(message, "warning", 7) == 0) {
6262         /* Undocumented feature, use tellusererror in new code */
6263         DisplayError(message, 0);
6264         return;
6265     }
6266     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6267         strcpy(realname, cps->tidy);
6268         strcat(realname, " query");
6269         AskQuestion(realname, buf2, buf1, cps->pr);
6270         return;
6271     }
6272     /* Commands from the engine directly to ICS.  We don't allow these to be 
6273      *  sent until we are logged on. Crafty kibitzes have been known to 
6274      *  interfere with the login process.
6275      */
6276     if (loggedOn) {
6277         if (!strncmp(message, "tellics ", 8)) {
6278             SendToICS(message + 8);
6279             SendToICS("\n");
6280             return;
6281         }
6282         if (!strncmp(message, "tellicsnoalias ", 15)) {
6283             SendToICS(ics_prefix);
6284             SendToICS(message + 15);
6285             SendToICS("\n");
6286             return;
6287         }
6288         /* The following are for backward compatibility only */
6289         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6290             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6291             SendToICS(ics_prefix);
6292             SendToICS(message);
6293             SendToICS("\n");
6294             return;
6295         }
6296     }
6297     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6298         return;
6299     }
6300     /*
6301      * If the move is illegal, cancel it and redraw the board.
6302      * Also deal with other error cases.  Matching is rather loose
6303      * here to accommodate engines written before the spec.
6304      */
6305     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6306         strncmp(message, "Error", 5) == 0) {
6307         if (StrStr(message, "name") || 
6308             StrStr(message, "rating") || StrStr(message, "?") ||
6309             StrStr(message, "result") || StrStr(message, "board") ||
6310             StrStr(message, "bk") || StrStr(message, "computer") ||
6311             StrStr(message, "variant") || StrStr(message, "hint") ||
6312             StrStr(message, "random") || StrStr(message, "depth") ||
6313             StrStr(message, "accepted")) {
6314             return;
6315         }
6316         if (StrStr(message, "protover")) {
6317           /* Program is responding to input, so it's apparently done
6318              initializing, and this error message indicates it is
6319              protocol version 1.  So we don't need to wait any longer
6320              for it to initialize and send feature commands. */
6321           FeatureDone(cps, 1);
6322           cps->protocolVersion = 1;
6323           return;
6324         }
6325         cps->maybeThinking = FALSE;
6326
6327         if (StrStr(message, "draw")) {
6328             /* Program doesn't have "draw" command */
6329             cps->sendDrawOffers = 0;
6330             return;
6331         }
6332         if (cps->sendTime != 1 &&
6333             (StrStr(message, "time") || StrStr(message, "otim"))) {
6334           /* Program apparently doesn't have "time" or "otim" command */
6335           cps->sendTime = 0;
6336           return;
6337         }
6338         if (StrStr(message, "analyze")) {
6339             cps->analysisSupport = FALSE;
6340             cps->analyzing = FALSE;
6341             Reset(FALSE, TRUE);
6342             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6343             DisplayError(buf2, 0);
6344             return;
6345         }
6346         if (StrStr(message, "(no matching move)st")) {
6347           /* Special kludge for GNU Chess 4 only */
6348           cps->stKludge = TRUE;
6349           SendTimeControl(cps, movesPerSession, timeControl,
6350                           timeIncrement, appData.searchDepth,
6351                           searchTime);
6352           return;
6353         }
6354         if (StrStr(message, "(no matching move)sd")) {
6355           /* Special kludge for GNU Chess 4 only */
6356           cps->sdKludge = TRUE;
6357           SendTimeControl(cps, movesPerSession, timeControl,
6358                           timeIncrement, appData.searchDepth,
6359                           searchTime);
6360           return;
6361         }
6362         if (!StrStr(message, "llegal")) {
6363             return;
6364         }
6365         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6366             gameMode == IcsIdle) return;
6367         if (forwardMostMove <= backwardMostMove) return;
6368 #if 0
6369         /* Following removed: it caused a bug where a real illegal move
6370            message in analyze mored would be ignored. */
6371         if (cps == &first && programStats.ok_to_send == 0) {
6372             /* Bogus message from Crafty responding to "."  This filtering
6373                can miss some of the bad messages, but fortunately the bug 
6374                is fixed in current Crafty versions, so it doesn't matter. */
6375             return;
6376         }
6377 #endif
6378         if (pausing) PauseEvent();
6379       if(appData.forceIllegal) {
6380             // [HGM] illegal: machine refused move; force position after move into it
6381           SendToProgram("force\n", cps);
6382           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6383                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6384                 // when black is to move, while there might be nothing on a2 or black
6385                 // might already have the move. So send the board as if white has the move.
6386                 // But first we must change the stm of the engine, as it refused the last move
6387                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6388                 if(WhiteOnMove(forwardMostMove)) {
6389                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6390                     SendBoard(cps, forwardMostMove); // kludgeless board
6391                 } else {
6392                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6393                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6394                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6395                 }
6396           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6397             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6398                  gameMode == TwoMachinesPlay)
6399               SendToProgram("go\n", cps);
6400             return;
6401       } else
6402         if (gameMode == PlayFromGameFile) {
6403             /* Stop reading this game file */
6404             gameMode = EditGame;
6405             ModeHighlight();
6406         }
6407         currentMove = --forwardMostMove;
6408         DisplayMove(currentMove-1); /* before DisplayMoveError */
6409         SwitchClocks();
6410         DisplayBothClocks();
6411         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6412                 parseList[currentMove], cps->which);
6413         DisplayMoveError(buf1);
6414         DrawPosition(FALSE, boards[currentMove]);
6415
6416         /* [HGM] illegal-move claim should forfeit game when Xboard */
6417         /* only passes fully legal moves                            */
6418         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6419             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6420                                 "False illegal-move claim", GE_XBOARD );
6421         }
6422         return;
6423     }
6424     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6425         /* Program has a broken "time" command that
6426            outputs a string not ending in newline.
6427            Don't use it. */
6428         cps->sendTime = 0;
6429     }
6430     
6431     /*
6432      * If chess program startup fails, exit with an error message.
6433      * Attempts to recover here are futile.
6434      */
6435     if ((StrStr(message, "unknown host") != NULL)
6436         || (StrStr(message, "No remote directory") != NULL)
6437         || (StrStr(message, "not found") != NULL)
6438         || (StrStr(message, "No such file") != NULL)
6439         || (StrStr(message, "can't alloc") != NULL)
6440         || (StrStr(message, "Permission denied") != NULL)) {
6441
6442         cps->maybeThinking = FALSE;
6443         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6444                 cps->which, cps->program, cps->host, message);
6445         RemoveInputSource(cps->isr);
6446         DisplayFatalError(buf1, 0, 1);
6447         return;
6448     }
6449     
6450     /* 
6451      * Look for hint output
6452      */
6453     if (sscanf(message, "Hint: %s", buf1) == 1) {
6454         if (cps == &first && hintRequested) {
6455             hintRequested = FALSE;
6456             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6457                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6458                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6459                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6460                                     fromY, fromX, toY, toX, promoChar, buf1);
6461                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6462                 DisplayInformation(buf2);
6463             } else {
6464                 /* Hint move could not be parsed!? */
6465               snprintf(buf2, sizeof(buf2),
6466                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6467                         buf1, cps->which);
6468                 DisplayError(buf2, 0);
6469             }
6470         } else {
6471             strcpy(lastHint, buf1);
6472         }
6473         return;
6474     }
6475
6476     /*
6477      * Ignore other messages if game is not in progress
6478      */
6479     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6480         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6481
6482     /*
6483      * look for win, lose, draw, or draw offer
6484      */
6485     if (strncmp(message, "1-0", 3) == 0) {
6486         char *p, *q, *r = "";
6487         p = strchr(message, '{');
6488         if (p) {
6489             q = strchr(p, '}');
6490             if (q) {
6491                 *q = NULLCHAR;
6492                 r = p + 1;
6493             }
6494         }
6495         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6496         return;
6497     } else if (strncmp(message, "0-1", 3) == 0) {
6498         char *p, *q, *r = "";
6499         p = strchr(message, '{');
6500         if (p) {
6501             q = strchr(p, '}');
6502             if (q) {
6503                 *q = NULLCHAR;
6504                 r = p + 1;
6505             }
6506         }
6507         /* Kludge for Arasan 4.1 bug */
6508         if (strcmp(r, "Black resigns") == 0) {
6509             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6510             return;
6511         }
6512         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6513         return;
6514     } else if (strncmp(message, "1/2", 3) == 0) {
6515         char *p, *q, *r = "";
6516         p = strchr(message, '{');
6517         if (p) {
6518             q = strchr(p, '}');
6519             if (q) {
6520                 *q = NULLCHAR;
6521                 r = p + 1;
6522             }
6523         }
6524             
6525         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6526         return;
6527
6528     } else if (strncmp(message, "White resign", 12) == 0) {
6529         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6530         return;
6531     } else if (strncmp(message, "Black resign", 12) == 0) {
6532         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6533         return;
6534     } else if (strncmp(message, "White matches", 13) == 0 ||
6535                strncmp(message, "Black matches", 13) == 0   ) {
6536         /* [HGM] ignore GNUShogi noises */
6537         return;
6538     } else if (strncmp(message, "White", 5) == 0 &&
6539                message[5] != '(' &&
6540                StrStr(message, "Black") == NULL) {
6541         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6542         return;
6543     } else if (strncmp(message, "Black", 5) == 0 &&
6544                message[5] != '(') {
6545         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6546         return;
6547     } else if (strcmp(message, "resign") == 0 ||
6548                strcmp(message, "computer resigns") == 0) {
6549         switch (gameMode) {
6550           case MachinePlaysBlack:
6551           case IcsPlayingBlack:
6552             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6553             break;
6554           case MachinePlaysWhite:
6555           case IcsPlayingWhite:
6556             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6557             break;
6558           case TwoMachinesPlay:
6559             if (cps->twoMachinesColor[0] == 'w')
6560               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6561             else
6562               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6563             break;
6564           default:
6565             /* can't happen */
6566             break;
6567         }
6568         return;
6569     } else if (strncmp(message, "opponent mates", 14) == 0) {
6570         switch (gameMode) {
6571           case MachinePlaysBlack:
6572           case IcsPlayingBlack:
6573             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6574             break;
6575           case MachinePlaysWhite:
6576           case IcsPlayingWhite:
6577             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6578             break;
6579           case TwoMachinesPlay:
6580             if (cps->twoMachinesColor[0] == 'w')
6581               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6582             else
6583               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6584             break;
6585           default:
6586             /* can't happen */
6587             break;
6588         }
6589         return;
6590     } else if (strncmp(message, "computer mates", 14) == 0) {
6591         switch (gameMode) {
6592           case MachinePlaysBlack:
6593           case IcsPlayingBlack:
6594             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6595             break;
6596           case MachinePlaysWhite:
6597           case IcsPlayingWhite:
6598             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6599             break;
6600           case TwoMachinesPlay:
6601             if (cps->twoMachinesColor[0] == 'w')
6602               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6603             else
6604               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6605             break;
6606           default:
6607             /* can't happen */
6608             break;
6609         }
6610         return;
6611     } else if (strncmp(message, "checkmate", 9) == 0) {
6612         if (WhiteOnMove(forwardMostMove)) {
6613             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6614         } else {
6615             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6616         }
6617         return;
6618     } else if (strstr(message, "Draw") != NULL ||
6619                strstr(message, "game is a draw") != NULL) {
6620         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6621         return;
6622     } else if (strstr(message, "offer") != NULL &&
6623                strstr(message, "draw") != NULL) {
6624 #if ZIPPY
6625         if (appData.zippyPlay && first.initDone) {
6626             /* Relay offer to ICS */
6627             SendToICS(ics_prefix);
6628             SendToICS("draw\n");
6629         }
6630 #endif
6631         cps->offeredDraw = 2; /* valid until this engine moves twice */
6632         if (gameMode == TwoMachinesPlay) {
6633             if (cps->other->offeredDraw) {
6634                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6635             /* [HGM] in two-machine mode we delay relaying draw offer      */
6636             /* until after we also have move, to see if it is really claim */
6637             }
6638 #if 0
6639               else {
6640                 if (cps->other->sendDrawOffers) {
6641                     SendToProgram("draw\n", cps->other);
6642                 }
6643             }
6644 #endif
6645         } else if (gameMode == MachinePlaysWhite ||
6646                    gameMode == MachinePlaysBlack) {
6647           if (userOfferedDraw) {
6648             DisplayInformation(_("Machine accepts your draw offer"));
6649             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6650           } else {
6651             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6652           }
6653         }
6654     }
6655
6656     
6657     /*
6658      * Look for thinking output
6659      */
6660     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6661           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6662                                 ) {
6663         int plylev, mvleft, mvtot, curscore, time;
6664         char mvname[MOVE_LEN];
6665         u64 nodes; // [DM]
6666         char plyext;
6667         int ignore = FALSE;
6668         int prefixHint = FALSE;
6669         mvname[0] = NULLCHAR;
6670
6671         switch (gameMode) {
6672           case MachinePlaysBlack:
6673           case IcsPlayingBlack:
6674             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6675             break;
6676           case MachinePlaysWhite:
6677           case IcsPlayingWhite:
6678             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6679             break;
6680           case AnalyzeMode:
6681           case AnalyzeFile:
6682             break;
6683           case IcsObserving: /* [DM] icsEngineAnalyze */
6684             if (!appData.icsEngineAnalyze) ignore = TRUE;
6685             break;
6686           case TwoMachinesPlay:
6687             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6688                 ignore = TRUE;
6689             }
6690             break;
6691           default:
6692             ignore = TRUE;
6693             break;
6694         }
6695
6696         if (!ignore) {
6697             buf1[0] = NULLCHAR;
6698             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6699                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6700
6701                 if (plyext != ' ' && plyext != '\t') {
6702                     time *= 100;
6703                 }
6704
6705                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6706                 if( cps->scoreIsAbsolute && 
6707                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6708                 {
6709                     curscore = -curscore;
6710                 }
6711
6712
6713                 programStats.depth = plylev;
6714                 programStats.nodes = nodes;
6715                 programStats.time = time;
6716                 programStats.score = curscore;
6717                 programStats.got_only_move = 0;
6718
6719                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6720                         int ticklen;
6721
6722                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6723                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6724                         if(WhiteOnMove(forwardMostMove)) 
6725                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6726                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6727                 }
6728
6729                 /* Buffer overflow protection */
6730                 if (buf1[0] != NULLCHAR) {
6731                     if (strlen(buf1) >= sizeof(programStats.movelist)
6732                         && appData.debugMode) {
6733                         fprintf(debugFP,
6734                                 "PV is too long; using the first %d bytes.\n",
6735                                 sizeof(programStats.movelist) - 1);
6736                     }
6737
6738                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6739                 } else {
6740                     sprintf(programStats.movelist, " no PV\n");
6741                 }
6742
6743                 if (programStats.seen_stat) {
6744                     programStats.ok_to_send = 1;
6745                 }
6746
6747                 if (strchr(programStats.movelist, '(') != NULL) {
6748                     programStats.line_is_book = 1;
6749                     programStats.nr_moves = 0;
6750                     programStats.moves_left = 0;
6751                 } else {
6752                     programStats.line_is_book = 0;
6753                 }
6754
6755                 SendProgramStatsToFrontend( cps, &programStats );
6756
6757                 /* 
6758                     [AS] Protect the thinkOutput buffer from overflow... this
6759                     is only useful if buf1 hasn't overflowed first!
6760                 */
6761                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6762                         plylev, 
6763                         (gameMode == TwoMachinesPlay ?
6764                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6765                         ((double) curscore) / 100.0,
6766                         prefixHint ? lastHint : "",
6767                         prefixHint ? " " : "" );
6768
6769                 if( buf1[0] != NULLCHAR ) {
6770                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6771
6772                     if( strlen(buf1) > max_len ) {
6773                         if( appData.debugMode) {
6774                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6775                         }
6776                         buf1[max_len+1] = '\0';
6777                     }
6778
6779                     strcat( thinkOutput, buf1 );
6780                 }
6781
6782                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6783                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6784                     DisplayMove(currentMove - 1);
6785                     DisplayAnalysis();
6786                 }
6787                 return;
6788
6789             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6790                 /* crafty (9.25+) says "(only move) <move>"
6791                  * if there is only 1 legal move
6792                  */
6793                 sscanf(p, "(only move) %s", buf1);
6794                 sprintf(thinkOutput, "%s (only move)", buf1);
6795                 sprintf(programStats.movelist, "%s (only move)", buf1);
6796                 programStats.depth = 1;
6797                 programStats.nr_moves = 1;
6798                 programStats.moves_left = 1;
6799                 programStats.nodes = 1;
6800                 programStats.time = 1;
6801                 programStats.got_only_move = 1;
6802
6803                 /* Not really, but we also use this member to
6804                    mean "line isn't going to change" (Crafty
6805                    isn't searching, so stats won't change) */
6806                 programStats.line_is_book = 1;
6807
6808                 SendProgramStatsToFrontend( cps, &programStats );
6809                 
6810                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6811                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6812                     DisplayMove(currentMove - 1);
6813                     DisplayAnalysis();
6814                 }
6815                 return;
6816             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6817                               &time, &nodes, &plylev, &mvleft,
6818                               &mvtot, mvname) >= 5) {
6819                 /* The stat01: line is from Crafty (9.29+) in response
6820                    to the "." command */
6821                 programStats.seen_stat = 1;
6822                 cps->maybeThinking = TRUE;
6823
6824                 if (programStats.got_only_move || !appData.periodicUpdates)
6825                   return;
6826
6827                 programStats.depth = plylev;
6828                 programStats.time = time;
6829                 programStats.nodes = nodes;
6830                 programStats.moves_left = mvleft;
6831                 programStats.nr_moves = mvtot;
6832                 strcpy(programStats.move_name, mvname);
6833                 programStats.ok_to_send = 1;
6834                 programStats.movelist[0] = '\0';
6835
6836                 SendProgramStatsToFrontend( cps, &programStats );
6837
6838                 DisplayAnalysis();
6839                 return;
6840
6841             } else if (strncmp(message,"++",2) == 0) {
6842                 /* Crafty 9.29+ outputs this */
6843                 programStats.got_fail = 2;
6844                 return;
6845
6846             } else if (strncmp(message,"--",2) == 0) {
6847                 /* Crafty 9.29+ outputs this */
6848                 programStats.got_fail = 1;
6849                 return;
6850
6851             } else if (thinkOutput[0] != NULLCHAR &&
6852                        strncmp(message, "    ", 4) == 0) {
6853                 unsigned message_len;
6854
6855                 p = message;
6856                 while (*p && *p == ' ') p++;
6857
6858                 message_len = strlen( p );
6859
6860                 /* [AS] Avoid buffer overflow */
6861                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6862                     strcat(thinkOutput, " ");
6863                     strcat(thinkOutput, p);
6864                 }
6865
6866                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6867                     strcat(programStats.movelist, " ");
6868                     strcat(programStats.movelist, p);
6869                 }
6870
6871                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6872                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6873                     DisplayMove(currentMove - 1);
6874                     DisplayAnalysis();
6875                 }
6876                 return;
6877             }
6878         }
6879         else {
6880             buf1[0] = NULLCHAR;
6881
6882             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6883                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6884             {
6885                 ChessProgramStats cpstats;
6886
6887                 if (plyext != ' ' && plyext != '\t') {
6888                     time *= 100;
6889                 }
6890
6891                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6892                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6893                     curscore = -curscore;
6894                 }
6895
6896                 cpstats.depth = plylev;
6897                 cpstats.nodes = nodes;
6898                 cpstats.time = time;
6899                 cpstats.score = curscore;
6900                 cpstats.got_only_move = 0;
6901                 cpstats.movelist[0] = '\0';
6902
6903                 if (buf1[0] != NULLCHAR) {
6904                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6905                 }
6906
6907                 cpstats.ok_to_send = 0;
6908                 cpstats.line_is_book = 0;
6909                 cpstats.nr_moves = 0;
6910                 cpstats.moves_left = 0;
6911
6912                 SendProgramStatsToFrontend( cps, &cpstats );
6913             }
6914         }
6915     }
6916 }
6917
6918
6919 /* Parse a game score from the character string "game", and
6920    record it as the history of the current game.  The game
6921    score is NOT assumed to start from the standard position. 
6922    The display is not updated in any way.
6923    */
6924 void
6925 ParseGameHistory(game)
6926      char *game;
6927 {
6928     ChessMove moveType;
6929     int fromX, fromY, toX, toY, boardIndex;
6930     char promoChar;
6931     char *p, *q;
6932     char buf[MSG_SIZ];
6933
6934     if (appData.debugMode)
6935       fprintf(debugFP, "Parsing game history: %s\n", game);
6936
6937     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6938     gameInfo.site = StrSave(appData.icsHost);
6939     gameInfo.date = PGNDate();
6940     gameInfo.round = StrSave("-");
6941
6942     /* Parse out names of players */
6943     while (*game == ' ') game++;
6944     p = buf;
6945     while (*game != ' ') *p++ = *game++;
6946     *p = NULLCHAR;
6947     gameInfo.white = StrSave(buf);
6948     while (*game == ' ') game++;
6949     p = buf;
6950     while (*game != ' ' && *game != '\n') *p++ = *game++;
6951     *p = NULLCHAR;
6952     gameInfo.black = StrSave(buf);
6953
6954     /* Parse moves */
6955     boardIndex = blackPlaysFirst ? 1 : 0;
6956     yynewstr(game);
6957     for (;;) {
6958         yyboardindex = boardIndex;
6959         moveType = (ChessMove) yylex();
6960         switch (moveType) {
6961           case IllegalMove:             /* maybe suicide chess, etc. */
6962   if (appData.debugMode) {
6963     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6964     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6965     setbuf(debugFP, NULL);
6966   }
6967           case WhitePromotionChancellor:
6968           case BlackPromotionChancellor:
6969           case WhitePromotionArchbishop:
6970           case BlackPromotionArchbishop:
6971           case WhitePromotionQueen:
6972           case BlackPromotionQueen:
6973           case WhitePromotionRook:
6974           case BlackPromotionRook:
6975           case WhitePromotionBishop:
6976           case BlackPromotionBishop:
6977           case WhitePromotionKnight:
6978           case BlackPromotionKnight:
6979           case WhitePromotionKing:
6980           case BlackPromotionKing:
6981           case NormalMove:
6982           case WhiteCapturesEnPassant:
6983           case BlackCapturesEnPassant:
6984           case WhiteKingSideCastle:
6985           case WhiteQueenSideCastle:
6986           case BlackKingSideCastle:
6987           case BlackQueenSideCastle:
6988           case WhiteKingSideCastleWild:
6989           case WhiteQueenSideCastleWild:
6990           case BlackKingSideCastleWild:
6991           case BlackQueenSideCastleWild:
6992           /* PUSH Fabien */
6993           case WhiteHSideCastleFR:
6994           case WhiteASideCastleFR:
6995           case BlackHSideCastleFR:
6996           case BlackASideCastleFR:
6997           /* POP Fabien */
6998             fromX = currentMoveString[0] - AAA;
6999             fromY = currentMoveString[1] - ONE;
7000             toX = currentMoveString[2] - AAA;
7001             toY = currentMoveString[3] - ONE;
7002             promoChar = currentMoveString[4];
7003             break;
7004           case WhiteDrop:
7005           case BlackDrop:
7006             fromX = moveType == WhiteDrop ?
7007               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7008             (int) CharToPiece(ToLower(currentMoveString[0]));
7009             fromY = DROP_RANK;
7010             toX = currentMoveString[2] - AAA;
7011             toY = currentMoveString[3] - ONE;
7012             promoChar = NULLCHAR;
7013             break;
7014           case AmbiguousMove:
7015             /* bug? */
7016             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7017   if (appData.debugMode) {
7018     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7019     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7020     setbuf(debugFP, NULL);
7021   }
7022             DisplayError(buf, 0);
7023             return;
7024           case ImpossibleMove:
7025             /* bug? */
7026             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7027   if (appData.debugMode) {
7028     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7029     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7030     setbuf(debugFP, NULL);
7031   }
7032             DisplayError(buf, 0);
7033             return;
7034           case (ChessMove) 0:   /* end of file */
7035             if (boardIndex < backwardMostMove) {
7036                 /* Oops, gap.  How did that happen? */
7037                 DisplayError(_("Gap in move list"), 0);
7038                 return;
7039             }
7040             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7041             if (boardIndex > forwardMostMove) {
7042                 forwardMostMove = boardIndex;
7043             }
7044             return;
7045           case ElapsedTime:
7046             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7047                 strcat(parseList[boardIndex-1], " ");
7048                 strcat(parseList[boardIndex-1], yy_text);
7049             }
7050             continue;
7051           case Comment:
7052           case PGNTag:
7053           case NAG:
7054           default:
7055             /* ignore */
7056             continue;
7057           case WhiteWins:
7058           case BlackWins:
7059           case GameIsDrawn:
7060           case GameUnfinished:
7061             if (gameMode == IcsExamining) {
7062                 if (boardIndex < backwardMostMove) {
7063                     /* Oops, gap.  How did that happen? */
7064                     return;
7065                 }
7066                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7067                 return;
7068             }
7069             gameInfo.result = moveType;
7070             p = strchr(yy_text, '{');
7071             if (p == NULL) p = strchr(yy_text, '(');
7072             if (p == NULL) {
7073                 p = yy_text;
7074                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7075             } else {
7076                 q = strchr(p, *p == '{' ? '}' : ')');
7077                 if (q != NULL) *q = NULLCHAR;
7078                 p++;
7079             }
7080             gameInfo.resultDetails = StrSave(p);
7081             continue;
7082         }
7083         if (boardIndex >= forwardMostMove &&
7084             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7085             backwardMostMove = blackPlaysFirst ? 1 : 0;
7086             return;
7087         }
7088         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7089                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7090                                  parseList[boardIndex]);
7091         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7092         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7093         /* currentMoveString is set as a side-effect of yylex */
7094         strcpy(moveList[boardIndex], currentMoveString);
7095         strcat(moveList[boardIndex], "\n");
7096         boardIndex++;
7097         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7098                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7099         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7100                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7101           case MT_NONE:
7102           case MT_STALEMATE:
7103           default:
7104             break;
7105           case MT_CHECK:
7106             if(gameInfo.variant != VariantShogi)
7107                 strcat(parseList[boardIndex - 1], "+");
7108             break;
7109           case MT_CHECKMATE:
7110           case MT_STAINMATE:
7111             strcat(parseList[boardIndex - 1], "#");
7112             break;
7113         }
7114     }
7115 }
7116
7117
7118 /* Apply a move to the given board  */
7119 void
7120 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7121      int fromX, fromY, toX, toY;
7122      int promoChar;
7123      Board board;
7124      char *castling;
7125      char *ep;
7126 {
7127   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7128
7129     /* [HGM] compute & store e.p. status and castling rights for new position */
7130     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7131     { int i;
7132
7133       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7134       oldEP = *ep;
7135       *ep = EP_NONE;
7136
7137       if( board[toY][toX] != EmptySquare ) 
7138            *ep = EP_CAPTURE;  
7139
7140       if( board[fromY][fromX] == WhitePawn ) {
7141            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7142                *ep = EP_PAWN_MOVE;
7143            if( toY-fromY==2) {
7144                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7145                         gameInfo.variant != VariantBerolina || toX < fromX)
7146                       *ep = toX | berolina;
7147                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7148                         gameInfo.variant != VariantBerolina || toX > fromX) 
7149                       *ep = toX;
7150            }
7151       } else 
7152       if( board[fromY][fromX] == BlackPawn ) {
7153            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7154                *ep = EP_PAWN_MOVE; 
7155            if( toY-fromY== -2) {
7156                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7157                         gameInfo.variant != VariantBerolina || toX < fromX)
7158                       *ep = toX | berolina;
7159                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7160                         gameInfo.variant != VariantBerolina || toX > fromX) 
7161                       *ep = toX;
7162            }
7163        }
7164
7165        for(i=0; i<nrCastlingRights; i++) {
7166            if(castling[i] == fromX && castlingRank[i] == fromY ||
7167               castling[i] == toX   && castlingRank[i] == toY   
7168              ) castling[i] = -1; // revoke for moved or captured piece
7169        }
7170
7171     }
7172
7173   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7174   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7175        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7176          
7177   if (fromX == toX && fromY == toY) return;
7178
7179   if (fromY == DROP_RANK) {
7180         /* must be first */
7181         piece = board[toY][toX] = (ChessSquare) fromX;
7182   } else {
7183      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7184      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7185      if(gameInfo.variant == VariantKnightmate)
7186          king += (int) WhiteUnicorn - (int) WhiteKing;
7187
7188     /* Code added by Tord: */
7189     /* FRC castling assumed when king captures friendly rook. */
7190     if (board[fromY][fromX] == WhiteKing &&
7191              board[toY][toX] == WhiteRook) {
7192       board[fromY][fromX] = EmptySquare;
7193       board[toY][toX] = EmptySquare;
7194       if(toX > fromX) {
7195         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7196       } else {
7197         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7198       }
7199     } else if (board[fromY][fromX] == BlackKing &&
7200                board[toY][toX] == BlackRook) {
7201       board[fromY][fromX] = EmptySquare;
7202       board[toY][toX] = EmptySquare;
7203       if(toX > fromX) {
7204         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7205       } else {
7206         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7207       }
7208     /* End of code added by Tord */
7209
7210     } else if (board[fromY][fromX] == king
7211         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7212         && toY == fromY && toX > fromX+1) {
7213         board[fromY][fromX] = EmptySquare;
7214         board[toY][toX] = king;
7215         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7216         board[fromY][BOARD_RGHT-1] = EmptySquare;
7217     } else if (board[fromY][fromX] == king
7218         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219                && toY == fromY && toX < fromX-1) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = king;
7222         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7223         board[fromY][BOARD_LEFT] = EmptySquare;
7224     } else if (board[fromY][fromX] == WhitePawn
7225                && toY == BOARD_HEIGHT-1
7226                && gameInfo.variant != VariantXiangqi
7227                ) {
7228         /* white pawn promotion */
7229         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7230         if (board[toY][toX] == EmptySquare) {
7231             board[toY][toX] = WhiteQueen;
7232         }
7233         if(gameInfo.variant==VariantBughouse ||
7234            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7235             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7236         board[fromY][fromX] = EmptySquare;
7237     } else if ((fromY == BOARD_HEIGHT-4)
7238                && (toX != fromX)
7239                && gameInfo.variant != VariantXiangqi
7240                && gameInfo.variant != VariantBerolina
7241                && (board[fromY][fromX] == WhitePawn)
7242                && (board[toY][toX] == EmptySquare)) {
7243         board[fromY][fromX] = EmptySquare;
7244         board[toY][toX] = WhitePawn;
7245         captured = board[toY - 1][toX];
7246         board[toY - 1][toX] = EmptySquare;
7247     } else if ((fromY == BOARD_HEIGHT-4)
7248                && (toX == fromX)
7249                && gameInfo.variant == VariantBerolina
7250                && (board[fromY][fromX] == WhitePawn)
7251                && (board[toY][toX] == EmptySquare)) {
7252         board[fromY][fromX] = EmptySquare;
7253         board[toY][toX] = WhitePawn;
7254         if(oldEP & EP_BEROLIN_A) {
7255                 captured = board[fromY][fromX-1];
7256                 board[fromY][fromX-1] = EmptySquare;
7257         }else{  captured = board[fromY][fromX+1];
7258                 board[fromY][fromX+1] = EmptySquare;
7259         }
7260     } else if (board[fromY][fromX] == king
7261         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7262                && toY == fromY && toX > fromX+1) {
7263         board[fromY][fromX] = EmptySquare;
7264         board[toY][toX] = king;
7265         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7266         board[fromY][BOARD_RGHT-1] = EmptySquare;
7267     } else if (board[fromY][fromX] == king
7268         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7269                && toY == fromY && toX < fromX-1) {
7270         board[fromY][fromX] = EmptySquare;
7271         board[toY][toX] = king;
7272         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7273         board[fromY][BOARD_LEFT] = EmptySquare;
7274     } else if (fromY == 7 && fromX == 3
7275                && board[fromY][fromX] == BlackKing
7276                && toY == 7 && toX == 5) {
7277         board[fromY][fromX] = EmptySquare;
7278         board[toY][toX] = BlackKing;
7279         board[fromY][7] = EmptySquare;
7280         board[toY][4] = BlackRook;
7281     } else if (fromY == 7 && fromX == 3
7282                && board[fromY][fromX] == BlackKing
7283                && toY == 7 && toX == 1) {
7284         board[fromY][fromX] = EmptySquare;
7285         board[toY][toX] = BlackKing;
7286         board[fromY][0] = EmptySquare;
7287         board[toY][2] = BlackRook;
7288     } else if (board[fromY][fromX] == BlackPawn
7289                && toY == 0
7290                && gameInfo.variant != VariantXiangqi
7291                ) {
7292         /* black pawn promotion */
7293         board[0][toX] = CharToPiece(ToLower(promoChar));
7294         if (board[0][toX] == EmptySquare) {
7295             board[0][toX] = BlackQueen;
7296         }
7297         if(gameInfo.variant==VariantBughouse ||
7298            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7299             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7300         board[fromY][fromX] = EmptySquare;
7301     } else if ((fromY == 3)
7302                && (toX != fromX)
7303                && gameInfo.variant != VariantXiangqi
7304                && gameInfo.variant != VariantBerolina
7305                && (board[fromY][fromX] == BlackPawn)
7306                && (board[toY][toX] == EmptySquare)) {
7307         board[fromY][fromX] = EmptySquare;
7308         board[toY][toX] = BlackPawn;
7309         captured = board[toY + 1][toX];
7310         board[toY + 1][toX] = EmptySquare;
7311     } else if ((fromY == 3)
7312                && (toX == fromX)
7313                && gameInfo.variant == VariantBerolina
7314                && (board[fromY][fromX] == BlackPawn)
7315                && (board[toY][toX] == EmptySquare)) {
7316         board[fromY][fromX] = EmptySquare;
7317         board[toY][toX] = BlackPawn;
7318         if(oldEP & EP_BEROLIN_A) {
7319                 captured = board[fromY][fromX-1];
7320                 board[fromY][fromX-1] = EmptySquare;
7321         }else{  captured = board[fromY][fromX+1];
7322                 board[fromY][fromX+1] = EmptySquare;
7323         }
7324     } else {
7325         board[toY][toX] = board[fromY][fromX];
7326         board[fromY][fromX] = EmptySquare;
7327     }
7328
7329     /* [HGM] now we promote for Shogi, if needed */
7330     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7331         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7332   }
7333
7334     if (gameInfo.holdingsWidth != 0) {
7335
7336       /* !!A lot more code needs to be written to support holdings  */
7337       /* [HGM] OK, so I have written it. Holdings are stored in the */
7338       /* penultimate board files, so they are automaticlly stored   */
7339       /* in the game history.                                       */
7340       if (fromY == DROP_RANK) {
7341         /* Delete from holdings, by decreasing count */
7342         /* and erasing image if necessary            */
7343         p = (int) fromX;
7344         if(p < (int) BlackPawn) { /* white drop */
7345              p -= (int)WhitePawn;
7346              if(p >= gameInfo.holdingsSize) p = 0;
7347              if(--board[p][BOARD_WIDTH-2] == 0)
7348                   board[p][BOARD_WIDTH-1] = EmptySquare;
7349         } else {                  /* black drop */
7350              p -= (int)BlackPawn;
7351              if(p >= gameInfo.holdingsSize) p = 0;
7352              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7353                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7354         }
7355       }
7356       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7357           && gameInfo.variant != VariantBughouse        ) {
7358         /* [HGM] holdings: Add to holdings, if holdings exist */
7359         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7360                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7361                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7362         }
7363         p = (int) captured;
7364         if (p >= (int) BlackPawn) {
7365           p -= (int)BlackPawn;
7366           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7367                   /* in Shogi restore piece to its original  first */
7368                   captured = (ChessSquare) (DEMOTED captured);
7369                   p = DEMOTED p;
7370           }
7371           p = PieceToNumber((ChessSquare)p);
7372           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7373           board[p][BOARD_WIDTH-2]++;
7374           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7375         } else {
7376           p -= (int)WhitePawn;
7377           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7378                   captured = (ChessSquare) (DEMOTED captured);
7379                   p = DEMOTED p;
7380           }
7381           p = PieceToNumber((ChessSquare)p);
7382           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7383           board[BOARD_HEIGHT-1-p][1]++;
7384           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7385         }
7386       }
7387
7388     } else if (gameInfo.variant == VariantAtomic) {
7389       if (captured != EmptySquare) {
7390         int y, x;
7391         for (y = toY-1; y <= toY+1; y++) {
7392           for (x = toX-1; x <= toX+1; x++) {
7393             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7394                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7395               board[y][x] = EmptySquare;
7396             }
7397           }
7398         }
7399         board[toY][toX] = EmptySquare;
7400       }
7401     }
7402     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7403         /* [HGM] Shogi promotions */
7404         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7405     }
7406
7407     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7408                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7409         // [HGM] superchess: take promotion piece out of holdings
7410         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7411         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7412             if(!--board[k][BOARD_WIDTH-2])
7413                 board[k][BOARD_WIDTH-1] = EmptySquare;
7414         } else {
7415             if(!--board[BOARD_HEIGHT-1-k][1])
7416                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7417         }
7418     }
7419
7420 }
7421
7422 /* Updates forwardMostMove */
7423 void
7424 MakeMove(fromX, fromY, toX, toY, promoChar)
7425      int fromX, fromY, toX, toY;
7426      int promoChar;
7427 {
7428 //    forwardMostMove++; // [HGM] bare: moved downstream
7429
7430     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7431         int timeLeft; static int lastLoadFlag=0; int king, piece;
7432         piece = boards[forwardMostMove][fromY][fromX];
7433         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7434         if(gameInfo.variant == VariantKnightmate)
7435             king += (int) WhiteUnicorn - (int) WhiteKing;
7436         if(forwardMostMove == 0) {
7437             if(blackPlaysFirst) 
7438                 fprintf(serverMoves, "%s;", second.tidy);
7439             fprintf(serverMoves, "%s;", first.tidy);
7440             if(!blackPlaysFirst) 
7441                 fprintf(serverMoves, "%s;", second.tidy);
7442         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7443         lastLoadFlag = loadFlag;
7444         // print base move
7445         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7446         // print castling suffix
7447         if( toY == fromY && piece == king ) {
7448             if(toX-fromX > 1)
7449                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7450             if(fromX-toX >1)
7451                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7452         }
7453         // e.p. suffix
7454         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7455              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7456              boards[forwardMostMove][toY][toX] == EmptySquare
7457              && fromX != toX )
7458                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7459         // promotion suffix
7460         if(promoChar != NULLCHAR)
7461                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7462         if(!loadFlag) {
7463             fprintf(serverMoves, "/%d/%d",
7464                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7465             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7466             else                      timeLeft = blackTimeRemaining/1000;
7467             fprintf(serverMoves, "/%d", timeLeft);
7468         }
7469         fflush(serverMoves);
7470     }
7471
7472     if (forwardMostMove+1 >= MAX_MOVES) {
7473       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7474                         0, 1);
7475       return;
7476     }
7477     if (commentList[forwardMostMove+1] != NULL) {
7478         free(commentList[forwardMostMove+1]);
7479         commentList[forwardMostMove+1] = NULL;
7480     }
7481     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7482     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7483     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7484                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7485     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7486     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7487     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7488     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7489     gameInfo.result = GameUnfinished;
7490     if (gameInfo.resultDetails != NULL) {
7491         free(gameInfo.resultDetails);
7492         gameInfo.resultDetails = NULL;
7493     }
7494     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7495                               moveList[forwardMostMove - 1]);
7496     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7497                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7498                              fromY, fromX, toY, toX, promoChar,
7499                              parseList[forwardMostMove - 1]);
7500     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7501                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7502                             castlingRights[forwardMostMove]) ) {
7503       case MT_NONE:
7504       case MT_STALEMATE:
7505       default:
7506         break;
7507       case MT_CHECK:
7508         if(gameInfo.variant != VariantShogi)
7509             strcat(parseList[forwardMostMove - 1], "+");
7510         break;
7511       case MT_CHECKMATE:
7512       case MT_STAINMATE:
7513         strcat(parseList[forwardMostMove - 1], "#");
7514         break;
7515     }
7516     if (appData.debugMode) {
7517         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7518     }
7519
7520 }
7521
7522 /* Updates currentMove if not pausing */
7523 void
7524 ShowMove(fromX, fromY, toX, toY)
7525 {
7526     int instant = (gameMode == PlayFromGameFile) ?
7527         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7528     if(appData.noGUI) return;
7529     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7530         if (!instant) {
7531             if (forwardMostMove == currentMove + 1) {
7532                 AnimateMove(boards[forwardMostMove - 1],
7533                             fromX, fromY, toX, toY);
7534             }
7535             if (appData.highlightLastMove) {
7536                 SetHighlights(fromX, fromY, toX, toY);
7537             }
7538         }
7539         currentMove = forwardMostMove;
7540     }
7541
7542     if (instant) return;
7543
7544     DisplayMove(currentMove - 1);
7545     DrawPosition(FALSE, boards[currentMove]);
7546     DisplayBothClocks();
7547     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7548 }
7549
7550 void SendEgtPath(ChessProgramState *cps)
7551 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7552         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7553
7554         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7555
7556         while(*p) {
7557             char c, *q = name+1, *r, *s;
7558
7559             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7560             while(*p && *p != ',') *q++ = *p++;
7561             *q++ = ':'; *q = 0;
7562             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7563                 strcmp(name, ",nalimov:") == 0 ) {
7564                 // take nalimov path from the menu-changeable option first, if it is defined
7565                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7566                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7567             } else
7568             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7569                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7570                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7571                 s = r = StrStr(s, ":") + 1; // beginning of path info
7572                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7573                 c = *r; *r = 0;             // temporarily null-terminate path info
7574                     *--q = 0;               // strip of trailig ':' from name
7575                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7576                 *r = c;
7577                 SendToProgram(buf,cps);     // send egtbpath command for this format
7578             }
7579             if(*p == ',') p++; // read away comma to position for next format name
7580         }
7581 }
7582
7583 void
7584 InitChessProgram(cps, setup)
7585      ChessProgramState *cps;
7586      int setup; /* [HGM] needed to setup FRC opening position */
7587 {
7588     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7589     if (appData.noChessProgram) return;
7590     hintRequested = FALSE;
7591     bookRequested = FALSE;
7592
7593     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7594     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7595     if(cps->memSize) { /* [HGM] memory */
7596         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7597         SendToProgram(buf, cps);
7598     }
7599     SendEgtPath(cps); /* [HGM] EGT */
7600     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7601         sprintf(buf, "cores %d\n", appData.smpCores);
7602         SendToProgram(buf, cps);
7603     }
7604
7605     SendToProgram(cps->initString, cps);
7606     if (gameInfo.variant != VariantNormal &&
7607         gameInfo.variant != VariantLoadable
7608         /* [HGM] also send variant if board size non-standard */
7609         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7610                                             ) {
7611       char *v = VariantName(gameInfo.variant);
7612       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7613         /* [HGM] in protocol 1 we have to assume all variants valid */
7614         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7615         DisplayFatalError(buf, 0, 1);
7616         return;
7617       }
7618
7619       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7620       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7621       if( gameInfo.variant == VariantXiangqi )
7622            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7623       if( gameInfo.variant == VariantShogi )
7624            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7625       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7626            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7627       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7628                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7629            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7630       if( gameInfo.variant == VariantCourier )
7631            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7632       if( gameInfo.variant == VariantSuper )
7633            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7634       if( gameInfo.variant == VariantGreat )
7635            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7636
7637       if(overruled) {
7638            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7639                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7640            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7641            if(StrStr(cps->variants, b) == NULL) { 
7642                // specific sized variant not known, check if general sizing allowed
7643                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7644                    if(StrStr(cps->variants, "boardsize") == NULL) {
7645                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7646                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7647                        DisplayFatalError(buf, 0, 1);
7648                        return;
7649                    }
7650                    /* [HGM] here we really should compare with the maximum supported board size */
7651                }
7652            }
7653       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7654       sprintf(buf, "variant %s\n", b);
7655       SendToProgram(buf, cps);
7656     }
7657     currentlyInitializedVariant = gameInfo.variant;
7658
7659     /* [HGM] send opening position in FRC to first engine */
7660     if(setup) {
7661           SendToProgram("force\n", cps);
7662           SendBoard(cps, 0);
7663           /* engine is now in force mode! Set flag to wake it up after first move. */
7664           setboardSpoiledMachineBlack = 1;
7665     }
7666
7667     if (cps->sendICS) {
7668       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7669       SendToProgram(buf, cps);
7670     }
7671     cps->maybeThinking = FALSE;
7672     cps->offeredDraw = 0;
7673     if (!appData.icsActive) {
7674         SendTimeControl(cps, movesPerSession, timeControl,
7675                         timeIncrement, appData.searchDepth,
7676                         searchTime);
7677     }
7678     if (appData.showThinking 
7679         // [HGM] thinking: four options require thinking output to be sent
7680         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7681                                 ) {
7682         SendToProgram("post\n", cps);
7683     }
7684     SendToProgram("hard\n", cps);
7685     if (!appData.ponderNextMove) {
7686         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7687            it without being sure what state we are in first.  "hard"
7688            is not a toggle, so that one is OK.
7689          */
7690         SendToProgram("easy\n", cps);
7691     }
7692     if (cps->usePing) {
7693       sprintf(buf, "ping %d\n", ++cps->lastPing);
7694       SendToProgram(buf, cps);
7695     }
7696     cps->initDone = TRUE;
7697 }   
7698
7699
7700 void
7701 StartChessProgram(cps)
7702      ChessProgramState *cps;
7703 {
7704     char buf[MSG_SIZ];
7705     int err;
7706
7707     if (appData.noChessProgram) return;
7708     cps->initDone = FALSE;
7709
7710     if (strcmp(cps->host, "localhost") == 0) {
7711         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7712     } else if (*appData.remoteShell == NULLCHAR) {
7713         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7714     } else {
7715         if (*appData.remoteUser == NULLCHAR) {
7716           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7717                     cps->program);
7718         } else {
7719           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7720                     cps->host, appData.remoteUser, cps->program);
7721         }
7722         err = StartChildProcess(buf, "", &cps->pr);
7723     }
7724     
7725     if (err != 0) {
7726         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7727         DisplayFatalError(buf, err, 1);
7728         cps->pr = NoProc;
7729         cps->isr = NULL;
7730         return;
7731     }
7732     
7733     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7734     if (cps->protocolVersion > 1) {
7735       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7736       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7737       cps->comboCnt = 0;  //                and values of combo boxes
7738       SendToProgram(buf, cps);
7739     } else {
7740       SendToProgram("xboard\n", cps);
7741     }
7742 }
7743
7744
7745 void
7746 TwoMachinesEventIfReady P((void))
7747 {
7748   if (first.lastPing != first.lastPong) {
7749     DisplayMessage("", _("Waiting for first chess program"));
7750     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7751     return;
7752   }
7753   if (second.lastPing != second.lastPong) {
7754     DisplayMessage("", _("Waiting for second chess program"));
7755     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7756     return;
7757   }
7758   ThawUI();
7759   TwoMachinesEvent();
7760 }
7761
7762 void
7763 NextMatchGame P((void))
7764 {
7765     int index; /* [HGM] autoinc: step lod index during match */
7766     Reset(FALSE, TRUE);
7767     if (*appData.loadGameFile != NULLCHAR) {
7768         index = appData.loadGameIndex;
7769         if(index < 0) { // [HGM] autoinc
7770             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7771             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7772         } 
7773         LoadGameFromFile(appData.loadGameFile,
7774                          index,
7775                          appData.loadGameFile, FALSE);
7776     } else if (*appData.loadPositionFile != NULLCHAR) {
7777         index = appData.loadPositionIndex;
7778         if(index < 0) { // [HGM] autoinc
7779             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7780             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7781         } 
7782         LoadPositionFromFile(appData.loadPositionFile,
7783                              index,
7784                              appData.loadPositionFile);
7785     }
7786     TwoMachinesEventIfReady();
7787 }
7788
7789 void UserAdjudicationEvent( int result )
7790 {
7791     ChessMove gameResult = GameIsDrawn;
7792
7793     if( result > 0 ) {
7794         gameResult = WhiteWins;
7795     }
7796     else if( result < 0 ) {
7797         gameResult = BlackWins;
7798     }
7799
7800     if( gameMode == TwoMachinesPlay ) {
7801         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7802     }
7803 }
7804
7805
7806 // [HGM] save: calculate checksum of game to make games easily identifiable
7807 int StringCheckSum(char *s)
7808 {
7809         int i = 0;
7810         if(s==NULL) return 0;
7811         while(*s) i = i*259 + *s++;
7812         return i;
7813 }
7814
7815 int GameCheckSum()
7816 {
7817         int i, sum=0;
7818         for(i=backwardMostMove; i<forwardMostMove; i++) {
7819                 sum += pvInfoList[i].depth;
7820                 sum += StringCheckSum(parseList[i]);
7821                 sum += StringCheckSum(commentList[i]);
7822                 sum *= 261;
7823         }
7824         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7825         return sum + StringCheckSum(commentList[i]);
7826 } // end of save patch
7827
7828 void
7829 GameEnds(result, resultDetails, whosays)
7830      ChessMove result;
7831      char *resultDetails;
7832      int whosays;
7833 {
7834     GameMode nextGameMode;
7835     int isIcsGame;
7836     char buf[MSG_SIZ];
7837
7838     if(endingGame) return; /* [HGM] crash: forbid recursion */
7839     endingGame = 1;
7840
7841     if (appData.debugMode) {
7842       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7843               result, resultDetails ? resultDetails : "(null)", whosays);
7844     }
7845
7846     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7847         /* If we are playing on ICS, the server decides when the
7848            game is over, but the engine can offer to draw, claim 
7849            a draw, or resign. 
7850          */
7851 #if ZIPPY
7852         if (appData.zippyPlay && first.initDone) {
7853             if (result == GameIsDrawn) {
7854                 /* In case draw still needs to be claimed */
7855                 SendToICS(ics_prefix);
7856                 SendToICS("draw\n");
7857             } else if (StrCaseStr(resultDetails, "resign")) {
7858                 SendToICS(ics_prefix);
7859                 SendToICS("resign\n");
7860             }
7861         }
7862 #endif
7863         endingGame = 0; /* [HGM] crash */
7864         return;
7865     }
7866
7867     /* If we're loading the game from a file, stop */
7868     if (whosays == GE_FILE) {
7869       (void) StopLoadGameTimer();
7870       gameFileFP = NULL;
7871     }
7872
7873     /* Cancel draw offers */
7874     first.offeredDraw = second.offeredDraw = 0;
7875
7876     /* If this is an ICS game, only ICS can really say it's done;
7877        if not, anyone can. */
7878     isIcsGame = (gameMode == IcsPlayingWhite || 
7879                  gameMode == IcsPlayingBlack || 
7880                  gameMode == IcsObserving    || 
7881                  gameMode == IcsExamining);
7882
7883     if (!isIcsGame || whosays == GE_ICS) {
7884         /* OK -- not an ICS game, or ICS said it was done */
7885         StopClocks();
7886         if (!isIcsGame && !appData.noChessProgram) 
7887           SetUserThinkingEnables();
7888     
7889         /* [HGM] if a machine claims the game end we verify this claim */
7890         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7891             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7892                 char claimer;
7893                 ChessMove trueResult = (ChessMove) -1;
7894
7895                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7896                                             first.twoMachinesColor[0] :
7897                                             second.twoMachinesColor[0] ;
7898
7899                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7900                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7901                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7902                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7903                 } else
7904                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7905                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7906                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7907                 } else
7908                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7909                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7910                 }
7911
7912                 // now verify win claims, but not in drop games, as we don't understand those yet
7913                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7914                                                  || gameInfo.variant == VariantGreat) &&
7915                     (result == WhiteWins && claimer == 'w' ||
7916                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7917                       if (appData.debugMode) {
7918                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7919                                 result, epStatus[forwardMostMove], forwardMostMove);
7920                       }
7921                       if(result != trueResult) {
7922                               sprintf(buf, "False win claim: '%s'", resultDetails);
7923                               result = claimer == 'w' ? BlackWins : WhiteWins;
7924                               resultDetails = buf;
7925                       }
7926                 } else
7927                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7928                     && (forwardMostMove <= backwardMostMove ||
7929                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7930                         (claimer=='b')==(forwardMostMove&1))
7931                                                                                   ) {
7932                       /* [HGM] verify: draws that were not flagged are false claims */
7933                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7934                       result = claimer == 'w' ? BlackWins : WhiteWins;
7935                       resultDetails = buf;
7936                 }
7937                 /* (Claiming a loss is accepted no questions asked!) */
7938             }
7939             /* [HGM] bare: don't allow bare King to win */
7940             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7941                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7942                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7943                && result != GameIsDrawn)
7944             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7945                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7946                         int p = (int)boards[forwardMostMove][i][j] - color;
7947                         if(p >= 0 && p <= (int)WhiteKing) k++;
7948                 }
7949                 if (appData.debugMode) {
7950                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7951                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7952                 }
7953                 if(k <= 1) {
7954                         result = GameIsDrawn;
7955                         sprintf(buf, "%s but bare king", resultDetails);
7956                         resultDetails = buf;
7957                 }
7958             }
7959         }
7960
7961
7962         if(serverMoves != NULL && !loadFlag) { char c = '=';
7963             if(result==WhiteWins) c = '+';
7964             if(result==BlackWins) c = '-';
7965             if(resultDetails != NULL)
7966                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7967         }
7968         if (resultDetails != NULL) {
7969             gameInfo.result = result;
7970             gameInfo.resultDetails = StrSave(resultDetails);
7971
7972             /* display last move only if game was not loaded from file */
7973             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7974                 DisplayMove(currentMove - 1);
7975     
7976             if (forwardMostMove != 0) {
7977                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7978                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7979                                                                 ) {
7980                     if (*appData.saveGameFile != NULLCHAR) {
7981                         SaveGameToFile(appData.saveGameFile, TRUE);
7982                     } else if (appData.autoSaveGames) {
7983                         AutoSaveGame();
7984                     }
7985                     if (*appData.savePositionFile != NULLCHAR) {
7986                         SavePositionToFile(appData.savePositionFile);
7987                     }
7988                 }
7989             }
7990
7991             /* Tell program how game ended in case it is learning */
7992             /* [HGM] Moved this to after saving the PGN, just in case */
7993             /* engine died and we got here through time loss. In that */
7994             /* case we will get a fatal error writing the pipe, which */
7995             /* would otherwise lose us the PGN.                       */
7996             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7997             /* output during GameEnds should never be fatal anymore   */
7998             if (gameMode == MachinePlaysWhite ||
7999                 gameMode == MachinePlaysBlack ||
8000                 gameMode == TwoMachinesPlay ||
8001                 gameMode == IcsPlayingWhite ||
8002                 gameMode == IcsPlayingBlack ||
8003                 gameMode == BeginningOfGame) {
8004                 char buf[MSG_SIZ];
8005                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8006                         resultDetails);
8007                 if (first.pr != NoProc) {
8008                     SendToProgram(buf, &first);
8009                 }
8010                 if (second.pr != NoProc &&
8011                     gameMode == TwoMachinesPlay) {
8012                     SendToProgram(buf, &second);
8013                 }
8014             }
8015         }
8016
8017         if (appData.icsActive) {
8018             if (appData.quietPlay &&
8019                 (gameMode == IcsPlayingWhite ||
8020                  gameMode == IcsPlayingBlack)) {
8021                 SendToICS(ics_prefix);
8022                 SendToICS("set shout 1\n");
8023             }
8024             nextGameMode = IcsIdle;
8025             ics_user_moved = FALSE;
8026             /* clean up premove.  It's ugly when the game has ended and the
8027              * premove highlights are still on the board.
8028              */
8029             if (gotPremove) {
8030               gotPremove = FALSE;
8031               ClearPremoveHighlights();
8032               DrawPosition(FALSE, boards[currentMove]);
8033             }
8034             if (whosays == GE_ICS) {
8035                 switch (result) {
8036                 case WhiteWins:
8037                     if (gameMode == IcsPlayingWhite)
8038                         PlayIcsWinSound();
8039                     else if(gameMode == IcsPlayingBlack)
8040                         PlayIcsLossSound();
8041                     break;
8042                 case BlackWins:
8043                     if (gameMode == IcsPlayingBlack)
8044                         PlayIcsWinSound();
8045                     else if(gameMode == IcsPlayingWhite)
8046                         PlayIcsLossSound();
8047                     break;
8048                 case GameIsDrawn:
8049                     PlayIcsDrawSound();
8050                     break;
8051                 default:
8052                     PlayIcsUnfinishedSound();
8053                 }
8054             }
8055         } else if (gameMode == EditGame ||
8056                    gameMode == PlayFromGameFile || 
8057                    gameMode == AnalyzeMode || 
8058                    gameMode == AnalyzeFile) {
8059             nextGameMode = gameMode;
8060         } else {
8061             nextGameMode = EndOfGame;
8062         }
8063         pausing = FALSE;
8064         ModeHighlight();
8065     } else {
8066         nextGameMode = gameMode;
8067     }
8068
8069     if (appData.noChessProgram) {
8070         gameMode = nextGameMode;
8071         ModeHighlight();
8072         endingGame = 0; /* [HGM] crash */
8073         return;
8074     }
8075
8076     if (first.reuse) {
8077         /* Put first chess program into idle state */
8078         if (first.pr != NoProc &&
8079             (gameMode == MachinePlaysWhite ||
8080              gameMode == MachinePlaysBlack ||
8081              gameMode == TwoMachinesPlay ||
8082              gameMode == IcsPlayingWhite ||
8083              gameMode == IcsPlayingBlack ||
8084              gameMode == BeginningOfGame)) {
8085             SendToProgram("force\n", &first);
8086             if (first.usePing) {
8087               char buf[MSG_SIZ];
8088               sprintf(buf, "ping %d\n", ++first.lastPing);
8089               SendToProgram(buf, &first);
8090             }
8091         }
8092     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8093         /* Kill off first chess program */
8094         if (first.isr != NULL)
8095           RemoveInputSource(first.isr);
8096         first.isr = NULL;
8097     
8098         if (first.pr != NoProc) {
8099             ExitAnalyzeMode();
8100             DoSleep( appData.delayBeforeQuit );
8101             SendToProgram("quit\n", &first);
8102             DoSleep( appData.delayAfterQuit );
8103             DestroyChildProcess(first.pr, first.useSigterm);
8104         }
8105         first.pr = NoProc;
8106     }
8107     if (second.reuse) {
8108         /* Put second chess program into idle state */
8109         if (second.pr != NoProc &&
8110             gameMode == TwoMachinesPlay) {
8111             SendToProgram("force\n", &second);
8112             if (second.usePing) {
8113               char buf[MSG_SIZ];
8114               sprintf(buf, "ping %d\n", ++second.lastPing);
8115               SendToProgram(buf, &second);
8116             }
8117         }
8118     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8119         /* Kill off second chess program */
8120         if (second.isr != NULL)
8121           RemoveInputSource(second.isr);
8122         second.isr = NULL;
8123     
8124         if (second.pr != NoProc) {
8125             DoSleep( appData.delayBeforeQuit );
8126             SendToProgram("quit\n", &second);
8127             DoSleep( appData.delayAfterQuit );
8128             DestroyChildProcess(second.pr, second.useSigterm);
8129         }
8130         second.pr = NoProc;
8131     }
8132
8133     if (matchMode && gameMode == TwoMachinesPlay) {
8134         switch (result) {
8135         case WhiteWins:
8136           if (first.twoMachinesColor[0] == 'w') {
8137             first.matchWins++;
8138           } else {
8139             second.matchWins++;
8140           }
8141           break;
8142         case BlackWins:
8143           if (first.twoMachinesColor[0] == 'b') {
8144             first.matchWins++;
8145           } else {
8146             second.matchWins++;
8147           }
8148           break;
8149         default:
8150           break;
8151         }
8152         if (matchGame < appData.matchGames) {
8153             char *tmp;
8154             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8155                 tmp = first.twoMachinesColor;
8156                 first.twoMachinesColor = second.twoMachinesColor;
8157                 second.twoMachinesColor = tmp;
8158             }
8159             gameMode = nextGameMode;
8160             matchGame++;
8161             if(appData.matchPause>10000 || appData.matchPause<10)
8162                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8163             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8164             endingGame = 0; /* [HGM] crash */
8165             return;
8166         } else {
8167             char buf[MSG_SIZ];
8168             gameMode = nextGameMode;
8169             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8170                     first.tidy, second.tidy,
8171                     first.matchWins, second.matchWins,
8172                     appData.matchGames - (first.matchWins + second.matchWins));
8173             DisplayFatalError(buf, 0, 0);
8174         }
8175     }
8176     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8177         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8178       ExitAnalyzeMode();
8179     gameMode = nextGameMode;
8180     ModeHighlight();
8181     endingGame = 0;  /* [HGM] crash */
8182 }
8183
8184 /* Assumes program was just initialized (initString sent).
8185    Leaves program in force mode. */
8186 void
8187 FeedMovesToProgram(cps, upto) 
8188      ChessProgramState *cps;
8189      int upto;
8190 {
8191     int i;
8192     
8193     if (appData.debugMode)
8194       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8195               startedFromSetupPosition ? "position and " : "",
8196               backwardMostMove, upto, cps->which);
8197     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8198         // [HGM] variantswitch: make engine aware of new variant
8199         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8200                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8201         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8202         SendToProgram(buf, cps);
8203         currentlyInitializedVariant = gameInfo.variant;
8204     }
8205     SendToProgram("force\n", cps);
8206     if (startedFromSetupPosition) {
8207         SendBoard(cps, backwardMostMove);
8208     if (appData.debugMode) {
8209         fprintf(debugFP, "feedMoves\n");
8210     }
8211     }
8212     for (i = backwardMostMove; i < upto; i++) {
8213         SendMoveToProgram(i, cps);
8214     }
8215 }
8216
8217
8218 void
8219 ResurrectChessProgram()
8220 {
8221      /* The chess program may have exited.
8222         If so, restart it and feed it all the moves made so far. */
8223
8224     if (appData.noChessProgram || first.pr != NoProc) return;
8225     
8226     StartChessProgram(&first);
8227     InitChessProgram(&first, FALSE);
8228     FeedMovesToProgram(&first, currentMove);
8229
8230     if (!first.sendTime) {
8231         /* can't tell gnuchess what its clock should read,
8232            so we bow to its notion. */
8233         ResetClocks();
8234         timeRemaining[0][currentMove] = whiteTimeRemaining;
8235         timeRemaining[1][currentMove] = blackTimeRemaining;
8236     }
8237
8238     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8239                 appData.icsEngineAnalyze) && first.analysisSupport) {
8240       SendToProgram("analyze\n", &first);
8241       first.analyzing = TRUE;
8242     }
8243 }
8244
8245 /*
8246  * Button procedures
8247  */
8248 void
8249 Reset(redraw, init)
8250      int redraw, init;
8251 {
8252     int i;
8253
8254     if (appData.debugMode) {
8255         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8256                 redraw, init, gameMode);
8257     }
8258     pausing = pauseExamInvalid = FALSE;
8259     startedFromSetupPosition = blackPlaysFirst = FALSE;
8260     firstMove = TRUE;
8261     whiteFlag = blackFlag = FALSE;
8262     userOfferedDraw = FALSE;
8263     hintRequested = bookRequested = FALSE;
8264     first.maybeThinking = FALSE;
8265     second.maybeThinking = FALSE;
8266     first.bookSuspend = FALSE; // [HGM] book
8267     second.bookSuspend = FALSE;
8268     thinkOutput[0] = NULLCHAR;
8269     lastHint[0] = NULLCHAR;
8270     ClearGameInfo(&gameInfo);
8271     gameInfo.variant = StringToVariant(appData.variant);
8272     ics_user_moved = ics_clock_paused = FALSE;
8273     ics_getting_history = H_FALSE;
8274     ics_gamenum = -1;
8275     white_holding[0] = black_holding[0] = NULLCHAR;
8276     ClearProgramStats();
8277     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8278     
8279     ResetFrontEnd();
8280     ClearHighlights();
8281     flipView = appData.flipView;
8282     ClearPremoveHighlights();
8283     gotPremove = FALSE;
8284     alarmSounded = FALSE;
8285
8286     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8287     if(appData.serverMovesName != NULL) {
8288         /* [HGM] prepare to make moves file for broadcasting */
8289         clock_t t = clock();
8290         if(serverMoves != NULL) fclose(serverMoves);
8291         serverMoves = fopen(appData.serverMovesName, "r");
8292         if(serverMoves != NULL) {
8293             fclose(serverMoves);
8294             /* delay 15 sec before overwriting, so all clients can see end */
8295             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8296         }
8297         serverMoves = fopen(appData.serverMovesName, "w");
8298     }
8299
8300     ExitAnalyzeMode();
8301     gameMode = BeginningOfGame;
8302     ModeHighlight();
8303     if(appData.icsActive) gameInfo.variant = VariantNormal;
8304     currentMove = forwardMostMove = backwardMostMove = 0;
8305     InitPosition(redraw);
8306     for (i = 0; i < MAX_MOVES; i++) {
8307         if (commentList[i] != NULL) {
8308             free(commentList[i]);
8309             commentList[i] = NULL;
8310         }
8311     }
8312     ResetClocks();
8313     timeRemaining[0][0] = whiteTimeRemaining;
8314     timeRemaining[1][0] = blackTimeRemaining;
8315     if (first.pr == NULL) {
8316         StartChessProgram(&first);
8317     }
8318     if (init) {
8319             InitChessProgram(&first, startedFromSetupPosition);
8320     }
8321     DisplayTitle("");
8322     DisplayMessage("", "");
8323     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8324     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8325 }
8326
8327 void
8328 AutoPlayGameLoop()
8329 {
8330     for (;;) {
8331         if (!AutoPlayOneMove())
8332           return;
8333         if (matchMode || appData.timeDelay == 0)
8334           continue;
8335         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8336           return;
8337         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8338         break;
8339     }
8340 }
8341
8342
8343 int
8344 AutoPlayOneMove()
8345 {
8346     int fromX, fromY, toX, toY;
8347
8348     if (appData.debugMode) {
8349       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8350     }
8351
8352     if (gameMode != PlayFromGameFile)
8353       return FALSE;
8354
8355     if (currentMove >= forwardMostMove) {
8356       gameMode = EditGame;
8357       ModeHighlight();
8358
8359       /* [AS] Clear current move marker at the end of a game */
8360       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8361
8362       return FALSE;
8363     }
8364     
8365     toX = moveList[currentMove][2] - AAA;
8366     toY = moveList[currentMove][3] - ONE;
8367
8368     if (moveList[currentMove][1] == '@') {
8369         if (appData.highlightLastMove) {
8370             SetHighlights(-1, -1, toX, toY);
8371         }
8372     } else {
8373         fromX = moveList[currentMove][0] - AAA;
8374         fromY = moveList[currentMove][1] - ONE;
8375
8376         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8377
8378         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8379
8380         if (appData.highlightLastMove) {
8381             SetHighlights(fromX, fromY, toX, toY);
8382         }
8383     }
8384     DisplayMove(currentMove);
8385     SendMoveToProgram(currentMove++, &first);
8386     DisplayBothClocks();
8387     DrawPosition(FALSE, boards[currentMove]);
8388     // [HGM] PV info: always display, routine tests if empty
8389     DisplayComment(currentMove - 1, commentList[currentMove]);
8390     return TRUE;
8391 }
8392
8393
8394 int
8395 LoadGameOneMove(readAhead)
8396      ChessMove readAhead;
8397 {
8398     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8399     char promoChar = NULLCHAR;
8400     ChessMove moveType;
8401     char move[MSG_SIZ];
8402     char *p, *q;
8403     
8404     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8405         gameMode != AnalyzeMode && gameMode != Training) {
8406         gameFileFP = NULL;
8407         return FALSE;
8408     }
8409     
8410     yyboardindex = forwardMostMove;
8411     if (readAhead != (ChessMove)0) {
8412       moveType = readAhead;
8413     } else {
8414       if (gameFileFP == NULL)
8415           return FALSE;
8416       moveType = (ChessMove) yylex();
8417     }
8418     
8419     done = FALSE;
8420     switch (moveType) {
8421       case Comment:
8422         if (appData.debugMode) 
8423           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8424         p = yy_text;
8425         if (*p == '{' || *p == '[' || *p == '(') {
8426             p[strlen(p) - 1] = NULLCHAR;
8427             p++;
8428         }
8429
8430         /* append the comment but don't display it */
8431         while (*p == '\n') p++;
8432         AppendComment(currentMove, p);
8433         return TRUE;
8434
8435       case WhiteCapturesEnPassant:
8436       case BlackCapturesEnPassant:
8437       case WhitePromotionChancellor:
8438       case BlackPromotionChancellor:
8439       case WhitePromotionArchbishop:
8440       case BlackPromotionArchbishop:
8441       case WhitePromotionCentaur:
8442       case BlackPromotionCentaur:
8443       case WhitePromotionQueen:
8444       case BlackPromotionQueen:
8445       case WhitePromotionRook:
8446       case BlackPromotionRook:
8447       case WhitePromotionBishop:
8448       case BlackPromotionBishop:
8449       case WhitePromotionKnight:
8450       case BlackPromotionKnight:
8451       case WhitePromotionKing:
8452       case BlackPromotionKing:
8453       case NormalMove:
8454       case WhiteKingSideCastle:
8455       case WhiteQueenSideCastle:
8456       case BlackKingSideCastle:
8457       case BlackQueenSideCastle:
8458       case WhiteKingSideCastleWild:
8459       case WhiteQueenSideCastleWild:
8460       case BlackKingSideCastleWild:
8461       case BlackQueenSideCastleWild:
8462       /* PUSH Fabien */
8463       case WhiteHSideCastleFR:
8464       case WhiteASideCastleFR:
8465       case BlackHSideCastleFR:
8466       case BlackASideCastleFR:
8467       /* POP Fabien */
8468         if (appData.debugMode)
8469           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8470         fromX = currentMoveString[0] - AAA;
8471         fromY = currentMoveString[1] - ONE;
8472         toX = currentMoveString[2] - AAA;
8473         toY = currentMoveString[3] - ONE;
8474         promoChar = currentMoveString[4];
8475         break;
8476
8477       case WhiteDrop:
8478       case BlackDrop:
8479         if (appData.debugMode)
8480           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8481         fromX = moveType == WhiteDrop ?
8482           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8483         (int) CharToPiece(ToLower(currentMoveString[0]));
8484         fromY = DROP_RANK;
8485         toX = currentMoveString[2] - AAA;
8486         toY = currentMoveString[3] - ONE;
8487         break;
8488
8489       case WhiteWins:
8490       case BlackWins:
8491       case GameIsDrawn:
8492       case GameUnfinished:
8493         if (appData.debugMode)
8494           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8495         p = strchr(yy_text, '{');
8496         if (p == NULL) p = strchr(yy_text, '(');
8497         if (p == NULL) {
8498             p = yy_text;
8499             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8500         } else {
8501             q = strchr(p, *p == '{' ? '}' : ')');
8502             if (q != NULL) *q = NULLCHAR;
8503             p++;
8504         }
8505         GameEnds(moveType, p, GE_FILE);
8506         done = TRUE;
8507         if (cmailMsgLoaded) {
8508             ClearHighlights();
8509             flipView = WhiteOnMove(currentMove);
8510             if (moveType == GameUnfinished) flipView = !flipView;
8511             if (appData.debugMode)
8512               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8513         }
8514         break;
8515
8516       case (ChessMove) 0:       /* end of file */
8517         if (appData.debugMode)
8518           fprintf(debugFP, "Parser hit end of file\n");
8519         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8520                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8521           case MT_NONE:
8522           case MT_CHECK:
8523             break;
8524           case MT_CHECKMATE:
8525           case MT_STAINMATE:
8526             if (WhiteOnMove(currentMove)) {
8527                 GameEnds(BlackWins, "Black mates", GE_FILE);
8528             } else {
8529                 GameEnds(WhiteWins, "White mates", GE_FILE);
8530             }
8531             break;
8532           case MT_STALEMATE:
8533             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8534             break;
8535         }
8536         done = TRUE;
8537         break;
8538
8539       case MoveNumberOne:
8540         if (lastLoadGameStart == GNUChessGame) {
8541             /* GNUChessGames have numbers, but they aren't move numbers */
8542             if (appData.debugMode)
8543               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8544                       yy_text, (int) moveType);
8545             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8546         }
8547         /* else fall thru */
8548
8549       case XBoardGame:
8550       case GNUChessGame:
8551       case PGNTag:
8552         /* Reached start of next game in file */
8553         if (appData.debugMode)
8554           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8555         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8556                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8557           case MT_NONE:
8558           case MT_CHECK:
8559             break;
8560           case MT_CHECKMATE:
8561           case MT_STAINMATE:
8562             if (WhiteOnMove(currentMove)) {
8563                 GameEnds(BlackWins, "Black mates", GE_FILE);
8564             } else {
8565                 GameEnds(WhiteWins, "White mates", GE_FILE);
8566             }
8567             break;
8568           case MT_STALEMATE:
8569             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8570             break;
8571         }
8572         done = TRUE;
8573         break;
8574
8575       case PositionDiagram:     /* should not happen; ignore */
8576       case ElapsedTime:         /* ignore */
8577       case NAG:                 /* ignore */
8578         if (appData.debugMode)
8579           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8580                   yy_text, (int) moveType);
8581         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8582
8583       case IllegalMove:
8584         if (appData.testLegality) {
8585             if (appData.debugMode)
8586               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8587             sprintf(move, _("Illegal move: %d.%s%s"),
8588                     (forwardMostMove / 2) + 1,
8589                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8590             DisplayError(move, 0);
8591             done = TRUE;
8592         } else {
8593             if (appData.debugMode)
8594               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8595                       yy_text, currentMoveString);
8596             fromX = currentMoveString[0] - AAA;
8597             fromY = currentMoveString[1] - ONE;
8598             toX = currentMoveString[2] - AAA;
8599             toY = currentMoveString[3] - ONE;
8600             promoChar = currentMoveString[4];
8601         }
8602         break;
8603
8604       case AmbiguousMove:
8605         if (appData.debugMode)
8606           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8607         sprintf(move, _("Ambiguous move: %d.%s%s"),
8608                 (forwardMostMove / 2) + 1,
8609                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8610         DisplayError(move, 0);
8611         done = TRUE;
8612         break;
8613
8614       default:
8615       case ImpossibleMove:
8616         if (appData.debugMode)
8617           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8618         sprintf(move, _("Illegal move: %d.%s%s"),
8619                 (forwardMostMove / 2) + 1,
8620                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8621         DisplayError(move, 0);
8622         done = TRUE;
8623         break;
8624     }
8625
8626     if (done) {
8627         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8628             DrawPosition(FALSE, boards[currentMove]);
8629             DisplayBothClocks();
8630             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8631               DisplayComment(currentMove - 1, commentList[currentMove]);
8632         }
8633         (void) StopLoadGameTimer();
8634         gameFileFP = NULL;
8635         cmailOldMove = forwardMostMove;
8636         return FALSE;
8637     } else {
8638         /* currentMoveString is set as a side-effect of yylex */
8639         strcat(currentMoveString, "\n");
8640         strcpy(moveList[forwardMostMove], currentMoveString);
8641         
8642         thinkOutput[0] = NULLCHAR;
8643         MakeMove(fromX, fromY, toX, toY, promoChar);
8644         currentMove = forwardMostMove;
8645         return TRUE;
8646     }
8647 }
8648
8649 /* Load the nth game from the given file */
8650 int
8651 LoadGameFromFile(filename, n, title, useList)
8652      char *filename;
8653      int n;
8654      char *title;
8655      /*Boolean*/ int useList;
8656 {
8657     FILE *f;
8658     char buf[MSG_SIZ];
8659
8660     if (strcmp(filename, "-") == 0) {
8661         f = stdin;
8662         title = "stdin";
8663     } else {
8664         f = fopen(filename, "rb");
8665         if (f == NULL) {
8666           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8667             DisplayError(buf, errno);
8668             return FALSE;
8669         }
8670     }
8671     if (fseek(f, 0, 0) == -1) {
8672         /* f is not seekable; probably a pipe */
8673         useList = FALSE;
8674     }
8675     if (useList && n == 0) {
8676         int error = GameListBuild(f);
8677         if (error) {
8678             DisplayError(_("Cannot build game list"), error);
8679         } else if (!ListEmpty(&gameList) &&
8680                    ((ListGame *) gameList.tailPred)->number > 1) {
8681             GameListPopUp(f, title);
8682             return TRUE;
8683         }
8684         GameListDestroy();
8685         n = 1;
8686     }
8687     if (n == 0) n = 1;
8688     return LoadGame(f, n, title, FALSE);
8689 }
8690
8691
8692 void
8693 MakeRegisteredMove()
8694 {
8695     int fromX, fromY, toX, toY;
8696     char promoChar;
8697     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8698         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8699           case CMAIL_MOVE:
8700           case CMAIL_DRAW:
8701             if (appData.debugMode)
8702               fprintf(debugFP, "Restoring %s for game %d\n",
8703                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8704     
8705             thinkOutput[0] = NULLCHAR;
8706             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8707             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8708             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8709             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8710             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8711             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8712             MakeMove(fromX, fromY, toX, toY, promoChar);
8713             ShowMove(fromX, fromY, toX, toY);
8714               
8715             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8716                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8717               case MT_NONE:
8718               case MT_CHECK:
8719                 break;
8720                 
8721               case MT_CHECKMATE:
8722               case MT_STAINMATE:
8723                 if (WhiteOnMove(currentMove)) {
8724                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8725                 } else {
8726                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8727                 }
8728                 break;
8729                 
8730               case MT_STALEMATE:
8731                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8732                 break;
8733             }
8734
8735             break;
8736             
8737           case CMAIL_RESIGN:
8738             if (WhiteOnMove(currentMove)) {
8739                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8740             } else {
8741                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8742             }
8743             break;
8744             
8745           case CMAIL_ACCEPT:
8746             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8747             break;
8748               
8749           default:
8750             break;
8751         }
8752     }
8753
8754     return;
8755 }
8756
8757 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8758 int
8759 CmailLoadGame(f, gameNumber, title, useList)
8760      FILE *f;
8761      int gameNumber;
8762      char *title;
8763      int useList;
8764 {
8765     int retVal;
8766
8767     if (gameNumber > nCmailGames) {
8768         DisplayError(_("No more games in this message"), 0);
8769         return FALSE;
8770     }
8771     if (f == lastLoadGameFP) {
8772         int offset = gameNumber - lastLoadGameNumber;
8773         if (offset == 0) {
8774             cmailMsg[0] = NULLCHAR;
8775             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8776                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8777                 nCmailMovesRegistered--;
8778             }
8779             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8780             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8781                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8782             }
8783         } else {
8784             if (! RegisterMove()) return FALSE;
8785         }
8786     }
8787
8788     retVal = LoadGame(f, gameNumber, title, useList);
8789
8790     /* Make move registered during previous look at this game, if any */
8791     MakeRegisteredMove();
8792
8793     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8794         commentList[currentMove]
8795           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8796         DisplayComment(currentMove - 1, commentList[currentMove]);
8797     }
8798
8799     return retVal;
8800 }
8801
8802 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8803 int
8804 ReloadGame(offset)
8805      int offset;
8806 {
8807     int gameNumber = lastLoadGameNumber + offset;
8808     if (lastLoadGameFP == NULL) {
8809         DisplayError(_("No game has been loaded yet"), 0);
8810         return FALSE;
8811     }
8812     if (gameNumber <= 0) {
8813         DisplayError(_("Can't back up any further"), 0);
8814         return FALSE;
8815     }
8816     if (cmailMsgLoaded) {
8817         return CmailLoadGame(lastLoadGameFP, gameNumber,
8818                              lastLoadGameTitle, lastLoadGameUseList);
8819     } else {
8820         return LoadGame(lastLoadGameFP, gameNumber,
8821                         lastLoadGameTitle, lastLoadGameUseList);
8822     }
8823 }
8824
8825
8826
8827 /* Load the nth game from open file f */
8828 int
8829 LoadGame(f, gameNumber, title, useList)
8830      FILE *f;
8831      int gameNumber;
8832      char *title;
8833      int useList;
8834 {
8835     ChessMove cm;
8836     char buf[MSG_SIZ];
8837     int gn = gameNumber;
8838     ListGame *lg = NULL;
8839     int numPGNTags = 0;
8840     int err;
8841     GameMode oldGameMode;
8842     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8843
8844     if (appData.debugMode) 
8845         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8846
8847     if (gameMode == Training )
8848         SetTrainingModeOff();
8849
8850     oldGameMode = gameMode;
8851     if (gameMode != BeginningOfGame) {
8852       Reset(FALSE, TRUE);
8853     }
8854
8855     gameFileFP = f;
8856     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8857         fclose(lastLoadGameFP);
8858     }
8859
8860     if (useList) {
8861         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8862         
8863         if (lg) {
8864             fseek(f, lg->offset, 0);
8865             GameListHighlight(gameNumber);
8866             gn = 1;
8867         }
8868         else {
8869             DisplayError(_("Game number out of range"), 0);
8870             return FALSE;
8871         }
8872     } else {
8873         GameListDestroy();
8874         if (fseek(f, 0, 0) == -1) {
8875             if (f == lastLoadGameFP ?
8876                 gameNumber == lastLoadGameNumber + 1 :
8877                 gameNumber == 1) {
8878                 gn = 1;
8879             } else {
8880                 DisplayError(_("Can't seek on game file"), 0);
8881                 return FALSE;
8882             }
8883         }
8884     }
8885     lastLoadGameFP = f;
8886     lastLoadGameNumber = gameNumber;
8887     strcpy(lastLoadGameTitle, title);
8888     lastLoadGameUseList = useList;
8889
8890     yynewfile(f);
8891
8892     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8893       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8894                 lg->gameInfo.black);
8895             DisplayTitle(buf);
8896     } else if (*title != NULLCHAR) {
8897         if (gameNumber > 1) {
8898             sprintf(buf, "%s %d", title, gameNumber);
8899             DisplayTitle(buf);
8900         } else {
8901             DisplayTitle(title);
8902         }
8903     }
8904
8905     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8906         gameMode = PlayFromGameFile;
8907         ModeHighlight();
8908     }
8909
8910     currentMove = forwardMostMove = backwardMostMove = 0;
8911     CopyBoard(boards[0], initialPosition);
8912     StopClocks();
8913
8914     /*
8915      * Skip the first gn-1 games in the file.
8916      * Also skip over anything that precedes an identifiable 
8917      * start of game marker, to avoid being confused by 
8918      * garbage at the start of the file.  Currently 
8919      * recognized start of game markers are the move number "1",
8920      * the pattern "gnuchess .* game", the pattern
8921      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8922      * A game that starts with one of the latter two patterns
8923      * will also have a move number 1, possibly
8924      * following a position diagram.
8925      * 5-4-02: Let's try being more lenient and allowing a game to
8926      * start with an unnumbered move.  Does that break anything?
8927      */
8928     cm = lastLoadGameStart = (ChessMove) 0;
8929     while (gn > 0) {
8930         yyboardindex = forwardMostMove;
8931         cm = (ChessMove) yylex();
8932         switch (cm) {
8933           case (ChessMove) 0:
8934             if (cmailMsgLoaded) {
8935                 nCmailGames = CMAIL_MAX_GAMES - gn;
8936             } else {
8937                 Reset(TRUE, TRUE);
8938                 DisplayError(_("Game not found in file"), 0);
8939             }
8940             return FALSE;
8941
8942           case GNUChessGame:
8943           case XBoardGame:
8944             gn--;
8945             lastLoadGameStart = cm;
8946             break;
8947             
8948           case MoveNumberOne:
8949             switch (lastLoadGameStart) {
8950               case GNUChessGame:
8951               case XBoardGame:
8952               case PGNTag:
8953                 break;
8954               case MoveNumberOne:
8955               case (ChessMove) 0:
8956                 gn--;           /* count this game */
8957                 lastLoadGameStart = cm;
8958                 break;
8959               default:
8960                 /* impossible */
8961                 break;
8962             }
8963             break;
8964
8965           case PGNTag:
8966             switch (lastLoadGameStart) {
8967               case GNUChessGame:
8968               case PGNTag:
8969               case MoveNumberOne:
8970               case (ChessMove) 0:
8971                 gn--;           /* count this game */
8972                 lastLoadGameStart = cm;
8973                 break;
8974               case XBoardGame:
8975                 lastLoadGameStart = cm; /* game counted already */
8976                 break;
8977               default:
8978                 /* impossible */
8979                 break;
8980             }
8981             if (gn > 0) {
8982                 do {
8983                     yyboardindex = forwardMostMove;
8984                     cm = (ChessMove) yylex();
8985                 } while (cm == PGNTag || cm == Comment);
8986             }
8987             break;
8988
8989           case WhiteWins:
8990           case BlackWins:
8991           case GameIsDrawn:
8992             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8993                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8994                     != CMAIL_OLD_RESULT) {
8995                     nCmailResults ++ ;
8996                     cmailResult[  CMAIL_MAX_GAMES
8997                                 - gn - 1] = CMAIL_OLD_RESULT;
8998                 }
8999             }
9000             break;
9001
9002           case NormalMove:
9003             /* Only a NormalMove can be at the start of a game
9004              * without a position diagram. */
9005             if (lastLoadGameStart == (ChessMove) 0) {
9006               gn--;
9007               lastLoadGameStart = MoveNumberOne;
9008             }
9009             break;
9010
9011           default:
9012             break;
9013         }
9014     }
9015     
9016     if (appData.debugMode)
9017       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9018
9019     if (cm == XBoardGame) {
9020         /* Skip any header junk before position diagram and/or move 1 */
9021         for (;;) {
9022             yyboardindex = forwardMostMove;
9023             cm = (ChessMove) yylex();
9024
9025             if (cm == (ChessMove) 0 ||
9026                 cm == GNUChessGame || cm == XBoardGame) {
9027                 /* Empty game; pretend end-of-file and handle later */
9028                 cm = (ChessMove) 0;
9029                 break;
9030             }
9031
9032             if (cm == MoveNumberOne || cm == PositionDiagram ||
9033                 cm == PGNTag || cm == Comment)
9034               break;
9035         }
9036     } else if (cm == GNUChessGame) {
9037         if (gameInfo.event != NULL) {
9038             free(gameInfo.event);
9039         }
9040         gameInfo.event = StrSave(yy_text);
9041     }   
9042
9043     startedFromSetupPosition = FALSE;
9044     while (cm == PGNTag) {
9045         if (appData.debugMode) 
9046           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9047         err = ParsePGNTag(yy_text, &gameInfo);
9048         if (!err) numPGNTags++;
9049
9050         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9051         if(gameInfo.variant != oldVariant) {
9052             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9053             InitPosition(TRUE);
9054             oldVariant = gameInfo.variant;
9055             if (appData.debugMode) 
9056               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9057         }
9058
9059
9060         if (gameInfo.fen != NULL) {
9061           Board initial_position;
9062           startedFromSetupPosition = TRUE;
9063           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9064             Reset(TRUE, TRUE);
9065             DisplayError(_("Bad FEN position in file"), 0);
9066             return FALSE;
9067           }
9068           CopyBoard(boards[0], initial_position);
9069           if (blackPlaysFirst) {
9070             currentMove = forwardMostMove = backwardMostMove = 1;
9071             CopyBoard(boards[1], initial_position);
9072             strcpy(moveList[0], "");
9073             strcpy(parseList[0], "");
9074             timeRemaining[0][1] = whiteTimeRemaining;
9075             timeRemaining[1][1] = blackTimeRemaining;
9076             if (commentList[0] != NULL) {
9077               commentList[1] = commentList[0];
9078               commentList[0] = NULL;
9079             }
9080           } else {
9081             currentMove = forwardMostMove = backwardMostMove = 0;
9082           }
9083           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9084           {   int i;
9085               initialRulePlies = FENrulePlies;
9086               epStatus[forwardMostMove] = FENepStatus;
9087               for( i=0; i< nrCastlingRights; i++ )
9088                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9089           }
9090           yyboardindex = forwardMostMove;
9091           free(gameInfo.fen);
9092           gameInfo.fen = NULL;
9093         }
9094
9095         yyboardindex = forwardMostMove;
9096         cm = (ChessMove) yylex();
9097
9098         /* Handle comments interspersed among the tags */
9099         while (cm == Comment) {
9100             char *p;
9101             if (appData.debugMode) 
9102               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9103             p = yy_text;
9104             if (*p == '{' || *p == '[' || *p == '(') {
9105                 p[strlen(p) - 1] = NULLCHAR;
9106                 p++;
9107             }
9108             while (*p == '\n') p++;
9109             AppendComment(currentMove, p);
9110             yyboardindex = forwardMostMove;
9111             cm = (ChessMove) yylex();
9112         }
9113     }
9114
9115     /* don't rely on existence of Event tag since if game was
9116      * pasted from clipboard the Event tag may not exist
9117      */
9118     if (numPGNTags > 0){
9119         char *tags;
9120         if (gameInfo.variant == VariantNormal) {
9121           gameInfo.variant = StringToVariant(gameInfo.event);
9122         }
9123         if (!matchMode) {
9124           if( appData.autoDisplayTags ) {
9125             tags = PGNTags(&gameInfo);
9126             TagsPopUp(tags, CmailMsg());
9127             free(tags);
9128           }
9129         }
9130     } else {
9131         /* Make something up, but don't display it now */
9132         SetGameInfo();
9133         TagsPopDown();
9134     }
9135
9136     if (cm == PositionDiagram) {
9137         int i, j;
9138         char *p;
9139         Board initial_position;
9140
9141         if (appData.debugMode)
9142           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9143
9144         if (!startedFromSetupPosition) {
9145             p = yy_text;
9146             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9147               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9148                 switch (*p) {
9149                   case '[':
9150                   case '-':
9151                   case ' ':
9152                   case '\t':
9153                   case '\n':
9154                   case '\r':
9155                     break;
9156                   default:
9157                     initial_position[i][j++] = CharToPiece(*p);
9158                     break;
9159                 }
9160             while (*p == ' ' || *p == '\t' ||
9161                    *p == '\n' || *p == '\r') p++;
9162         
9163             if (strncmp(p, "black", strlen("black"))==0)
9164               blackPlaysFirst = TRUE;
9165             else
9166               blackPlaysFirst = FALSE;
9167             startedFromSetupPosition = TRUE;
9168         
9169             CopyBoard(boards[0], initial_position);
9170             if (blackPlaysFirst) {
9171                 currentMove = forwardMostMove = backwardMostMove = 1;
9172                 CopyBoard(boards[1], initial_position);
9173                 strcpy(moveList[0], "");
9174                 strcpy(parseList[0], "");
9175                 timeRemaining[0][1] = whiteTimeRemaining;
9176                 timeRemaining[1][1] = blackTimeRemaining;
9177                 if (commentList[0] != NULL) {
9178                     commentList[1] = commentList[0];
9179                     commentList[0] = NULL;
9180                 }
9181             } else {
9182                 currentMove = forwardMostMove = backwardMostMove = 0;
9183             }
9184         }
9185         yyboardindex = forwardMostMove;
9186         cm = (ChessMove) yylex();
9187     }
9188
9189     if (first.pr == NoProc) {
9190         StartChessProgram(&first);
9191     }
9192     InitChessProgram(&first, FALSE);
9193     SendToProgram("force\n", &first);
9194     if (startedFromSetupPosition) {
9195         SendBoard(&first, forwardMostMove);
9196     if (appData.debugMode) {
9197         fprintf(debugFP, "Load Game\n");
9198     }
9199         DisplayBothClocks();
9200     }      
9201
9202     /* [HGM] server: flag to write setup moves in broadcast file as one */
9203     loadFlag = appData.suppressLoadMoves;
9204
9205     while (cm == Comment) {
9206         char *p;
9207         if (appData.debugMode) 
9208           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9209         p = yy_text;
9210         if (*p == '{' || *p == '[' || *p == '(') {
9211             p[strlen(p) - 1] = NULLCHAR;
9212             p++;
9213         }
9214         while (*p == '\n') p++;
9215         AppendComment(currentMove, p);
9216         yyboardindex = forwardMostMove;
9217         cm = (ChessMove) yylex();
9218     }
9219
9220     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9221         cm == WhiteWins || cm == BlackWins ||
9222         cm == GameIsDrawn || cm == GameUnfinished) {
9223         DisplayMessage("", _("No moves in game"));
9224         if (cmailMsgLoaded) {
9225             if (appData.debugMode)
9226               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9227             ClearHighlights();
9228             flipView = FALSE;
9229         }
9230         DrawPosition(FALSE, boards[currentMove]);
9231         DisplayBothClocks();
9232         gameMode = EditGame;
9233         ModeHighlight();
9234         gameFileFP = NULL;
9235         cmailOldMove = 0;
9236         return TRUE;
9237     }
9238
9239     // [HGM] PV info: routine tests if comment empty
9240     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9241         DisplayComment(currentMove - 1, commentList[currentMove]);
9242     }
9243     if (!matchMode && appData.timeDelay != 0) 
9244       DrawPosition(FALSE, boards[currentMove]);
9245
9246     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9247       programStats.ok_to_send = 1;
9248     }
9249
9250     /* if the first token after the PGN tags is a move
9251      * and not move number 1, retrieve it from the parser 
9252      */
9253     if (cm != MoveNumberOne)
9254         LoadGameOneMove(cm);
9255
9256     /* load the remaining moves from the file */
9257     while (LoadGameOneMove((ChessMove)0)) {
9258       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9259       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9260     }
9261
9262     /* rewind to the start of the game */
9263     currentMove = backwardMostMove;
9264
9265     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9266
9267     if (oldGameMode == AnalyzeFile ||
9268         oldGameMode == AnalyzeMode) {
9269       AnalyzeFileEvent();
9270     }
9271
9272     if (matchMode || appData.timeDelay == 0) {
9273       ToEndEvent();
9274       gameMode = EditGame;
9275       ModeHighlight();
9276     } else if (appData.timeDelay > 0) {
9277       AutoPlayGameLoop();
9278     }
9279
9280     if (appData.debugMode) 
9281         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9282
9283     loadFlag = 0; /* [HGM] true game starts */
9284     return TRUE;
9285 }
9286
9287 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9288 int
9289 ReloadPosition(offset)
9290      int offset;
9291 {
9292     int positionNumber = lastLoadPositionNumber + offset;
9293     if (lastLoadPositionFP == NULL) {
9294         DisplayError(_("No position has been loaded yet"), 0);
9295         return FALSE;
9296     }
9297     if (positionNumber <= 0) {
9298         DisplayError(_("Can't back up any further"), 0);
9299         return FALSE;
9300     }
9301     return LoadPosition(lastLoadPositionFP, positionNumber,
9302                         lastLoadPositionTitle);
9303 }
9304
9305 /* Load the nth position from the given file */
9306 int
9307 LoadPositionFromFile(filename, n, title)
9308      char *filename;
9309      int n;
9310      char *title;
9311 {
9312     FILE *f;
9313     char buf[MSG_SIZ];
9314
9315     if (strcmp(filename, "-") == 0) {
9316         return LoadPosition(stdin, n, "stdin");
9317     } else {
9318         f = fopen(filename, "rb");
9319         if (f == NULL) {
9320             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9321             DisplayError(buf, errno);
9322             return FALSE;
9323         } else {
9324             return LoadPosition(f, n, title);
9325         }
9326     }
9327 }
9328
9329 /* Load the nth position from the given open file, and close it */
9330 int
9331 LoadPosition(f, positionNumber, title)
9332      FILE *f;
9333      int positionNumber;
9334      char *title;
9335 {
9336     char *p, line[MSG_SIZ];
9337     Board initial_position;
9338     int i, j, fenMode, pn;
9339     
9340     if (gameMode == Training )
9341         SetTrainingModeOff();
9342
9343     if (gameMode != BeginningOfGame) {
9344         Reset(FALSE, TRUE);
9345     }
9346     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9347         fclose(lastLoadPositionFP);
9348     }
9349     if (positionNumber == 0) positionNumber = 1;
9350     lastLoadPositionFP = f;
9351     lastLoadPositionNumber = positionNumber;
9352     strcpy(lastLoadPositionTitle, title);
9353     if (first.pr == NoProc) {
9354       StartChessProgram(&first);
9355       InitChessProgram(&first, FALSE);
9356     }    
9357     pn = positionNumber;
9358     if (positionNumber < 0) {
9359         /* Negative position number means to seek to that byte offset */
9360         if (fseek(f, -positionNumber, 0) == -1) {
9361             DisplayError(_("Can't seek on position file"), 0);
9362             return FALSE;
9363         };
9364         pn = 1;
9365     } else {
9366         if (fseek(f, 0, 0) == -1) {
9367             if (f == lastLoadPositionFP ?
9368                 positionNumber == lastLoadPositionNumber + 1 :
9369                 positionNumber == 1) {
9370                 pn = 1;
9371             } else {
9372                 DisplayError(_("Can't seek on position file"), 0);
9373                 return FALSE;
9374             }
9375         }
9376     }
9377     /* See if this file is FEN or old-style xboard */
9378     if (fgets(line, MSG_SIZ, f) == NULL) {
9379         DisplayError(_("Position not found in file"), 0);
9380         return FALSE;
9381     }
9382 #if 0
9383     switch (line[0]) {
9384       case '#':  case 'x':
9385       default:
9386         fenMode = FALSE;
9387         break;
9388       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9389       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9390       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9391       case '7':  case '8':  case '9':
9392       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9393       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9394       case 'C':  case 'W':             case 'c':  case 'w': 
9395         fenMode = TRUE;
9396         break;
9397     }
9398 #else
9399     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9400     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9401 #endif
9402
9403     if (pn >= 2) {
9404         if (fenMode || line[0] == '#') pn--;
9405         while (pn > 0) {
9406             /* skip positions before number pn */
9407             if (fgets(line, MSG_SIZ, f) == NULL) {
9408                 Reset(TRUE, TRUE);
9409                 DisplayError(_("Position not found in file"), 0);
9410                 return FALSE;
9411             }
9412             if (fenMode || line[0] == '#') pn--;
9413         }
9414     }
9415
9416     if (fenMode) {
9417         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9418             DisplayError(_("Bad FEN position in file"), 0);
9419             return FALSE;
9420         }
9421     } else {
9422         (void) fgets(line, MSG_SIZ, f);
9423         (void) fgets(line, MSG_SIZ, f);
9424     
9425         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9426             (void) fgets(line, MSG_SIZ, f);
9427             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9428                 if (*p == ' ')
9429                   continue;
9430                 initial_position[i][j++] = CharToPiece(*p);
9431             }
9432         }
9433     
9434         blackPlaysFirst = FALSE;
9435         if (!feof(f)) {
9436             (void) fgets(line, MSG_SIZ, f);
9437             if (strncmp(line, "black", strlen("black"))==0)
9438               blackPlaysFirst = TRUE;
9439         }
9440     }
9441     startedFromSetupPosition = TRUE;
9442     
9443     SendToProgram("force\n", &first);
9444     CopyBoard(boards[0], initial_position);
9445     if (blackPlaysFirst) {
9446         currentMove = forwardMostMove = backwardMostMove = 1;
9447         strcpy(moveList[0], "");
9448         strcpy(parseList[0], "");
9449         CopyBoard(boards[1], initial_position);
9450         DisplayMessage("", _("Black to play"));
9451     } else {
9452         currentMove = forwardMostMove = backwardMostMove = 0;
9453         DisplayMessage("", _("White to play"));
9454     }
9455           /* [HGM] copy FEN attributes as well */
9456           {   int i;
9457               initialRulePlies = FENrulePlies;
9458               epStatus[forwardMostMove] = FENepStatus;
9459               for( i=0; i< nrCastlingRights; i++ )
9460                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9461           }
9462     SendBoard(&first, forwardMostMove);
9463     if (appData.debugMode) {
9464 int i, j;
9465   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9466   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9467         fprintf(debugFP, "Load Position\n");
9468     }
9469
9470     if (positionNumber > 1) {
9471         sprintf(line, "%s %d", title, positionNumber);
9472         DisplayTitle(line);
9473     } else {
9474         DisplayTitle(title);
9475     }
9476     gameMode = EditGame;
9477     ModeHighlight();
9478     ResetClocks();
9479     timeRemaining[0][1] = whiteTimeRemaining;
9480     timeRemaining[1][1] = blackTimeRemaining;
9481     DrawPosition(FALSE, boards[currentMove]);
9482    
9483     return TRUE;
9484 }
9485
9486
9487 void
9488 CopyPlayerNameIntoFileName(dest, src)
9489      char **dest, *src;
9490 {
9491     while (*src != NULLCHAR && *src != ',') {
9492         if (*src == ' ') {
9493             *(*dest)++ = '_';
9494             src++;
9495         } else {
9496             *(*dest)++ = *src++;
9497         }
9498     }
9499 }
9500
9501 char *DefaultFileName(ext)
9502      char *ext;
9503 {
9504     static char def[MSG_SIZ];
9505     char *p;
9506
9507     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9508         p = def;
9509         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9510         *p++ = '-';
9511         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9512         *p++ = '.';
9513         strcpy(p, ext);
9514     } else {
9515         def[0] = NULLCHAR;
9516     }
9517     return def;
9518 }
9519
9520 /* Save the current game to the given file */
9521 int
9522 SaveGameToFile(filename, append)
9523      char *filename;
9524      int append;
9525 {
9526     FILE *f;
9527     char buf[MSG_SIZ];
9528
9529     if (strcmp(filename, "-") == 0) {
9530         return SaveGame(stdout, 0, NULL);
9531     } else {
9532         f = fopen(filename, append ? "a" : "w");
9533         if (f == NULL) {
9534             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9535             DisplayError(buf, errno);
9536             return FALSE;
9537         } else {
9538             return SaveGame(f, 0, NULL);
9539         }
9540     }
9541 }
9542
9543 char *
9544 SavePart(str)
9545      char *str;
9546 {
9547     static char buf[MSG_SIZ];
9548     char *p;
9549     
9550     p = strchr(str, ' ');
9551     if (p == NULL) return str;
9552     strncpy(buf, str, p - str);
9553     buf[p - str] = NULLCHAR;
9554     return buf;
9555 }
9556
9557 #define PGN_MAX_LINE 75
9558
9559 #define PGN_SIDE_WHITE  0
9560 #define PGN_SIDE_BLACK  1
9561
9562 /* [AS] */
9563 static int FindFirstMoveOutOfBook( int side )
9564 {
9565     int result = -1;
9566
9567     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9568         int index = backwardMostMove;
9569         int has_book_hit = 0;
9570
9571         if( (index % 2) != side ) {
9572             index++;
9573         }
9574
9575         while( index < forwardMostMove ) {
9576             /* Check to see if engine is in book */
9577             int depth = pvInfoList[index].depth;
9578             int score = pvInfoList[index].score;
9579             int in_book = 0;
9580
9581             if( depth <= 2 ) {
9582                 in_book = 1;
9583             }
9584             else if( score == 0 && depth == 63 ) {
9585                 in_book = 1; /* Zappa */
9586             }
9587             else if( score == 2 && depth == 99 ) {
9588                 in_book = 1; /* Abrok */
9589             }
9590
9591             has_book_hit += in_book;
9592
9593             if( ! in_book ) {
9594                 result = index;
9595
9596                 break;
9597             }
9598
9599             index += 2;
9600         }
9601     }
9602
9603     return result;
9604 }
9605
9606 /* [AS] */
9607 void GetOutOfBookInfo( char * buf )
9608 {
9609     int oob[2];
9610     int i;
9611     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9612
9613     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9614     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9615
9616     *buf = '\0';
9617
9618     if( oob[0] >= 0 || oob[1] >= 0 ) {
9619         for( i=0; i<2; i++ ) {
9620             int idx = oob[i];
9621
9622             if( idx >= 0 ) {
9623                 if( i > 0 && oob[0] >= 0 ) {
9624                     strcat( buf, "   " );
9625                 }
9626
9627                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9628                 sprintf( buf+strlen(buf), "%s%.2f", 
9629                     pvInfoList[idx].score >= 0 ? "+" : "",
9630                     pvInfoList[idx].score / 100.0 );
9631             }
9632         }
9633     }
9634 }
9635
9636 /* Save game in PGN style and close the file */
9637 int
9638 SaveGamePGN(f)
9639      FILE *f;
9640 {
9641     int i, offset, linelen, newblock;
9642     time_t tm;
9643 //    char *movetext;
9644     char numtext[32];
9645     int movelen, numlen, blank;
9646     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9647
9648     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9649     
9650     tm = time((time_t *) NULL);
9651     
9652     PrintPGNTags(f, &gameInfo);
9653     
9654     if (backwardMostMove > 0 || startedFromSetupPosition) {
9655         char *fen = PositionToFEN(backwardMostMove, NULL);
9656         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9657         fprintf(f, "\n{--------------\n");
9658         PrintPosition(f, backwardMostMove);
9659         fprintf(f, "--------------}\n");
9660         free(fen);
9661     }
9662     else {
9663         /* [AS] Out of book annotation */
9664         if( appData.saveOutOfBookInfo ) {
9665             char buf[64];
9666
9667             GetOutOfBookInfo( buf );
9668
9669             if( buf[0] != '\0' ) {
9670                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9671             }
9672         }
9673
9674         fprintf(f, "\n");
9675     }
9676
9677     i = backwardMostMove;
9678     linelen = 0;
9679     newblock = TRUE;
9680
9681     while (i < forwardMostMove) {
9682         /* Print comments preceding this move */
9683         if (commentList[i] != NULL) {
9684             if (linelen > 0) fprintf(f, "\n");
9685             fprintf(f, "{\n%s}\n", commentList[i]);
9686             linelen = 0;
9687             newblock = TRUE;
9688         }
9689
9690         /* Format move number */
9691         if ((i % 2) == 0) {
9692             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9693         } else {
9694             if (newblock) {
9695                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9696             } else {
9697                 numtext[0] = NULLCHAR;
9698             }
9699         }
9700         numlen = strlen(numtext);
9701         newblock = FALSE;
9702
9703         /* Print move number */
9704         blank = linelen > 0 && numlen > 0;
9705         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9706             fprintf(f, "\n");
9707             linelen = 0;
9708             blank = 0;
9709         }
9710         if (blank) {
9711             fprintf(f, " ");
9712             linelen++;
9713         }
9714         fprintf(f, numtext);
9715         linelen += numlen;
9716
9717         /* Get move */
9718         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9719         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9720 #if 0
9721         // SavePart already does this!
9722         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9723                 int p = movelen - 1;
9724                 if(move_buffer[p] == ' ') p--;
9725                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9726                     while(p && move_buffer[--p] != '(');
9727                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9728                 }
9729         }
9730 #endif
9731         /* Print move */
9732         blank = linelen > 0 && movelen > 0;
9733         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9734             fprintf(f, "\n");
9735             linelen = 0;
9736             blank = 0;
9737         }
9738         if (blank) {
9739             fprintf(f, " ");
9740             linelen++;
9741         }
9742         fprintf(f, move_buffer);
9743         linelen += movelen;
9744
9745         /* [AS] Add PV info if present */
9746         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9747             /* [HGM] add time */
9748             char buf[MSG_SIZ]; int seconds = 0;
9749
9750 #if 1
9751             if(i >= backwardMostMove) {
9752                 if(WhiteOnMove(i))
9753                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9754                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9755                 else
9756                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9757                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9758             }
9759             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9760 #else
9761             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9762 #endif
9763
9764             if( seconds <= 0) buf[0] = 0; else
9765             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9766                 seconds = (seconds + 4)/10; // round to full seconds
9767                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9768                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9769             }
9770
9771             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9772                 pvInfoList[i].score >= 0 ? "+" : "",
9773                 pvInfoList[i].score / 100.0,
9774                 pvInfoList[i].depth,
9775                 buf );
9776
9777             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9778
9779             /* Print score/depth */
9780             blank = linelen > 0 && movelen > 0;
9781             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9782                 fprintf(f, "\n");
9783                 linelen = 0;
9784                 blank = 0;
9785             }
9786             if (blank) {
9787                 fprintf(f, " ");
9788                 linelen++;
9789             }
9790             fprintf(f, move_buffer);
9791             linelen += movelen;
9792         }
9793
9794         i++;
9795     }
9796     
9797     /* Start a new line */
9798     if (linelen > 0) fprintf(f, "\n");
9799
9800     /* Print comments after last move */
9801     if (commentList[i] != NULL) {
9802         fprintf(f, "{\n%s}\n", commentList[i]);
9803     }
9804
9805     /* Print result */
9806     if (gameInfo.resultDetails != NULL &&
9807         gameInfo.resultDetails[0] != NULLCHAR) {
9808         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9809                 PGNResult(gameInfo.result));
9810     } else {
9811         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9812     }
9813
9814     fclose(f);
9815     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9816     return TRUE;
9817 }
9818
9819 /* Save game in old style and close the file */
9820 int
9821 SaveGameOldStyle(f)
9822      FILE *f;
9823 {
9824     int i, offset;
9825     time_t tm;
9826     
9827     tm = time((time_t *) NULL);
9828     
9829     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9830     PrintOpponents(f);
9831     
9832     if (backwardMostMove > 0 || startedFromSetupPosition) {
9833         fprintf(f, "\n[--------------\n");
9834         PrintPosition(f, backwardMostMove);
9835         fprintf(f, "--------------]\n");
9836     } else {
9837         fprintf(f, "\n");
9838     }
9839
9840     i = backwardMostMove;
9841     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9842
9843     while (i < forwardMostMove) {
9844         if (commentList[i] != NULL) {
9845             fprintf(f, "[%s]\n", commentList[i]);
9846         }
9847
9848         if ((i % 2) == 1) {
9849             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9850             i++;
9851         } else {
9852             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9853             i++;
9854             if (commentList[i] != NULL) {
9855                 fprintf(f, "\n");
9856                 continue;
9857             }
9858             if (i >= forwardMostMove) {
9859                 fprintf(f, "\n");
9860                 break;
9861             }
9862             fprintf(f, "%s\n", parseList[i]);
9863             i++;
9864         }
9865     }
9866     
9867     if (commentList[i] != NULL) {
9868         fprintf(f, "[%s]\n", commentList[i]);
9869     }
9870
9871     /* This isn't really the old style, but it's close enough */
9872     if (gameInfo.resultDetails != NULL &&
9873         gameInfo.resultDetails[0] != NULLCHAR) {
9874         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9875                 gameInfo.resultDetails);
9876     } else {
9877         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9878     }
9879
9880     fclose(f);
9881     return TRUE;
9882 }
9883
9884 /* Save the current game to open file f and close the file */
9885 int
9886 SaveGame(f, dummy, dummy2)
9887      FILE *f;
9888      int dummy;
9889      char *dummy2;
9890 {
9891     if (gameMode == EditPosition) EditPositionDone();
9892     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9893     if (appData.oldSaveStyle)
9894       return SaveGameOldStyle(f);
9895     else
9896       return SaveGamePGN(f);
9897 }
9898
9899 /* Save the current position to the given file */
9900 int
9901 SavePositionToFile(filename)
9902      char *filename;
9903 {
9904     FILE *f;
9905     char buf[MSG_SIZ];
9906
9907     if (strcmp(filename, "-") == 0) {
9908         return SavePosition(stdout, 0, NULL);
9909     } else {
9910         f = fopen(filename, "a");
9911         if (f == NULL) {
9912             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9913             DisplayError(buf, errno);
9914             return FALSE;
9915         } else {
9916             SavePosition(f, 0, NULL);
9917             return TRUE;
9918         }
9919     }
9920 }
9921
9922 /* Save the current position to the given open file and close the file */
9923 int
9924 SavePosition(f, dummy, dummy2)
9925      FILE *f;
9926      int dummy;
9927      char *dummy2;
9928 {
9929     time_t tm;
9930     char *fen;
9931     
9932     if (appData.oldSaveStyle) {
9933         tm = time((time_t *) NULL);
9934     
9935         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9936         PrintOpponents(f);
9937         fprintf(f, "[--------------\n");
9938         PrintPosition(f, currentMove);
9939         fprintf(f, "--------------]\n");
9940     } else {
9941         fen = PositionToFEN(currentMove, NULL);
9942         fprintf(f, "%s\n", fen);
9943         free(fen);
9944     }
9945     fclose(f);
9946     return TRUE;
9947 }
9948
9949 void
9950 ReloadCmailMsgEvent(unregister)
9951      int unregister;
9952 {
9953 #if !WIN32
9954     static char *inFilename = NULL;
9955     static char *outFilename;
9956     int i;
9957     struct stat inbuf, outbuf;
9958     int status;
9959     
9960     /* Any registered moves are unregistered if unregister is set, */
9961     /* i.e. invoked by the signal handler */
9962     if (unregister) {
9963         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9964             cmailMoveRegistered[i] = FALSE;
9965             if (cmailCommentList[i] != NULL) {
9966                 free(cmailCommentList[i]);
9967                 cmailCommentList[i] = NULL;
9968             }
9969         }
9970         nCmailMovesRegistered = 0;
9971     }
9972
9973     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9974         cmailResult[i] = CMAIL_NOT_RESULT;
9975     }
9976     nCmailResults = 0;
9977
9978     if (inFilename == NULL) {
9979         /* Because the filenames are static they only get malloced once  */
9980         /* and they never get freed                                      */
9981         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9982         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9983
9984         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9985         sprintf(outFilename, "%s.out", appData.cmailGameName);
9986     }
9987     
9988     status = stat(outFilename, &outbuf);
9989     if (status < 0) {
9990         cmailMailedMove = FALSE;
9991     } else {
9992         status = stat(inFilename, &inbuf);
9993         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9994     }
9995     
9996     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9997        counts the games, notes how each one terminated, etc.
9998        
9999        It would be nice to remove this kludge and instead gather all
10000        the information while building the game list.  (And to keep it
10001        in the game list nodes instead of having a bunch of fixed-size
10002        parallel arrays.)  Note this will require getting each game's
10003        termination from the PGN tags, as the game list builder does
10004        not process the game moves.  --mann
10005        */
10006     cmailMsgLoaded = TRUE;
10007     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10008     
10009     /* Load first game in the file or popup game menu */
10010     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10011
10012 #endif /* !WIN32 */
10013     return;
10014 }
10015
10016 int
10017 RegisterMove()
10018 {
10019     FILE *f;
10020     char string[MSG_SIZ];
10021
10022     if (   cmailMailedMove
10023         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10024         return TRUE;            /* Allow free viewing  */
10025     }
10026
10027     /* Unregister move to ensure that we don't leave RegisterMove        */
10028     /* with the move registered when the conditions for registering no   */
10029     /* longer hold                                                       */
10030     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10031         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10032         nCmailMovesRegistered --;
10033
10034         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10035           {
10036               free(cmailCommentList[lastLoadGameNumber - 1]);
10037               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10038           }
10039     }
10040
10041     if (cmailOldMove == -1) {
10042         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10043         return FALSE;
10044     }
10045
10046     if (currentMove > cmailOldMove + 1) {
10047         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10048         return FALSE;
10049     }
10050
10051     if (currentMove < cmailOldMove) {
10052         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10053         return FALSE;
10054     }
10055
10056     if (forwardMostMove > currentMove) {
10057         /* Silently truncate extra moves */
10058         TruncateGame();
10059     }
10060
10061     if (   (currentMove == cmailOldMove + 1)
10062         || (   (currentMove == cmailOldMove)
10063             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10064                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10065         if (gameInfo.result != GameUnfinished) {
10066             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10067         }
10068
10069         if (commentList[currentMove] != NULL) {
10070             cmailCommentList[lastLoadGameNumber - 1]
10071               = StrSave(commentList[currentMove]);
10072         }
10073         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10074
10075         if (appData.debugMode)
10076           fprintf(debugFP, "Saving %s for game %d\n",
10077                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10078
10079         sprintf(string,
10080                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10081         
10082         f = fopen(string, "w");
10083         if (appData.oldSaveStyle) {
10084             SaveGameOldStyle(f); /* also closes the file */
10085             
10086             sprintf(string, "%s.pos.out", appData.cmailGameName);
10087             f = fopen(string, "w");
10088             SavePosition(f, 0, NULL); /* also closes the file */
10089         } else {
10090             fprintf(f, "{--------------\n");
10091             PrintPosition(f, currentMove);
10092             fprintf(f, "--------------}\n\n");
10093             
10094             SaveGame(f, 0, NULL); /* also closes the file*/
10095         }
10096         
10097         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10098         nCmailMovesRegistered ++;
10099     } else if (nCmailGames == 1) {
10100         DisplayError(_("You have not made a move yet"), 0);
10101         return FALSE;
10102     }
10103
10104     return TRUE;
10105 }
10106
10107 void
10108 MailMoveEvent()
10109 {
10110 #if !WIN32
10111     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10112     FILE *commandOutput;
10113     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10114     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10115     int nBuffers;
10116     int i;
10117     int archived;
10118     char *arcDir;
10119
10120     if (! cmailMsgLoaded) {
10121         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10122         return;
10123     }
10124
10125     if (nCmailGames == nCmailResults) {
10126         DisplayError(_("No unfinished games"), 0);
10127         return;
10128     }
10129
10130 #if CMAIL_PROHIBIT_REMAIL
10131     if (cmailMailedMove) {
10132         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);
10133         DisplayError(msg, 0);
10134         return;
10135     }
10136 #endif
10137
10138     if (! (cmailMailedMove || RegisterMove())) return;
10139     
10140     if (   cmailMailedMove
10141         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10142         sprintf(string, partCommandString,
10143                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10144         commandOutput = popen(string, "r");
10145
10146         if (commandOutput == NULL) {
10147             DisplayError(_("Failed to invoke cmail"), 0);
10148         } else {
10149             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10150                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10151             }
10152             if (nBuffers > 1) {
10153                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10154                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10155                 nBytes = MSG_SIZ - 1;
10156             } else {
10157                 (void) memcpy(msg, buffer, nBytes);
10158             }
10159             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10160
10161             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10162                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10163
10164                 archived = TRUE;
10165                 for (i = 0; i < nCmailGames; i ++) {
10166                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10167                         archived = FALSE;
10168                     }
10169                 }
10170                 if (   archived
10171                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10172                         != NULL)) {
10173                     sprintf(buffer, "%s/%s.%s.archive",
10174                             arcDir,
10175                             appData.cmailGameName,
10176                             gameInfo.date);
10177                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10178                     cmailMsgLoaded = FALSE;
10179                 }
10180             }
10181
10182             DisplayInformation(msg);
10183             pclose(commandOutput);
10184         }
10185     } else {
10186         if ((*cmailMsg) != '\0') {
10187             DisplayInformation(cmailMsg);
10188         }
10189     }
10190
10191     return;
10192 #endif /* !WIN32 */
10193 }
10194
10195 char *
10196 CmailMsg()
10197 {
10198 #if WIN32
10199     return NULL;
10200 #else
10201     int  prependComma = 0;
10202     char number[5];
10203     char string[MSG_SIZ];       /* Space for game-list */
10204     int  i;
10205     
10206     if (!cmailMsgLoaded) return "";
10207
10208     if (cmailMailedMove) {
10209         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10210     } else {
10211         /* Create a list of games left */
10212         sprintf(string, "[");
10213         for (i = 0; i < nCmailGames; i ++) {
10214             if (! (   cmailMoveRegistered[i]
10215                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10216                 if (prependComma) {
10217                     sprintf(number, ",%d", i + 1);
10218                 } else {
10219                     sprintf(number, "%d", i + 1);
10220                     prependComma = 1;
10221                 }
10222                 
10223                 strcat(string, number);
10224             }
10225         }
10226         strcat(string, "]");
10227
10228         if (nCmailMovesRegistered + nCmailResults == 0) {
10229             switch (nCmailGames) {
10230               case 1:
10231                 sprintf(cmailMsg,
10232                         _("Still need to make move for game\n"));
10233                 break;
10234                 
10235               case 2:
10236                 sprintf(cmailMsg,
10237                         _("Still need to make moves for both games\n"));
10238                 break;
10239                 
10240               default:
10241                 sprintf(cmailMsg,
10242                         _("Still need to make moves for all %d games\n"),
10243                         nCmailGames);
10244                 break;
10245             }
10246         } else {
10247             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10248               case 1:
10249                 sprintf(cmailMsg,
10250                         _("Still need to make a move for game %s\n"),
10251                         string);
10252                 break;
10253                 
10254               case 0:
10255                 if (nCmailResults == nCmailGames) {
10256                     sprintf(cmailMsg, _("No unfinished games\n"));
10257                 } else {
10258                     sprintf(cmailMsg, _("Ready to send mail\n"));
10259                 }
10260                 break;
10261                 
10262               default:
10263                 sprintf(cmailMsg,
10264                         _("Still need to make moves for games %s\n"),
10265                         string);
10266             }
10267         }
10268     }
10269     return cmailMsg;
10270 #endif /* WIN32 */
10271 }
10272
10273 void
10274 ResetGameEvent()
10275 {
10276     if (gameMode == Training)
10277       SetTrainingModeOff();
10278
10279     Reset(TRUE, TRUE);
10280     cmailMsgLoaded = FALSE;
10281     if (appData.icsActive) {
10282       SendToICS(ics_prefix);
10283       SendToICS("refresh\n");
10284     }
10285 }
10286
10287 void
10288 ExitEvent(status)
10289      int status;
10290 {
10291     exiting++;
10292     if (exiting > 2) {
10293       /* Give up on clean exit */
10294       exit(status);
10295     }
10296     if (exiting > 1) {
10297       /* Keep trying for clean exit */
10298       return;
10299     }
10300
10301     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10302
10303     if (telnetISR != NULL) {
10304       RemoveInputSource(telnetISR);
10305     }
10306     if (icsPR != NoProc) {
10307       DestroyChildProcess(icsPR, TRUE);
10308     }
10309 #if 0
10310     /* Save game if resource set and not already saved by GameEnds() */
10311     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10312                              && forwardMostMove > 0) {
10313       if (*appData.saveGameFile != NULLCHAR) {
10314         SaveGameToFile(appData.saveGameFile, TRUE);
10315       } else if (appData.autoSaveGames) {
10316         AutoSaveGame();
10317       }
10318       if (*appData.savePositionFile != NULLCHAR) {
10319         SavePositionToFile(appData.savePositionFile);
10320       }
10321     }
10322     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10323 #else
10324     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10325     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10326 #endif
10327     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10328     /* make sure this other one finishes before killing it!                  */
10329     if(endingGame) { int count = 0;
10330         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10331         while(endingGame && count++ < 10) DoSleep(1);
10332         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10333     }
10334
10335     /* Kill off chess programs */
10336     if (first.pr != NoProc) {
10337         ExitAnalyzeMode();
10338         
10339         DoSleep( appData.delayBeforeQuit );
10340         SendToProgram("quit\n", &first);
10341         DoSleep( appData.delayAfterQuit );
10342         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10343     }
10344     if (second.pr != NoProc) {
10345         DoSleep( appData.delayBeforeQuit );
10346         SendToProgram("quit\n", &second);
10347         DoSleep( appData.delayAfterQuit );
10348         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10349     }
10350     if (first.isr != NULL) {
10351         RemoveInputSource(first.isr);
10352     }
10353     if (second.isr != NULL) {
10354         RemoveInputSource(second.isr);
10355     }
10356
10357     ShutDownFrontEnd();
10358     exit(status);
10359 }
10360
10361 void
10362 PauseEvent()
10363 {
10364     if (appData.debugMode)
10365         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10366     if (pausing) {
10367         pausing = FALSE;
10368         ModeHighlight();
10369         if (gameMode == MachinePlaysWhite ||
10370             gameMode == MachinePlaysBlack) {
10371             StartClocks();
10372         } else {
10373             DisplayBothClocks();
10374         }
10375         if (gameMode == PlayFromGameFile) {
10376             if (appData.timeDelay >= 0) 
10377                 AutoPlayGameLoop();
10378         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10379             Reset(FALSE, TRUE);
10380             SendToICS(ics_prefix);
10381             SendToICS("refresh\n");
10382         } else if (currentMove < forwardMostMove) {
10383             ForwardInner(forwardMostMove);
10384         }
10385         pauseExamInvalid = FALSE;
10386     } else {
10387         switch (gameMode) {
10388           default:
10389             return;
10390           case IcsExamining:
10391             pauseExamForwardMostMove = forwardMostMove;
10392             pauseExamInvalid = FALSE;
10393             /* fall through */
10394           case IcsObserving:
10395           case IcsPlayingWhite:
10396           case IcsPlayingBlack:
10397             pausing = TRUE;
10398             ModeHighlight();
10399             return;
10400           case PlayFromGameFile:
10401             (void) StopLoadGameTimer();
10402             pausing = TRUE;
10403             ModeHighlight();
10404             break;
10405           case BeginningOfGame:
10406             if (appData.icsActive) return;
10407             /* else fall through */
10408           case MachinePlaysWhite:
10409           case MachinePlaysBlack:
10410           case TwoMachinesPlay:
10411             if (forwardMostMove == 0)
10412               return;           /* don't pause if no one has moved */
10413             if ((gameMode == MachinePlaysWhite &&
10414                  !WhiteOnMove(forwardMostMove)) ||
10415                 (gameMode == MachinePlaysBlack &&
10416                  WhiteOnMove(forwardMostMove))) {
10417                 StopClocks();
10418             }
10419             pausing = TRUE;
10420             ModeHighlight();
10421             break;
10422         }
10423     }
10424 }
10425
10426 void
10427 EditCommentEvent()
10428 {
10429     char title[MSG_SIZ];
10430
10431     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10432         strcpy(title, _("Edit comment"));
10433     } else {
10434         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10435                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10436                 parseList[currentMove - 1]);
10437     }
10438
10439     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10440 }
10441
10442
10443 void
10444 EditTagsEvent()
10445 {
10446     char *tags = PGNTags(&gameInfo);
10447     EditTagsPopUp(tags);
10448     free(tags);
10449 }
10450
10451 void
10452 AnalyzeModeEvent()
10453 {
10454     if (appData.noChessProgram || gameMode == AnalyzeMode)
10455       return;
10456
10457     if (gameMode != AnalyzeFile) {
10458         if (!appData.icsEngineAnalyze) {
10459                EditGameEvent();
10460                if (gameMode != EditGame) return;
10461         }
10462         ResurrectChessProgram();
10463         SendToProgram("analyze\n", &first);
10464         first.analyzing = TRUE;
10465         /*first.maybeThinking = TRUE;*/
10466         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10467         AnalysisPopUp(_("Analysis"),
10468                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10469     }
10470     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10471     pausing = FALSE;
10472     ModeHighlight();
10473     SetGameInfo();
10474
10475     StartAnalysisClock();
10476     GetTimeMark(&lastNodeCountTime);
10477     lastNodeCount = 0;
10478 }
10479
10480 void
10481 AnalyzeFileEvent()
10482 {
10483     if (appData.noChessProgram || gameMode == AnalyzeFile)
10484       return;
10485
10486     if (gameMode != AnalyzeMode) {
10487         EditGameEvent();
10488         if (gameMode != EditGame) return;
10489         ResurrectChessProgram();
10490         SendToProgram("analyze\n", &first);
10491         first.analyzing = TRUE;
10492         /*first.maybeThinking = TRUE;*/
10493         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10494         AnalysisPopUp(_("Analysis"),
10495                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10496     }
10497     gameMode = AnalyzeFile;
10498     pausing = FALSE;
10499     ModeHighlight();
10500     SetGameInfo();
10501
10502     StartAnalysisClock();
10503     GetTimeMark(&lastNodeCountTime);
10504     lastNodeCount = 0;
10505 }
10506
10507 void
10508 MachineWhiteEvent()
10509 {
10510     char buf[MSG_SIZ];
10511     char *bookHit = NULL;
10512
10513     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10514       return;
10515
10516
10517     if (gameMode == PlayFromGameFile || 
10518         gameMode == TwoMachinesPlay  || 
10519         gameMode == Training         || 
10520         gameMode == AnalyzeMode      || 
10521         gameMode == EndOfGame)
10522         EditGameEvent();
10523
10524     if (gameMode == EditPosition) 
10525         EditPositionDone();
10526
10527     if (!WhiteOnMove(currentMove)) {
10528         DisplayError(_("It is not White's turn"), 0);
10529         return;
10530     }
10531   
10532     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10533       ExitAnalyzeMode();
10534
10535     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10536         gameMode == AnalyzeFile)
10537         TruncateGame();
10538
10539     ResurrectChessProgram();    /* in case it isn't running */
10540     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10541         gameMode = MachinePlaysWhite;
10542         ResetClocks();
10543     } else
10544     gameMode = MachinePlaysWhite;
10545     pausing = FALSE;
10546     ModeHighlight();
10547     SetGameInfo();
10548     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10549     DisplayTitle(buf);
10550     if (first.sendName) {
10551       sprintf(buf, "name %s\n", gameInfo.black);
10552       SendToProgram(buf, &first);
10553     }
10554     if (first.sendTime) {
10555       if (first.useColors) {
10556         SendToProgram("black\n", &first); /*gnu kludge*/
10557       }
10558       SendTimeRemaining(&first, TRUE);
10559     }
10560     if (first.useColors) {
10561       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10562     }
10563     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10564     SetMachineThinkingEnables();
10565     first.maybeThinking = TRUE;
10566     StartClocks();
10567     firstMove = FALSE;
10568
10569     if (appData.autoFlipView && !flipView) {
10570       flipView = !flipView;
10571       DrawPosition(FALSE, NULL);
10572       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10573     }
10574
10575     if(bookHit) { // [HGM] book: simulate book reply
10576         static char bookMove[MSG_SIZ]; // a bit generous?
10577
10578         programStats.nodes = programStats.depth = programStats.time = 
10579         programStats.score = programStats.got_only_move = 0;
10580         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10581
10582         strcpy(bookMove, "move ");
10583         strcat(bookMove, bookHit);
10584         HandleMachineMove(bookMove, &first);
10585     }
10586 }
10587
10588 void
10589 MachineBlackEvent()
10590 {
10591     char buf[MSG_SIZ];
10592    char *bookHit = NULL;
10593
10594     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10595         return;
10596
10597
10598     if (gameMode == PlayFromGameFile || 
10599         gameMode == TwoMachinesPlay  || 
10600         gameMode == Training         || 
10601         gameMode == AnalyzeMode      || 
10602         gameMode == EndOfGame)
10603         EditGameEvent();
10604
10605     if (gameMode == EditPosition) 
10606         EditPositionDone();
10607
10608     if (WhiteOnMove(currentMove)) {
10609         DisplayError(_("It is not Black's turn"), 0);
10610         return;
10611     }
10612     
10613     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10614       ExitAnalyzeMode();
10615
10616     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10617         gameMode == AnalyzeFile)
10618         TruncateGame();
10619
10620     ResurrectChessProgram();    /* in case it isn't running */
10621     gameMode = MachinePlaysBlack;
10622     pausing = FALSE;
10623     ModeHighlight();
10624     SetGameInfo();
10625     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10626     DisplayTitle(buf);
10627     if (first.sendName) {
10628       sprintf(buf, "name %s\n", gameInfo.white);
10629       SendToProgram(buf, &first);
10630     }
10631     if (first.sendTime) {
10632       if (first.useColors) {
10633         SendToProgram("white\n", &first); /*gnu kludge*/
10634       }
10635       SendTimeRemaining(&first, FALSE);
10636     }
10637     if (first.useColors) {
10638       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10639     }
10640     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10641     SetMachineThinkingEnables();
10642     first.maybeThinking = TRUE;
10643     StartClocks();
10644
10645     if (appData.autoFlipView && flipView) {
10646       flipView = !flipView;
10647       DrawPosition(FALSE, NULL);
10648       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10649     }
10650     if(bookHit) { // [HGM] book: simulate book reply
10651         static char bookMove[MSG_SIZ]; // a bit generous?
10652
10653         programStats.nodes = programStats.depth = programStats.time = 
10654         programStats.score = programStats.got_only_move = 0;
10655         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10656
10657         strcpy(bookMove, "move ");
10658         strcat(bookMove, bookHit);
10659         HandleMachineMove(bookMove, &first);
10660     }
10661 }
10662
10663
10664 void
10665 DisplayTwoMachinesTitle()
10666 {
10667     char buf[MSG_SIZ];
10668     if (appData.matchGames > 0) {
10669         if (first.twoMachinesColor[0] == 'w') {
10670             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10671                     gameInfo.white, gameInfo.black,
10672                     first.matchWins, second.matchWins,
10673                     matchGame - 1 - (first.matchWins + second.matchWins));
10674         } else {
10675             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10676                     gameInfo.white, gameInfo.black,
10677                     second.matchWins, first.matchWins,
10678                     matchGame - 1 - (first.matchWins + second.matchWins));
10679         }
10680     } else {
10681         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10682     }
10683     DisplayTitle(buf);
10684 }
10685
10686 void
10687 TwoMachinesEvent P((void))
10688 {
10689     int i;
10690     char buf[MSG_SIZ];
10691     ChessProgramState *onmove;
10692     char *bookHit = NULL;
10693     
10694     if (appData.noChessProgram) return;
10695
10696     switch (gameMode) {
10697       case TwoMachinesPlay:
10698         return;
10699       case MachinePlaysWhite:
10700       case MachinePlaysBlack:
10701         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10702             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10703             return;
10704         }
10705         /* fall through */
10706       case BeginningOfGame:
10707       case PlayFromGameFile:
10708       case EndOfGame:
10709         EditGameEvent();
10710         if (gameMode != EditGame) return;
10711         break;
10712       case EditPosition:
10713         EditPositionDone();
10714         break;
10715       case AnalyzeMode:
10716       case AnalyzeFile:
10717         ExitAnalyzeMode();
10718         break;
10719       case EditGame:
10720       default:
10721         break;
10722     }
10723
10724     forwardMostMove = currentMove;
10725     ResurrectChessProgram();    /* in case first program isn't running */
10726
10727     if (second.pr == NULL) {
10728         StartChessProgram(&second);
10729         if (second.protocolVersion == 1) {
10730           TwoMachinesEventIfReady();
10731         } else {
10732           /* kludge: allow timeout for initial "feature" command */
10733           FreezeUI();
10734           DisplayMessage("", _("Starting second chess program"));
10735           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10736         }
10737         return;
10738     }
10739     DisplayMessage("", "");
10740     InitChessProgram(&second, FALSE);
10741     SendToProgram("force\n", &second);
10742     if (startedFromSetupPosition) {
10743         SendBoard(&second, backwardMostMove);
10744     if (appData.debugMode) {
10745         fprintf(debugFP, "Two Machines\n");
10746     }
10747     }
10748     for (i = backwardMostMove; i < forwardMostMove; i++) {
10749         SendMoveToProgram(i, &second);
10750     }
10751
10752     gameMode = TwoMachinesPlay;
10753     pausing = FALSE;
10754     ModeHighlight();
10755     SetGameInfo();
10756     DisplayTwoMachinesTitle();
10757     firstMove = TRUE;
10758     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10759         onmove = &first;
10760     } else {
10761         onmove = &second;
10762     }
10763
10764     SendToProgram(first.computerString, &first);
10765     if (first.sendName) {
10766       sprintf(buf, "name %s\n", second.tidy);
10767       SendToProgram(buf, &first);
10768     }
10769     SendToProgram(second.computerString, &second);
10770     if (second.sendName) {
10771       sprintf(buf, "name %s\n", first.tidy);
10772       SendToProgram(buf, &second);
10773     }
10774
10775     ResetClocks();
10776     if (!first.sendTime || !second.sendTime) {
10777         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10778         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10779     }
10780     if (onmove->sendTime) {
10781       if (onmove->useColors) {
10782         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10783       }
10784       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10785     }
10786     if (onmove->useColors) {
10787       SendToProgram(onmove->twoMachinesColor, onmove);
10788     }
10789     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10790 //    SendToProgram("go\n", onmove);
10791     onmove->maybeThinking = TRUE;
10792     SetMachineThinkingEnables();
10793
10794     StartClocks();
10795
10796     if(bookHit) { // [HGM] book: simulate book reply
10797         static char bookMove[MSG_SIZ]; // a bit generous?
10798
10799         programStats.nodes = programStats.depth = programStats.time = 
10800         programStats.score = programStats.got_only_move = 0;
10801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10802
10803         strcpy(bookMove, "move ");
10804         strcat(bookMove, bookHit);
10805         HandleMachineMove(bookMove, &first);
10806     }
10807 }
10808
10809 void
10810 TrainingEvent()
10811 {
10812     if (gameMode == Training) {
10813       SetTrainingModeOff();
10814       gameMode = PlayFromGameFile;
10815       DisplayMessage("", _("Training mode off"));
10816     } else {
10817       gameMode = Training;
10818       animateTraining = appData.animate;
10819
10820       /* make sure we are not already at the end of the game */
10821       if (currentMove < forwardMostMove) {
10822         SetTrainingModeOn();
10823         DisplayMessage("", _("Training mode on"));
10824       } else {
10825         gameMode = PlayFromGameFile;
10826         DisplayError(_("Already at end of game"), 0);
10827       }
10828     }
10829     ModeHighlight();
10830 }
10831
10832 void
10833 IcsClientEvent()
10834 {
10835     if (!appData.icsActive) return;
10836     switch (gameMode) {
10837       case IcsPlayingWhite:
10838       case IcsPlayingBlack:
10839       case IcsObserving:
10840       case IcsIdle:
10841       case BeginningOfGame:
10842       case IcsExamining:
10843         return;
10844
10845       case EditGame:
10846         break;
10847
10848       case EditPosition:
10849         EditPositionDone();
10850         break;
10851
10852       case AnalyzeMode:
10853       case AnalyzeFile:
10854         ExitAnalyzeMode();
10855         break;
10856         
10857       default:
10858         EditGameEvent();
10859         break;
10860     }
10861
10862     gameMode = IcsIdle;
10863     ModeHighlight();
10864     return;
10865 }
10866
10867
10868 void
10869 EditGameEvent()
10870 {
10871     int i;
10872
10873     switch (gameMode) {
10874       case Training:
10875         SetTrainingModeOff();
10876         break;
10877       case MachinePlaysWhite:
10878       case MachinePlaysBlack:
10879       case BeginningOfGame:
10880         SendToProgram("force\n", &first);
10881         SetUserThinkingEnables();
10882         break;
10883       case PlayFromGameFile:
10884         (void) StopLoadGameTimer();
10885         if (gameFileFP != NULL) {
10886             gameFileFP = NULL;
10887         }
10888         break;
10889       case EditPosition:
10890         EditPositionDone();
10891         break;
10892       case AnalyzeMode:
10893       case AnalyzeFile:
10894         ExitAnalyzeMode();
10895         SendToProgram("force\n", &first);
10896         break;
10897       case TwoMachinesPlay:
10898         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10899         ResurrectChessProgram();
10900         SetUserThinkingEnables();
10901         break;
10902       case EndOfGame:
10903         ResurrectChessProgram();
10904         break;
10905       case IcsPlayingBlack:
10906       case IcsPlayingWhite:
10907         DisplayError(_("Warning: You are still playing a game"), 0);
10908         break;
10909       case IcsObserving:
10910         DisplayError(_("Warning: You are still observing a game"), 0);
10911         break;
10912       case IcsExamining:
10913         DisplayError(_("Warning: You are still examining a game"), 0);
10914         break;
10915       case IcsIdle:
10916         break;
10917       case EditGame:
10918       default:
10919         return;
10920     }
10921     
10922     pausing = FALSE;
10923     StopClocks();
10924     first.offeredDraw = second.offeredDraw = 0;
10925
10926     if (gameMode == PlayFromGameFile) {
10927         whiteTimeRemaining = timeRemaining[0][currentMove];
10928         blackTimeRemaining = timeRemaining[1][currentMove];
10929         DisplayTitle("");
10930     }
10931
10932     if (gameMode == MachinePlaysWhite ||
10933         gameMode == MachinePlaysBlack ||
10934         gameMode == TwoMachinesPlay ||
10935         gameMode == EndOfGame) {
10936         i = forwardMostMove;
10937         while (i > currentMove) {
10938             SendToProgram("undo\n", &first);
10939             i--;
10940         }
10941         whiteTimeRemaining = timeRemaining[0][currentMove];
10942         blackTimeRemaining = timeRemaining[1][currentMove];
10943         DisplayBothClocks();
10944         if (whiteFlag || blackFlag) {
10945             whiteFlag = blackFlag = 0;
10946         }
10947         DisplayTitle("");
10948     }           
10949     
10950     gameMode = EditGame;
10951     ModeHighlight();
10952     SetGameInfo();
10953 }
10954
10955
10956 void
10957 EditPositionEvent()
10958 {
10959     if (gameMode == EditPosition) {
10960         EditGameEvent();
10961         return;
10962     }
10963     
10964     EditGameEvent();
10965     if (gameMode != EditGame) return;
10966     
10967     gameMode = EditPosition;
10968     ModeHighlight();
10969     SetGameInfo();
10970     if (currentMove > 0)
10971       CopyBoard(boards[0], boards[currentMove]);
10972     
10973     blackPlaysFirst = !WhiteOnMove(currentMove);
10974     ResetClocks();
10975     currentMove = forwardMostMove = backwardMostMove = 0;
10976     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10977     DisplayMove(-1);
10978 }
10979
10980 void
10981 ExitAnalyzeMode()
10982 {
10983     /* [DM] icsEngineAnalyze - possible call from other functions */
10984     if (appData.icsEngineAnalyze) {
10985         appData.icsEngineAnalyze = FALSE;
10986
10987         DisplayMessage("",_("Close ICS engine analyze..."));
10988     }
10989     if (first.analysisSupport && first.analyzing) {
10990       SendToProgram("exit\n", &first);
10991       first.analyzing = FALSE;
10992     }
10993     AnalysisPopDown();
10994     thinkOutput[0] = NULLCHAR;
10995 }
10996
10997 void
10998 EditPositionDone()
10999 {
11000     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11001
11002     startedFromSetupPosition = TRUE;
11003     InitChessProgram(&first, FALSE);
11004     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11005     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11006         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11007         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11008     } else castlingRights[0][2] = -1;
11009     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11010         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11011         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11012     } else castlingRights[0][5] = -1;
11013     SendToProgram("force\n", &first);
11014     if (blackPlaysFirst) {
11015         strcpy(moveList[0], "");
11016         strcpy(parseList[0], "");
11017         currentMove = forwardMostMove = backwardMostMove = 1;
11018         CopyBoard(boards[1], boards[0]);
11019         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11020         { int i;
11021           epStatus[1] = epStatus[0];
11022           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11023         }
11024     } else {
11025         currentMove = forwardMostMove = backwardMostMove = 0;
11026     }
11027     SendBoard(&first, forwardMostMove);
11028     if (appData.debugMode) {
11029         fprintf(debugFP, "EditPosDone\n");
11030     }
11031     DisplayTitle("");
11032     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11033     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11034     gameMode = EditGame;
11035     ModeHighlight();
11036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11037     ClearHighlights(); /* [AS] */
11038 }
11039
11040 /* Pause for `ms' milliseconds */
11041 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11042 void
11043 TimeDelay(ms)
11044      long ms;
11045 {
11046     TimeMark m1, m2;
11047
11048     GetTimeMark(&m1);
11049     do {
11050         GetTimeMark(&m2);
11051     } while (SubtractTimeMarks(&m2, &m1) < ms);
11052 }
11053
11054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11055 void
11056 SendMultiLineToICS(buf)
11057      char *buf;
11058 {
11059     char temp[MSG_SIZ+1], *p;
11060     int len;
11061
11062     len = strlen(buf);
11063     if (len > MSG_SIZ)
11064       len = MSG_SIZ;
11065   
11066     strncpy(temp, buf, len);
11067     temp[len] = 0;
11068
11069     p = temp;
11070     while (*p) {
11071         if (*p == '\n' || *p == '\r')
11072           *p = ' ';
11073         ++p;
11074     }
11075
11076     strcat(temp, "\n");
11077     SendToICS(temp);
11078     SendToPlayer(temp, strlen(temp));
11079 }
11080
11081 void
11082 SetWhiteToPlayEvent()
11083 {
11084     if (gameMode == EditPosition) {
11085         blackPlaysFirst = FALSE;
11086         DisplayBothClocks();    /* works because currentMove is 0 */
11087     } else if (gameMode == IcsExamining) {
11088         SendToICS(ics_prefix);
11089         SendToICS("tomove white\n");
11090     }
11091 }
11092
11093 void
11094 SetBlackToPlayEvent()
11095 {
11096     if (gameMode == EditPosition) {
11097         blackPlaysFirst = TRUE;
11098         currentMove = 1;        /* kludge */
11099         DisplayBothClocks();
11100         currentMove = 0;
11101     } else if (gameMode == IcsExamining) {
11102         SendToICS(ics_prefix);
11103         SendToICS("tomove black\n");
11104     }
11105 }
11106
11107 void
11108 EditPositionMenuEvent(selection, x, y)
11109      ChessSquare selection;
11110      int x, y;
11111 {
11112     char buf[MSG_SIZ];
11113     ChessSquare piece = boards[0][y][x];
11114
11115     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11116
11117     switch (selection) {
11118       case ClearBoard:
11119         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11120             SendToICS(ics_prefix);
11121             SendToICS("bsetup clear\n");
11122         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11123             SendToICS(ics_prefix);
11124             SendToICS("clearboard\n");
11125         } else {
11126             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11127                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11128                 for (y = 0; y < BOARD_HEIGHT; y++) {
11129                     if (gameMode == IcsExamining) {
11130                         if (boards[currentMove][y][x] != EmptySquare) {
11131                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11132                                     AAA + x, ONE + y);
11133                             SendToICS(buf);
11134                         }
11135                     } else {
11136                         boards[0][y][x] = p;
11137                     }
11138                 }
11139             }
11140         }
11141         if (gameMode == EditPosition) {
11142             DrawPosition(FALSE, boards[0]);
11143         }
11144         break;
11145
11146       case WhitePlay:
11147         SetWhiteToPlayEvent();
11148         break;
11149
11150       case BlackPlay:
11151         SetBlackToPlayEvent();
11152         break;
11153
11154       case EmptySquare:
11155         if (gameMode == IcsExamining) {
11156             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11157             SendToICS(buf);
11158         } else {
11159             boards[0][y][x] = EmptySquare;
11160             DrawPosition(FALSE, boards[0]);
11161         }
11162         break;
11163
11164       case PromotePiece:
11165         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11166            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11167             selection = (ChessSquare) (PROMOTED piece);
11168         } else if(piece == EmptySquare) selection = WhiteSilver;
11169         else selection = (ChessSquare)((int)piece - 1);
11170         goto defaultlabel;
11171
11172       case DemotePiece:
11173         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11174            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11175             selection = (ChessSquare) (DEMOTED piece);
11176         } else if(piece == EmptySquare) selection = BlackSilver;
11177         else selection = (ChessSquare)((int)piece + 1);       
11178         goto defaultlabel;
11179
11180       case WhiteQueen:
11181       case BlackQueen:
11182         if(gameInfo.variant == VariantShatranj ||
11183            gameInfo.variant == VariantXiangqi  ||
11184            gameInfo.variant == VariantCourier    )
11185             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11186         goto defaultlabel;
11187
11188       case WhiteKing:
11189       case BlackKing:
11190         if(gameInfo.variant == VariantXiangqi)
11191             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11192         if(gameInfo.variant == VariantKnightmate)
11193             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11194       default:
11195         defaultlabel:
11196         if (gameMode == IcsExamining) {
11197             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11198                     PieceToChar(selection), AAA + x, ONE + y);
11199             SendToICS(buf);
11200         } else {
11201             boards[0][y][x] = selection;
11202             DrawPosition(FALSE, boards[0]);
11203         }
11204         break;
11205     }
11206 }
11207
11208
11209 void
11210 DropMenuEvent(selection, x, y)
11211      ChessSquare selection;
11212      int x, y;
11213 {
11214     ChessMove moveType;
11215
11216     switch (gameMode) {
11217       case IcsPlayingWhite:
11218       case MachinePlaysBlack:
11219         if (!WhiteOnMove(currentMove)) {
11220             DisplayMoveError(_("It is Black's turn"));
11221             return;
11222         }
11223         moveType = WhiteDrop;
11224         break;
11225       case IcsPlayingBlack:
11226       case MachinePlaysWhite:
11227         if (WhiteOnMove(currentMove)) {
11228             DisplayMoveError(_("It is White's turn"));
11229             return;
11230         }
11231         moveType = BlackDrop;
11232         break;
11233       case EditGame:
11234         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11235         break;
11236       default:
11237         return;
11238     }
11239
11240     if (moveType == BlackDrop && selection < BlackPawn) {
11241       selection = (ChessSquare) ((int) selection
11242                                  + (int) BlackPawn - (int) WhitePawn);
11243     }
11244     if (boards[currentMove][y][x] != EmptySquare) {
11245         DisplayMoveError(_("That square is occupied"));
11246         return;
11247     }
11248
11249     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11250 }
11251
11252 void
11253 AcceptEvent()
11254 {
11255     /* Accept a pending offer of any kind from opponent */
11256     
11257     if (appData.icsActive) {
11258         SendToICS(ics_prefix);
11259         SendToICS("accept\n");
11260     } else if (cmailMsgLoaded) {
11261         if (currentMove == cmailOldMove &&
11262             commentList[cmailOldMove] != NULL &&
11263             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11264                    "Black offers a draw" : "White offers a draw")) {
11265             TruncateGame();
11266             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11267             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11268         } else {
11269             DisplayError(_("There is no pending offer on this move"), 0);
11270             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11271         }
11272     } else {
11273         /* Not used for offers from chess program */
11274     }
11275 }
11276
11277 void
11278 DeclineEvent()
11279 {
11280     /* Decline a pending offer of any kind from opponent */
11281     
11282     if (appData.icsActive) {
11283         SendToICS(ics_prefix);
11284         SendToICS("decline\n");
11285     } else if (cmailMsgLoaded) {
11286         if (currentMove == cmailOldMove &&
11287             commentList[cmailOldMove] != NULL &&
11288             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11289                    "Black offers a draw" : "White offers a draw")) {
11290 #ifdef NOTDEF
11291             AppendComment(cmailOldMove, "Draw declined");
11292             DisplayComment(cmailOldMove - 1, "Draw declined");
11293 #endif /*NOTDEF*/
11294         } else {
11295             DisplayError(_("There is no pending offer on this move"), 0);
11296         }
11297     } else {
11298         /* Not used for offers from chess program */
11299     }
11300 }
11301
11302 void
11303 RematchEvent()
11304 {
11305     /* Issue ICS rematch command */
11306     if (appData.icsActive) {
11307         SendToICS(ics_prefix);
11308         SendToICS("rematch\n");
11309     }
11310 }
11311
11312 void
11313 CallFlagEvent()
11314 {
11315     /* Call your opponent's flag (claim a win on time) */
11316     if (appData.icsActive) {
11317         SendToICS(ics_prefix);
11318         SendToICS("flag\n");
11319     } else {
11320         switch (gameMode) {
11321           default:
11322             return;
11323           case MachinePlaysWhite:
11324             if (whiteFlag) {
11325                 if (blackFlag)
11326                   GameEnds(GameIsDrawn, "Both players ran out of time",
11327                            GE_PLAYER);
11328                 else
11329                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11330             } else {
11331                 DisplayError(_("Your opponent is not out of time"), 0);
11332             }
11333             break;
11334           case MachinePlaysBlack:
11335             if (blackFlag) {
11336                 if (whiteFlag)
11337                   GameEnds(GameIsDrawn, "Both players ran out of time",
11338                            GE_PLAYER);
11339                 else
11340                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11341             } else {
11342                 DisplayError(_("Your opponent is not out of time"), 0);
11343             }
11344             break;
11345         }
11346     }
11347 }
11348
11349 void
11350 DrawEvent()
11351 {
11352     /* Offer draw or accept pending draw offer from opponent */
11353     
11354     if (appData.icsActive) {
11355         /* Note: tournament rules require draw offers to be
11356            made after you make your move but before you punch
11357            your clock.  Currently ICS doesn't let you do that;
11358            instead, you immediately punch your clock after making
11359            a move, but you can offer a draw at any time. */
11360         
11361         SendToICS(ics_prefix);
11362         SendToICS("draw\n");
11363     } else if (cmailMsgLoaded) {
11364         if (currentMove == cmailOldMove &&
11365             commentList[cmailOldMove] != NULL &&
11366             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11367                    "Black offers a draw" : "White offers a draw")) {
11368             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11369             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11370         } else if (currentMove == cmailOldMove + 1) {
11371             char *offer = WhiteOnMove(cmailOldMove) ?
11372               "White offers a draw" : "Black offers a draw";
11373             AppendComment(currentMove, offer);
11374             DisplayComment(currentMove - 1, offer);
11375             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11376         } else {
11377             DisplayError(_("You must make your move before offering a draw"), 0);
11378             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11379         }
11380     } else if (first.offeredDraw) {
11381         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11382     } else {
11383         if (first.sendDrawOffers) {
11384             SendToProgram("draw\n", &first);
11385             userOfferedDraw = TRUE;
11386         }
11387     }
11388 }
11389
11390 void
11391 AdjournEvent()
11392 {
11393     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11394     
11395     if (appData.icsActive) {
11396         SendToICS(ics_prefix);
11397         SendToICS("adjourn\n");
11398     } else {
11399         /* Currently GNU Chess doesn't offer or accept Adjourns */
11400     }
11401 }
11402
11403
11404 void
11405 AbortEvent()
11406 {
11407     /* Offer Abort or accept pending Abort offer from opponent */
11408     
11409     if (appData.icsActive) {
11410         SendToICS(ics_prefix);
11411         SendToICS("abort\n");
11412     } else {
11413         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11414     }
11415 }
11416
11417 void
11418 ResignEvent()
11419 {
11420     /* Resign.  You can do this even if it's not your turn. */
11421     
11422     if (appData.icsActive) {
11423         SendToICS(ics_prefix);
11424         SendToICS("resign\n");
11425     } else {
11426         switch (gameMode) {
11427           case MachinePlaysWhite:
11428             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11429             break;
11430           case MachinePlaysBlack:
11431             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11432             break;
11433           case EditGame:
11434             if (cmailMsgLoaded) {
11435                 TruncateGame();
11436                 if (WhiteOnMove(cmailOldMove)) {
11437                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11438                 } else {
11439                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11440                 }
11441                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11442             }
11443             break;
11444           default:
11445             break;
11446         }
11447     }
11448 }
11449
11450
11451 void
11452 StopObservingEvent()
11453 {
11454     /* Stop observing current games */
11455     SendToICS(ics_prefix);
11456     SendToICS("unobserve\n");
11457 }
11458
11459 void
11460 StopExaminingEvent()
11461 {
11462     /* Stop observing current game */
11463     SendToICS(ics_prefix);
11464     SendToICS("unexamine\n");
11465 }
11466
11467 void
11468 ForwardInner(target)
11469      int target;
11470 {
11471     int limit;
11472
11473     if (appData.debugMode)
11474         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11475                 target, currentMove, forwardMostMove);
11476
11477     if (gameMode == EditPosition)
11478       return;
11479
11480     if (gameMode == PlayFromGameFile && !pausing)
11481       PauseEvent();
11482     
11483     if (gameMode == IcsExamining && pausing)
11484       limit = pauseExamForwardMostMove;
11485     else
11486       limit = forwardMostMove;
11487     
11488     if (target > limit) target = limit;
11489
11490     if (target > 0 && moveList[target - 1][0]) {
11491         int fromX, fromY, toX, toY;
11492         toX = moveList[target - 1][2] - AAA;
11493         toY = moveList[target - 1][3] - ONE;
11494         if (moveList[target - 1][1] == '@') {
11495             if (appData.highlightLastMove) {
11496                 SetHighlights(-1, -1, toX, toY);
11497             }
11498         } else {
11499             fromX = moveList[target - 1][0] - AAA;
11500             fromY = moveList[target - 1][1] - ONE;
11501             if (target == currentMove + 1) {
11502                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11503             }
11504             if (appData.highlightLastMove) {
11505                 SetHighlights(fromX, fromY, toX, toY);
11506             }
11507         }
11508     }
11509     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11510         gameMode == Training || gameMode == PlayFromGameFile || 
11511         gameMode == AnalyzeFile) {
11512         while (currentMove < target) {
11513             SendMoveToProgram(currentMove++, &first);
11514         }
11515     } else {
11516         currentMove = target;
11517     }
11518     
11519     if (gameMode == EditGame || gameMode == EndOfGame) {
11520         whiteTimeRemaining = timeRemaining[0][currentMove];
11521         blackTimeRemaining = timeRemaining[1][currentMove];
11522     }
11523     DisplayBothClocks();
11524     DisplayMove(currentMove - 1);
11525     DrawPosition(FALSE, boards[currentMove]);
11526     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11527     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11528         DisplayComment(currentMove - 1, commentList[currentMove]);
11529     }
11530 }
11531
11532
11533 void
11534 ForwardEvent()
11535 {
11536     if (gameMode == IcsExamining && !pausing) {
11537         SendToICS(ics_prefix);
11538         SendToICS("forward\n");
11539     } else {
11540         ForwardInner(currentMove + 1);
11541     }
11542 }
11543
11544 void
11545 ToEndEvent()
11546 {
11547     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11548         /* to optimze, we temporarily turn off analysis mode while we feed
11549          * the remaining moves to the engine. Otherwise we get analysis output
11550          * after each move.
11551          */ 
11552         if (first.analysisSupport) {
11553           SendToProgram("exit\nforce\n", &first);
11554           first.analyzing = FALSE;
11555         }
11556     }
11557         
11558     if (gameMode == IcsExamining && !pausing) {
11559         SendToICS(ics_prefix);
11560         SendToICS("forward 999999\n");
11561     } else {
11562         ForwardInner(forwardMostMove);
11563     }
11564
11565     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11566         /* we have fed all the moves, so reactivate analysis mode */
11567         SendToProgram("analyze\n", &first);
11568         first.analyzing = TRUE;
11569         /*first.maybeThinking = TRUE;*/
11570         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11571     }
11572 }
11573
11574 void
11575 BackwardInner(target)
11576      int target;
11577 {
11578     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11579
11580     if (appData.debugMode)
11581         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11582                 target, currentMove, forwardMostMove);
11583
11584     if (gameMode == EditPosition) return;
11585     if (currentMove <= backwardMostMove) {
11586         ClearHighlights();
11587         DrawPosition(full_redraw, boards[currentMove]);
11588         return;
11589     }
11590     if (gameMode == PlayFromGameFile && !pausing)
11591       PauseEvent();
11592     
11593     if (moveList[target][0]) {
11594         int fromX, fromY, toX, toY;
11595         toX = moveList[target][2] - AAA;
11596         toY = moveList[target][3] - ONE;
11597         if (moveList[target][1] == '@') {
11598             if (appData.highlightLastMove) {
11599                 SetHighlights(-1, -1, toX, toY);
11600             }
11601         } else {
11602             fromX = moveList[target][0] - AAA;
11603             fromY = moveList[target][1] - ONE;
11604             if (target == currentMove - 1) {
11605                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11606             }
11607             if (appData.highlightLastMove) {
11608                 SetHighlights(fromX, fromY, toX, toY);
11609             }
11610         }
11611     }
11612     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11613         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11614         while (currentMove > target) {
11615             SendToProgram("undo\n", &first);
11616             currentMove--;
11617         }
11618     } else {
11619         currentMove = target;
11620     }
11621     
11622     if (gameMode == EditGame || gameMode == EndOfGame) {
11623         whiteTimeRemaining = timeRemaining[0][currentMove];
11624         blackTimeRemaining = timeRemaining[1][currentMove];
11625     }
11626     DisplayBothClocks();
11627     DisplayMove(currentMove - 1);
11628     DrawPosition(full_redraw, boards[currentMove]);
11629     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11630     // [HGM] PV info: routine tests if comment empty
11631     DisplayComment(currentMove - 1, commentList[currentMove]);
11632 }
11633
11634 void
11635 BackwardEvent()
11636 {
11637     if (gameMode == IcsExamining && !pausing) {
11638         SendToICS(ics_prefix);
11639         SendToICS("backward\n");
11640     } else {
11641         BackwardInner(currentMove - 1);
11642     }
11643 }
11644
11645 void
11646 ToStartEvent()
11647 {
11648     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11649         /* to optimze, we temporarily turn off analysis mode while we undo
11650          * all the moves. Otherwise we get analysis output after each undo.
11651          */ 
11652         if (first.analysisSupport) {
11653           SendToProgram("exit\nforce\n", &first);
11654           first.analyzing = FALSE;
11655         }
11656     }
11657
11658     if (gameMode == IcsExamining && !pausing) {
11659         SendToICS(ics_prefix);
11660         SendToICS("backward 999999\n");
11661     } else {
11662         BackwardInner(backwardMostMove);
11663     }
11664
11665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11666         /* we have fed all the moves, so reactivate analysis mode */
11667         SendToProgram("analyze\n", &first);
11668         first.analyzing = TRUE;
11669         /*first.maybeThinking = TRUE;*/
11670         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11671     }
11672 }
11673
11674 void
11675 ToNrEvent(int to)
11676 {
11677   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11678   if (to >= forwardMostMove) to = forwardMostMove;
11679   if (to <= backwardMostMove) to = backwardMostMove;
11680   if (to < currentMove) {
11681     BackwardInner(to);
11682   } else {
11683     ForwardInner(to);
11684   }
11685 }
11686
11687 void
11688 RevertEvent()
11689 {
11690     if (gameMode != IcsExamining) {
11691         DisplayError(_("You are not examining a game"), 0);
11692         return;
11693     }
11694     if (pausing) {
11695         DisplayError(_("You can't revert while pausing"), 0);
11696         return;
11697     }
11698     SendToICS(ics_prefix);
11699     SendToICS("revert\n");
11700 }
11701
11702 void
11703 RetractMoveEvent()
11704 {
11705     switch (gameMode) {
11706       case MachinePlaysWhite:
11707       case MachinePlaysBlack:
11708         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11709             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11710             return;
11711         }
11712         if (forwardMostMove < 2) return;
11713         currentMove = forwardMostMove = forwardMostMove - 2;
11714         whiteTimeRemaining = timeRemaining[0][currentMove];
11715         blackTimeRemaining = timeRemaining[1][currentMove];
11716         DisplayBothClocks();
11717         DisplayMove(currentMove - 1);
11718         ClearHighlights();/*!! could figure this out*/
11719         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11720         SendToProgram("remove\n", &first);
11721         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11722         break;
11723
11724       case BeginningOfGame:
11725       default:
11726         break;
11727
11728       case IcsPlayingWhite:
11729       case IcsPlayingBlack:
11730         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11731             SendToICS(ics_prefix);
11732             SendToICS("takeback 2\n");
11733         } else {
11734             SendToICS(ics_prefix);
11735             SendToICS("takeback 1\n");
11736         }
11737         break;
11738     }
11739 }
11740
11741 void
11742 MoveNowEvent()
11743 {
11744     ChessProgramState *cps;
11745
11746     switch (gameMode) {
11747       case MachinePlaysWhite:
11748         if (!WhiteOnMove(forwardMostMove)) {
11749             DisplayError(_("It is your turn"), 0);
11750             return;
11751         }
11752         cps = &first;
11753         break;
11754       case MachinePlaysBlack:
11755         if (WhiteOnMove(forwardMostMove)) {
11756             DisplayError(_("It is your turn"), 0);
11757             return;
11758         }
11759         cps = &first;
11760         break;
11761       case TwoMachinesPlay:
11762         if (WhiteOnMove(forwardMostMove) ==
11763             (first.twoMachinesColor[0] == 'w')) {
11764             cps = &first;
11765         } else {
11766             cps = &second;
11767         }
11768         break;
11769       case BeginningOfGame:
11770       default:
11771         return;
11772     }
11773     SendToProgram("?\n", cps);
11774 }
11775
11776 void
11777 TruncateGameEvent()
11778 {
11779     EditGameEvent();
11780     if (gameMode != EditGame) return;
11781     TruncateGame();
11782 }
11783
11784 void
11785 TruncateGame()
11786 {
11787     if (forwardMostMove > currentMove) {
11788         if (gameInfo.resultDetails != NULL) {
11789             free(gameInfo.resultDetails);
11790             gameInfo.resultDetails = NULL;
11791             gameInfo.result = GameUnfinished;
11792         }
11793         forwardMostMove = currentMove;
11794         HistorySet(parseList, backwardMostMove, forwardMostMove,
11795                    currentMove-1);
11796     }
11797 }
11798
11799 void
11800 HintEvent()
11801 {
11802     if (appData.noChessProgram) return;
11803     switch (gameMode) {
11804       case MachinePlaysWhite:
11805         if (WhiteOnMove(forwardMostMove)) {
11806             DisplayError(_("Wait until your turn"), 0);
11807             return;
11808         }
11809         break;
11810       case BeginningOfGame:
11811       case MachinePlaysBlack:
11812         if (!WhiteOnMove(forwardMostMove)) {
11813             DisplayError(_("Wait until your turn"), 0);
11814             return;
11815         }
11816         break;
11817       default:
11818         DisplayError(_("No hint available"), 0);
11819         return;
11820     }
11821     SendToProgram("hint\n", &first);
11822     hintRequested = TRUE;
11823 }
11824
11825 void
11826 BookEvent()
11827 {
11828     if (appData.noChessProgram) return;
11829     switch (gameMode) {
11830       case MachinePlaysWhite:
11831         if (WhiteOnMove(forwardMostMove)) {
11832             DisplayError(_("Wait until your turn"), 0);
11833             return;
11834         }
11835         break;
11836       case BeginningOfGame:
11837       case MachinePlaysBlack:
11838         if (!WhiteOnMove(forwardMostMove)) {
11839             DisplayError(_("Wait until your turn"), 0);
11840             return;
11841         }
11842         break;
11843       case EditPosition:
11844         EditPositionDone();
11845         break;
11846       case TwoMachinesPlay:
11847         return;
11848       default:
11849         break;
11850     }
11851     SendToProgram("bk\n", &first);
11852     bookOutput[0] = NULLCHAR;
11853     bookRequested = TRUE;
11854 }
11855
11856 void
11857 AboutGameEvent()
11858 {
11859     char *tags = PGNTags(&gameInfo);
11860     TagsPopUp(tags, CmailMsg());
11861     free(tags);
11862 }
11863
11864 /* end button procedures */
11865
11866 void
11867 PrintPosition(fp, move)
11868      FILE *fp;
11869      int move;
11870 {
11871     int i, j;
11872     
11873     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11874         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11875             char c = PieceToChar(boards[move][i][j]);
11876             fputc(c == 'x' ? '.' : c, fp);
11877             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11878         }
11879     }
11880     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11881       fprintf(fp, "white to play\n");
11882     else
11883       fprintf(fp, "black to play\n");
11884 }
11885
11886 void
11887 PrintOpponents(fp)
11888      FILE *fp;
11889 {
11890     if (gameInfo.white != NULL) {
11891         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11892     } else {
11893         fprintf(fp, "\n");
11894     }
11895 }
11896
11897 /* Find last component of program's own name, using some heuristics */
11898 void
11899 TidyProgramName(prog, host, buf)
11900      char *prog, *host, buf[MSG_SIZ];
11901 {
11902     char *p, *q;
11903     int local = (strcmp(host, "localhost") == 0);
11904     while (!local && (p = strchr(prog, ';')) != NULL) {
11905         p++;
11906         while (*p == ' ') p++;
11907         prog = p;
11908     }
11909     if (*prog == '"' || *prog == '\'') {
11910         q = strchr(prog + 1, *prog);
11911     } else {
11912         q = strchr(prog, ' ');
11913     }
11914     if (q == NULL) q = prog + strlen(prog);
11915     p = q;
11916     while (p >= prog && *p != '/' && *p != '\\') p--;
11917     p++;
11918     if(p == prog && *p == '"') p++;
11919     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11920     memcpy(buf, p, q - p);
11921     buf[q - p] = NULLCHAR;
11922     if (!local) {
11923         strcat(buf, "@");
11924         strcat(buf, host);
11925     }
11926 }
11927
11928 char *
11929 TimeControlTagValue()
11930 {
11931     char buf[MSG_SIZ];
11932     if (!appData.clockMode) {
11933         strcpy(buf, "-");
11934     } else if (movesPerSession > 0) {
11935         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11936     } else if (timeIncrement == 0) {
11937         sprintf(buf, "%ld", timeControl/1000);
11938     } else {
11939         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11940     }
11941     return StrSave(buf);
11942 }
11943
11944 void
11945 SetGameInfo()
11946 {
11947     /* This routine is used only for certain modes */
11948     VariantClass v = gameInfo.variant;
11949     ClearGameInfo(&gameInfo);
11950     gameInfo.variant = v;
11951
11952     switch (gameMode) {
11953       case MachinePlaysWhite:
11954         gameInfo.event = StrSave( appData.pgnEventHeader );
11955         gameInfo.site = StrSave(HostName());
11956         gameInfo.date = PGNDate();
11957         gameInfo.round = StrSave("-");
11958         gameInfo.white = StrSave(first.tidy);
11959         gameInfo.black = StrSave(UserName());
11960         gameInfo.timeControl = TimeControlTagValue();
11961         break;
11962
11963       case MachinePlaysBlack:
11964         gameInfo.event = StrSave( appData.pgnEventHeader );
11965         gameInfo.site = StrSave(HostName());
11966         gameInfo.date = PGNDate();
11967         gameInfo.round = StrSave("-");
11968         gameInfo.white = StrSave(UserName());
11969         gameInfo.black = StrSave(first.tidy);
11970         gameInfo.timeControl = TimeControlTagValue();
11971         break;
11972
11973       case TwoMachinesPlay:
11974         gameInfo.event = StrSave( appData.pgnEventHeader );
11975         gameInfo.site = StrSave(HostName());
11976         gameInfo.date = PGNDate();
11977         if (matchGame > 0) {
11978             char buf[MSG_SIZ];
11979             sprintf(buf, "%d", matchGame);
11980             gameInfo.round = StrSave(buf);
11981         } else {
11982             gameInfo.round = StrSave("-");
11983         }
11984         if (first.twoMachinesColor[0] == 'w') {
11985             gameInfo.white = StrSave(first.tidy);
11986             gameInfo.black = StrSave(second.tidy);
11987         } else {
11988             gameInfo.white = StrSave(second.tidy);
11989             gameInfo.black = StrSave(first.tidy);
11990         }
11991         gameInfo.timeControl = TimeControlTagValue();
11992         break;
11993
11994       case EditGame:
11995         gameInfo.event = StrSave("Edited game");
11996         gameInfo.site = StrSave(HostName());
11997         gameInfo.date = PGNDate();
11998         gameInfo.round = StrSave("-");
11999         gameInfo.white = StrSave("-");
12000         gameInfo.black = StrSave("-");
12001         break;
12002
12003       case EditPosition:
12004         gameInfo.event = StrSave("Edited position");
12005         gameInfo.site = StrSave(HostName());
12006         gameInfo.date = PGNDate();
12007         gameInfo.round = StrSave("-");
12008         gameInfo.white = StrSave("-");
12009         gameInfo.black = StrSave("-");
12010         break;
12011
12012       case IcsPlayingWhite:
12013       case IcsPlayingBlack:
12014       case IcsObserving:
12015       case IcsExamining:
12016         break;
12017
12018       case PlayFromGameFile:
12019         gameInfo.event = StrSave("Game from non-PGN file");
12020         gameInfo.site = StrSave(HostName());
12021         gameInfo.date = PGNDate();
12022         gameInfo.round = StrSave("-");
12023         gameInfo.white = StrSave("?");
12024         gameInfo.black = StrSave("?");
12025         break;
12026
12027       default:
12028         break;
12029     }
12030 }
12031
12032 void
12033 ReplaceComment(index, text)
12034      int index;
12035      char *text;
12036 {
12037     int len;
12038
12039     while (*text == '\n') text++;
12040     len = strlen(text);
12041     while (len > 0 && text[len - 1] == '\n') len--;
12042
12043     if (commentList[index] != NULL)
12044       free(commentList[index]);
12045
12046     if (len == 0) {
12047         commentList[index] = NULL;
12048         return;
12049     }
12050     commentList[index] = (char *) malloc(len + 2);
12051     strncpy(commentList[index], text, len);
12052     commentList[index][len] = '\n';
12053     commentList[index][len + 1] = NULLCHAR;
12054 }
12055
12056 void
12057 CrushCRs(text)
12058      char *text;
12059 {
12060   char *p = text;
12061   char *q = text;
12062   char ch;
12063
12064   do {
12065     ch = *p++;
12066     if (ch == '\r') continue;
12067     *q++ = ch;
12068   } while (ch != '\0');
12069 }
12070
12071 void
12072 AppendComment(index, text)
12073      int index;
12074      char *text;
12075 {
12076     int oldlen, len;
12077     char *old;
12078
12079     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12080
12081     CrushCRs(text);
12082     while (*text == '\n') text++;
12083     len = strlen(text);
12084     while (len > 0 && text[len - 1] == '\n') len--;
12085
12086     if (len == 0) return;
12087
12088     if (commentList[index] != NULL) {
12089         old = commentList[index];
12090         oldlen = strlen(old);
12091         commentList[index] = (char *) malloc(oldlen + len + 2);
12092         strcpy(commentList[index], old);
12093         free(old);
12094         strncpy(&commentList[index][oldlen], text, len);
12095         commentList[index][oldlen + len] = '\n';
12096         commentList[index][oldlen + len + 1] = NULLCHAR;
12097     } else {
12098         commentList[index] = (char *) malloc(len + 2);
12099         strncpy(commentList[index], text, len);
12100         commentList[index][len] = '\n';
12101         commentList[index][len + 1] = NULLCHAR;
12102     }
12103 }
12104
12105 static char * FindStr( char * text, char * sub_text )
12106 {
12107     char * result = strstr( text, sub_text );
12108
12109     if( result != NULL ) {
12110         result += strlen( sub_text );
12111     }
12112
12113     return result;
12114 }
12115
12116 /* [AS] Try to extract PV info from PGN comment */
12117 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12118 char *GetInfoFromComment( int index, char * text )
12119 {
12120     char * sep = text;
12121
12122     if( text != NULL && index > 0 ) {
12123         int score = 0;
12124         int depth = 0;
12125         int time = -1, sec = 0, deci;
12126         char * s_eval = FindStr( text, "[%eval " );
12127         char * s_emt = FindStr( text, "[%emt " );
12128
12129         if( s_eval != NULL || s_emt != NULL ) {
12130             /* New style */
12131             char delim;
12132
12133             if( s_eval != NULL ) {
12134                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12135                     return text;
12136                 }
12137
12138                 if( delim != ']' ) {
12139                     return text;
12140                 }
12141             }
12142
12143             if( s_emt != NULL ) {
12144             }
12145         }
12146         else {
12147             /* We expect something like: [+|-]nnn.nn/dd */
12148             int score_lo = 0;
12149
12150             sep = strchr( text, '/' );
12151             if( sep == NULL || sep < (text+4) ) {
12152                 return text;
12153             }
12154
12155             time = -1; sec = -1; deci = -1;
12156             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12157                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12158                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12159                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12160                 return text;
12161             }
12162
12163             if( score_lo < 0 || score_lo >= 100 ) {
12164                 return text;
12165             }
12166
12167             if(sec >= 0) time = 600*time + 10*sec; else
12168             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12169
12170             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12171
12172             /* [HGM] PV time: now locate end of PV info */
12173             while( *++sep >= '0' && *sep <= '9'); // strip depth
12174             if(time >= 0)
12175             while( *++sep >= '0' && *sep <= '9'); // strip time
12176             if(sec >= 0)
12177             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12178             if(deci >= 0)
12179             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12180             while(*sep == ' ') sep++;
12181         }
12182
12183         if( depth <= 0 ) {
12184             return text;
12185         }
12186
12187         if( time < 0 ) {
12188             time = -1;
12189         }
12190
12191         pvInfoList[index-1].depth = depth;
12192         pvInfoList[index-1].score = score;
12193         pvInfoList[index-1].time  = 10*time; // centi-sec
12194     }
12195     return sep;
12196 }
12197
12198 void
12199 SendToProgram(message, cps)
12200      char *message;
12201      ChessProgramState *cps;
12202 {
12203     int count, outCount, error;
12204     char buf[MSG_SIZ];
12205
12206     if (cps->pr == NULL) return;
12207     Attention(cps);
12208     
12209     if (appData.debugMode) {
12210         TimeMark now;
12211         GetTimeMark(&now);
12212         fprintf(debugFP, "%ld >%-6s: %s", 
12213                 SubtractTimeMarks(&now, &programStartTime),
12214                 cps->which, message);
12215     }
12216     
12217     count = strlen(message);
12218     outCount = OutputToProcess(cps->pr, message, count, &error);
12219     if (outCount < count && !exiting 
12220                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12221         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12222         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12223             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12224                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12225                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12226             } else {
12227                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12228             }
12229             gameInfo.resultDetails = buf;
12230         }
12231         DisplayFatalError(buf, error, 1);
12232     }
12233 }
12234
12235 void
12236 ReceiveFromProgram(isr, closure, message, count, error)
12237      InputSourceRef isr;
12238      VOIDSTAR closure;
12239      char *message;
12240      int count;
12241      int error;
12242 {
12243     char *end_str;
12244     char buf[MSG_SIZ];
12245     ChessProgramState *cps = (ChessProgramState *)closure;
12246
12247     if (isr != cps->isr) return; /* Killed intentionally */
12248     if (count <= 0) {
12249         if (count == 0) {
12250             sprintf(buf,
12251                     _("Error: %s chess program (%s) exited unexpectedly"),
12252                     cps->which, cps->program);
12253         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12254                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12255                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12256                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12257                 } else {
12258                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12259                 }
12260                 gameInfo.resultDetails = buf;
12261             }
12262             RemoveInputSource(cps->isr);
12263             DisplayFatalError(buf, 0, 1);
12264         } else {
12265             sprintf(buf,
12266                     _("Error reading from %s chess program (%s)"),
12267                     cps->which, cps->program);
12268             RemoveInputSource(cps->isr);
12269
12270             /* [AS] Program is misbehaving badly... kill it */
12271             if( count == -2 ) {
12272                 DestroyChildProcess( cps->pr, 9 );
12273                 cps->pr = NoProc;
12274             }
12275
12276             DisplayFatalError(buf, error, 1);
12277         }
12278         return;
12279     }
12280     
12281     if ((end_str = strchr(message, '\r')) != NULL)
12282       *end_str = NULLCHAR;
12283     if ((end_str = strchr(message, '\n')) != NULL)
12284       *end_str = NULLCHAR;
12285     
12286     if (appData.debugMode) {
12287         TimeMark now; int print = 1;
12288         char *quote = ""; char c; int i;
12289
12290         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12291                 char start = message[0];
12292                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12293                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12294                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12295                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12296                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12297                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12298                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12299                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12300                         { quote = "# "; print = (appData.engineComments == 2); }
12301                 message[0] = start; // restore original message
12302         }
12303         if(print) {
12304                 GetTimeMark(&now);
12305                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12306                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12307                         quote,
12308                         message);
12309         }
12310     }
12311
12312     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12313     if (appData.icsEngineAnalyze) {
12314         if (strstr(message, "whisper") != NULL ||
12315              strstr(message, "kibitz") != NULL || 
12316             strstr(message, "tellics") != NULL) return;
12317     }
12318
12319     HandleMachineMove(message, cps);
12320 }
12321
12322
12323 void
12324 SendTimeControl(cps, mps, tc, inc, sd, st)
12325      ChessProgramState *cps;
12326      int mps, inc, sd, st;
12327      long tc;
12328 {
12329     char buf[MSG_SIZ];
12330     int seconds;
12331
12332     if( timeControl_2 > 0 ) {
12333         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12334             tc = timeControl_2;
12335         }
12336     }
12337     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12338     inc /= cps->timeOdds;
12339     st  /= cps->timeOdds;
12340
12341     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12342
12343     if (st > 0) {
12344       /* Set exact time per move, normally using st command */
12345       if (cps->stKludge) {
12346         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12347         seconds = st % 60;
12348         if (seconds == 0) {
12349           sprintf(buf, "level 1 %d\n", st/60);
12350         } else {
12351           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12352         }
12353       } else {
12354         sprintf(buf, "st %d\n", st);
12355       }
12356     } else {
12357       /* Set conventional or incremental time control, using level command */
12358       if (seconds == 0) {
12359         /* Note old gnuchess bug -- minutes:seconds used to not work.
12360            Fixed in later versions, but still avoid :seconds
12361            when seconds is 0. */
12362         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12363       } else {
12364         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12365                 seconds, inc/1000);
12366       }
12367     }
12368     SendToProgram(buf, cps);
12369
12370     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12371     /* Orthogonally, limit search to given depth */
12372     if (sd > 0) {
12373       if (cps->sdKludge) {
12374         sprintf(buf, "depth\n%d\n", sd);
12375       } else {
12376         sprintf(buf, "sd %d\n", sd);
12377       }
12378       SendToProgram(buf, cps);
12379     }
12380
12381     if(cps->nps > 0) { /* [HGM] nps */
12382         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12383         else {
12384                 sprintf(buf, "nps %d\n", cps->nps);
12385               SendToProgram(buf, cps);
12386         }
12387     }
12388 }
12389
12390 ChessProgramState *WhitePlayer()
12391 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12392 {
12393     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12394        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12395         return &second;
12396     return &first;
12397 }
12398
12399 void
12400 SendTimeRemaining(cps, machineWhite)
12401      ChessProgramState *cps;
12402      int /*boolean*/ machineWhite;
12403 {
12404     char message[MSG_SIZ];
12405     long time, otime;
12406
12407     /* Note: this routine must be called when the clocks are stopped
12408        or when they have *just* been set or switched; otherwise
12409        it will be off by the time since the current tick started.
12410     */
12411     if (machineWhite) {
12412         time = whiteTimeRemaining / 10;
12413         otime = blackTimeRemaining / 10;
12414     } else {
12415         time = blackTimeRemaining / 10;
12416         otime = whiteTimeRemaining / 10;
12417     }
12418     /* [HGM] translate opponent's time by time-odds factor */
12419     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12420     if (appData.debugMode) {
12421         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12422     }
12423
12424     if (time <= 0) time = 1;
12425     if (otime <= 0) otime = 1;
12426     
12427     sprintf(message, "time %ld\n", time);
12428     SendToProgram(message, cps);
12429
12430     sprintf(message, "otim %ld\n", otime);
12431     SendToProgram(message, cps);
12432 }
12433
12434 int
12435 BoolFeature(p, name, loc, cps)
12436      char **p;
12437      char *name;
12438      int *loc;
12439      ChessProgramState *cps;
12440 {
12441   char buf[MSG_SIZ];
12442   int len = strlen(name);
12443   int val;
12444   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12445     (*p) += len + 1;
12446     sscanf(*p, "%d", &val);
12447     *loc = (val != 0);
12448     while (**p && **p != ' ') (*p)++;
12449     sprintf(buf, "accepted %s\n", name);
12450     SendToProgram(buf, cps);
12451     return TRUE;
12452   }
12453   return FALSE;
12454 }
12455
12456 int
12457 IntFeature(p, name, loc, cps)
12458      char **p;
12459      char *name;
12460      int *loc;
12461      ChessProgramState *cps;
12462 {
12463   char buf[MSG_SIZ];
12464   int len = strlen(name);
12465   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12466     (*p) += len + 1;
12467     sscanf(*p, "%d", loc);
12468     while (**p && **p != ' ') (*p)++;
12469     sprintf(buf, "accepted %s\n", name);
12470     SendToProgram(buf, cps);
12471     return TRUE;
12472   }
12473   return FALSE;
12474 }
12475
12476 int
12477 StringFeature(p, name, loc, cps)
12478      char **p;
12479      char *name;
12480      char loc[];
12481      ChessProgramState *cps;
12482 {
12483   char buf[MSG_SIZ];
12484   int len = strlen(name);
12485   if (strncmp((*p), name, len) == 0
12486       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12487     (*p) += len + 2;
12488     sscanf(*p, "%[^\"]", loc);
12489     while (**p && **p != '\"') (*p)++;
12490     if (**p == '\"') (*p)++;
12491     sprintf(buf, "accepted %s\n", name);
12492     SendToProgram(buf, cps);
12493     return TRUE;
12494   }
12495   return FALSE;
12496 }
12497
12498 int 
12499 ParseOption(Option *opt, ChessProgramState *cps)
12500 // [HGM] options: process the string that defines an engine option, and determine
12501 // name, type, default value, and allowed value range
12502 {
12503         char *p, *q, buf[MSG_SIZ];
12504         int n, min = (-1)<<31, max = 1<<31, def;
12505
12506         if(p = strstr(opt->name, " -spin ")) {
12507             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12508             if(max < min) max = min; // enforce consistency
12509             if(def < min) def = min;
12510             if(def > max) def = max;
12511             opt->value = def;
12512             opt->min = min;
12513             opt->max = max;
12514             opt->type = Spin;
12515         } else if((p = strstr(opt->name, " -slider "))) {
12516             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12517             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12518             if(max < min) max = min; // enforce consistency
12519             if(def < min) def = min;
12520             if(def > max) def = max;
12521             opt->value = def;
12522             opt->min = min;
12523             opt->max = max;
12524             opt->type = Spin; // Slider;
12525         } else if((p = strstr(opt->name, " -string "))) {
12526             opt->textValue = p+9;
12527             opt->type = TextBox;
12528         } else if((p = strstr(opt->name, " -file "))) {
12529             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12530             opt->textValue = p+7;
12531             opt->type = TextBox; // FileName;
12532         } else if((p = strstr(opt->name, " -path "))) {
12533             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12534             opt->textValue = p+7;
12535             opt->type = TextBox; // PathName;
12536         } else if(p = strstr(opt->name, " -check ")) {
12537             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12538             opt->value = (def != 0);
12539             opt->type = CheckBox;
12540         } else if(p = strstr(opt->name, " -combo ")) {
12541             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12542             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12543             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12544             opt->value = n = 0;
12545             while(q = StrStr(q, " /// ")) {
12546                 n++; *q = 0;    // count choices, and null-terminate each of them
12547                 q += 5;
12548                 if(*q == '*') { // remember default, which is marked with * prefix
12549                     q++;
12550                     opt->value = n;
12551                 }
12552                 cps->comboList[cps->comboCnt++] = q;
12553             }
12554             cps->comboList[cps->comboCnt++] = NULL;
12555             opt->max = n + 1;
12556             opt->type = ComboBox;
12557         } else if(p = strstr(opt->name, " -button")) {
12558             opt->type = Button;
12559         } else if(p = strstr(opt->name, " -save")) {
12560             opt->type = SaveButton;
12561         } else return FALSE;
12562         *p = 0; // terminate option name
12563         // now look if the command-line options define a setting for this engine option.
12564         if(cps->optionSettings && cps->optionSettings[0])
12565             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12566         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12567                 sprintf(buf, "option %s", p);
12568                 if(p = strstr(buf, ",")) *p = 0;
12569                 strcat(buf, "\n");
12570                 SendToProgram(buf, cps);
12571         }
12572         return TRUE;
12573 }
12574
12575 void
12576 FeatureDone(cps, val)
12577      ChessProgramState* cps;
12578      int val;
12579 {
12580   DelayedEventCallback cb = GetDelayedEvent();
12581   if ((cb == InitBackEnd3 && cps == &first) ||
12582       (cb == TwoMachinesEventIfReady && cps == &second)) {
12583     CancelDelayedEvent();
12584     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12585   }
12586   cps->initDone = val;
12587 }
12588
12589 /* Parse feature command from engine */
12590 void
12591 ParseFeatures(args, cps)
12592      char* args;
12593      ChessProgramState *cps;  
12594 {
12595   char *p = args;
12596   char *q;
12597   int val;
12598   char buf[MSG_SIZ];
12599
12600   for (;;) {
12601     while (*p == ' ') p++;
12602     if (*p == NULLCHAR) return;
12603
12604     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12605     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12606     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12607     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12608     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12609     if (BoolFeature(&p, "reuse", &val, cps)) {
12610       /* Engine can disable reuse, but can't enable it if user said no */
12611       if (!val) cps->reuse = FALSE;
12612       continue;
12613     }
12614     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12615     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12616       if (gameMode == TwoMachinesPlay) {
12617         DisplayTwoMachinesTitle();
12618       } else {
12619         DisplayTitle("");
12620       }
12621       continue;
12622     }
12623     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12624     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12625     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12626     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12627     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12628     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12629     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12630     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12631     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12632     if (IntFeature(&p, "done", &val, cps)) {
12633       FeatureDone(cps, val);
12634       continue;
12635     }
12636     /* Added by Tord: */
12637     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12638     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12639     /* End of additions by Tord */
12640
12641     /* [HGM] added features: */
12642     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12643     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12644     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12645     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12646     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12647     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12648     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12649         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12650             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12651             SendToProgram(buf, cps);
12652             continue;
12653         }
12654         if(cps->nrOptions >= MAX_OPTIONS) {
12655             cps->nrOptions--;
12656             sprintf(buf, "%s engine has too many options\n", cps->which);
12657             DisplayError(buf, 0);
12658         }
12659         continue;
12660     }
12661     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12662     /* End of additions by HGM */
12663
12664     /* unknown feature: complain and skip */
12665     q = p;
12666     while (*q && *q != '=') q++;
12667     sprintf(buf, "rejected %.*s\n", q-p, p);
12668     SendToProgram(buf, cps);
12669     p = q;
12670     if (*p == '=') {
12671       p++;
12672       if (*p == '\"') {
12673         p++;
12674         while (*p && *p != '\"') p++;
12675         if (*p == '\"') p++;
12676       } else {
12677         while (*p && *p != ' ') p++;
12678       }
12679     }
12680   }
12681
12682 }
12683
12684 void
12685 PeriodicUpdatesEvent(newState)
12686      int newState;
12687 {
12688     if (newState == appData.periodicUpdates)
12689       return;
12690
12691     appData.periodicUpdates=newState;
12692
12693     /* Display type changes, so update it now */
12694     DisplayAnalysis();
12695
12696     /* Get the ball rolling again... */
12697     if (newState) {
12698         AnalysisPeriodicEvent(1);
12699         StartAnalysisClock();
12700     }
12701 }
12702
12703 void
12704 PonderNextMoveEvent(newState)
12705      int newState;
12706 {
12707     if (newState == appData.ponderNextMove) return;
12708     if (gameMode == EditPosition) EditPositionDone();
12709     if (newState) {
12710         SendToProgram("hard\n", &first);
12711         if (gameMode == TwoMachinesPlay) {
12712             SendToProgram("hard\n", &second);
12713         }
12714     } else {
12715         SendToProgram("easy\n", &first);
12716         thinkOutput[0] = NULLCHAR;
12717         if (gameMode == TwoMachinesPlay) {
12718             SendToProgram("easy\n", &second);
12719         }
12720     }
12721     appData.ponderNextMove = newState;
12722 }
12723
12724 void
12725 NewSettingEvent(option, command, value)
12726      char *command;
12727      int option, value;
12728 {
12729     char buf[MSG_SIZ];
12730
12731     if (gameMode == EditPosition) EditPositionDone();
12732     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12733     SendToProgram(buf, &first);
12734     if (gameMode == TwoMachinesPlay) {
12735         SendToProgram(buf, &second);
12736     }
12737 }
12738
12739 void
12740 ShowThinkingEvent()
12741 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12742 {
12743     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12744     int newState = appData.showThinking
12745         // [HGM] thinking: other features now need thinking output as well
12746         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12747     
12748     if (oldState == newState) return;
12749     oldState = newState;
12750     if (gameMode == EditPosition) EditPositionDone();
12751     if (oldState) {
12752         SendToProgram("post\n", &first);
12753         if (gameMode == TwoMachinesPlay) {
12754             SendToProgram("post\n", &second);
12755         }
12756     } else {
12757         SendToProgram("nopost\n", &first);
12758         thinkOutput[0] = NULLCHAR;
12759         if (gameMode == TwoMachinesPlay) {
12760             SendToProgram("nopost\n", &second);
12761         }
12762     }
12763 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12764 }
12765
12766 void
12767 AskQuestionEvent(title, question, replyPrefix, which)
12768      char *title; char *question; char *replyPrefix; char *which;
12769 {
12770   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12771   if (pr == NoProc) return;
12772   AskQuestion(title, question, replyPrefix, pr);
12773 }
12774
12775 void
12776 DisplayMove(moveNumber)
12777      int moveNumber;
12778 {
12779     char message[MSG_SIZ];
12780     char res[MSG_SIZ];
12781     char cpThinkOutput[MSG_SIZ];
12782
12783     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12784     
12785     if (moveNumber == forwardMostMove - 1 || 
12786         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12787
12788         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12789
12790         if (strchr(cpThinkOutput, '\n')) {
12791             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12792         }
12793     } else {
12794         *cpThinkOutput = NULLCHAR;
12795     }
12796
12797     /* [AS] Hide thinking from human user */
12798     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12799         *cpThinkOutput = NULLCHAR;
12800         if( thinkOutput[0] != NULLCHAR ) {
12801             int i;
12802
12803             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12804                 cpThinkOutput[i] = '.';
12805             }
12806             cpThinkOutput[i] = NULLCHAR;
12807             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12808         }
12809     }
12810
12811     if (moveNumber == forwardMostMove - 1 &&
12812         gameInfo.resultDetails != NULL) {
12813         if (gameInfo.resultDetails[0] == NULLCHAR) {
12814             sprintf(res, " %s", PGNResult(gameInfo.result));
12815         } else {
12816             sprintf(res, " {%s} %s",
12817                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12818         }
12819     } else {
12820         res[0] = NULLCHAR;
12821     }
12822
12823     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12824         DisplayMessage(res, cpThinkOutput);
12825     } else {
12826         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12827                 WhiteOnMove(moveNumber) ? " " : ".. ",
12828                 parseList[moveNumber], res);
12829         DisplayMessage(message, cpThinkOutput);
12830     }
12831 }
12832
12833 void
12834 DisplayAnalysisText(text)
12835      char *text;
12836 {
12837     char buf[MSG_SIZ];
12838
12839     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12840                || appData.icsEngineAnalyze) {
12841         sprintf(buf, "Analysis (%s)", first.tidy);
12842         AnalysisPopUp(buf, text);
12843     }
12844 }
12845
12846 static int
12847 only_one_move(str)
12848      char *str;
12849 {
12850     while (*str && isspace(*str)) ++str;
12851     while (*str && !isspace(*str)) ++str;
12852     if (!*str) return 1;
12853     while (*str && isspace(*str)) ++str;
12854     if (!*str) return 1;
12855     return 0;
12856 }
12857
12858 void
12859 DisplayAnalysis()
12860 {
12861     char buf[MSG_SIZ];
12862     char lst[MSG_SIZ / 2];
12863     double nps;
12864     static char *xtra[] = { "", " (--)", " (++)" };
12865     int h, m, s, cs;
12866   
12867     if (programStats.time == 0) {
12868         programStats.time = 1;
12869     }
12870   
12871     if (programStats.got_only_move) {
12872         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12873     } else {
12874         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12875
12876         nps = (u64ToDouble(programStats.nodes) /
12877              ((double)programStats.time /100.0));
12878
12879         cs = programStats.time % 100;
12880         s = programStats.time / 100;
12881         h = (s / (60*60));
12882         s = s - h*60*60;
12883         m = (s/60);
12884         s = s - m*60;
12885
12886         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12887           if (programStats.move_name[0] != NULLCHAR) {
12888             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12889                     programStats.depth,
12890                     programStats.nr_moves-programStats.moves_left,
12891                     programStats.nr_moves, programStats.move_name,
12892                     ((float)programStats.score)/100.0, lst,
12893                     only_one_move(lst)?
12894                     xtra[programStats.got_fail] : "",
12895                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12896           } else {
12897             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12898                     programStats.depth,
12899                     programStats.nr_moves-programStats.moves_left,
12900                     programStats.nr_moves, ((float)programStats.score)/100.0,
12901                     lst,
12902                     only_one_move(lst)?
12903                     xtra[programStats.got_fail] : "",
12904                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12905           }
12906         } else {
12907             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12908                     programStats.depth,
12909                     ((float)programStats.score)/100.0,
12910                     lst,
12911                     only_one_move(lst)?
12912                     xtra[programStats.got_fail] : "",
12913                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12914         }
12915     }
12916     DisplayAnalysisText(buf);
12917 }
12918
12919 void
12920 DisplayComment(moveNumber, text)
12921      int moveNumber;
12922      char *text;
12923 {
12924     char title[MSG_SIZ];
12925     char buf[8000]; // comment can be long!
12926     int score, depth;
12927
12928     if( appData.autoDisplayComment ) {
12929         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12930             strcpy(title, "Comment");
12931         } else {
12932             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12933                     WhiteOnMove(moveNumber) ? " " : ".. ",
12934                     parseList[moveNumber]);
12935         }
12936         // [HGM] PV info: display PV info together with (or as) comment
12937         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12938             if(text == NULL) text = "";                                           
12939             score = pvInfoList[moveNumber].score;
12940             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12941                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12942             text = buf;
12943         }
12944     } else title[0] = 0;
12945
12946     if (text != NULL)
12947         CommentPopUp(title, text);
12948 }
12949
12950 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12951  * might be busy thinking or pondering.  It can be omitted if your
12952  * gnuchess is configured to stop thinking immediately on any user
12953  * input.  However, that gnuchess feature depends on the FIONREAD
12954  * ioctl, which does not work properly on some flavors of Unix.
12955  */
12956 void
12957 Attention(cps)
12958      ChessProgramState *cps;
12959 {
12960 #if ATTENTION
12961     if (!cps->useSigint) return;
12962     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12963     switch (gameMode) {
12964       case MachinePlaysWhite:
12965       case MachinePlaysBlack:
12966       case TwoMachinesPlay:
12967       case IcsPlayingWhite:
12968       case IcsPlayingBlack:
12969       case AnalyzeMode:
12970       case AnalyzeFile:
12971         /* Skip if we know it isn't thinking */
12972         if (!cps->maybeThinking) return;
12973         if (appData.debugMode)
12974           fprintf(debugFP, "Interrupting %s\n", cps->which);
12975         InterruptChildProcess(cps->pr);
12976         cps->maybeThinking = FALSE;
12977         break;
12978       default:
12979         break;
12980     }
12981 #endif /*ATTENTION*/
12982 }
12983
12984 int
12985 CheckFlags()
12986 {
12987     if (whiteTimeRemaining <= 0) {
12988         if (!whiteFlag) {
12989             whiteFlag = TRUE;
12990             if (appData.icsActive) {
12991                 if (appData.autoCallFlag &&
12992                     gameMode == IcsPlayingBlack && !blackFlag) {
12993                   SendToICS(ics_prefix);
12994                   SendToICS("flag\n");
12995                 }
12996             } else {
12997                 if (blackFlag) {
12998                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12999                 } else {
13000                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13001                     if (appData.autoCallFlag) {
13002                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13003                         return TRUE;
13004                     }
13005                 }
13006             }
13007         }
13008     }
13009     if (blackTimeRemaining <= 0) {
13010         if (!blackFlag) {
13011             blackFlag = TRUE;
13012             if (appData.icsActive) {
13013                 if (appData.autoCallFlag &&
13014                     gameMode == IcsPlayingWhite && !whiteFlag) {
13015                   SendToICS(ics_prefix);
13016                   SendToICS("flag\n");
13017                 }
13018             } else {
13019                 if (whiteFlag) {
13020                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13021                 } else {
13022                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13023                     if (appData.autoCallFlag) {
13024                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13025                         return TRUE;
13026                     }
13027                 }
13028             }
13029         }
13030     }
13031     return FALSE;
13032 }
13033
13034 void
13035 CheckTimeControl()
13036 {
13037     if (!appData.clockMode || appData.icsActive ||
13038         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13039
13040     /*
13041      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13042      */
13043     if ( !WhiteOnMove(forwardMostMove) )
13044         /* White made time control */
13045         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13046         /* [HGM] time odds: correct new time quota for time odds! */
13047                                             / WhitePlayer()->timeOdds;
13048       else
13049         /* Black made time control */
13050         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13051                                             / WhitePlayer()->other->timeOdds;
13052 }
13053
13054 void
13055 DisplayBothClocks()
13056 {
13057     int wom = gameMode == EditPosition ?
13058       !blackPlaysFirst : WhiteOnMove(currentMove);
13059     DisplayWhiteClock(whiteTimeRemaining, wom);
13060     DisplayBlackClock(blackTimeRemaining, !wom);
13061 }
13062
13063
13064 /* Timekeeping seems to be a portability nightmare.  I think everyone
13065    has ftime(), but I'm really not sure, so I'm including some ifdefs
13066    to use other calls if you don't.  Clocks will be less accurate if
13067    you have neither ftime nor gettimeofday.
13068 */
13069
13070 /* VS 2008 requires the #include outside of the function */
13071 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13072 #include <sys/timeb.h>
13073 #endif
13074
13075 /* Get the current time as a TimeMark */
13076 void
13077 GetTimeMark(tm)
13078      TimeMark *tm;
13079 {
13080 #if HAVE_GETTIMEOFDAY
13081
13082     struct timeval timeVal;
13083     struct timezone timeZone;
13084
13085     gettimeofday(&timeVal, &timeZone);
13086     tm->sec = (long) timeVal.tv_sec; 
13087     tm->ms = (int) (timeVal.tv_usec / 1000L);
13088
13089 #else /*!HAVE_GETTIMEOFDAY*/
13090 #if HAVE_FTIME
13091
13092 // include <sys/timeb.h> / moved to just above start of function
13093     struct timeb timeB;
13094
13095     ftime(&timeB);
13096     tm->sec = (long) timeB.time;
13097     tm->ms = (int) timeB.millitm;
13098
13099 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13100     tm->sec = (long) time(NULL);
13101     tm->ms = 0;
13102 #endif
13103 #endif
13104 }
13105
13106 /* Return the difference in milliseconds between two
13107    time marks.  We assume the difference will fit in a long!
13108 */
13109 long
13110 SubtractTimeMarks(tm2, tm1)
13111      TimeMark *tm2, *tm1;
13112 {
13113     return 1000L*(tm2->sec - tm1->sec) +
13114            (long) (tm2->ms - tm1->ms);
13115 }
13116
13117
13118 /*
13119  * Code to manage the game clocks.
13120  *
13121  * In tournament play, black starts the clock and then white makes a move.
13122  * We give the human user a slight advantage if he is playing white---the
13123  * clocks don't run until he makes his first move, so it takes zero time.
13124  * Also, we don't account for network lag, so we could get out of sync
13125  * with GNU Chess's clock -- but then, referees are always right.  
13126  */
13127
13128 static TimeMark tickStartTM;
13129 static long intendedTickLength;
13130
13131 long
13132 NextTickLength(timeRemaining)
13133      long timeRemaining;
13134 {
13135     long nominalTickLength, nextTickLength;
13136
13137     if (timeRemaining > 0L && timeRemaining <= 10000L)
13138       nominalTickLength = 100L;
13139     else
13140       nominalTickLength = 1000L;
13141     nextTickLength = timeRemaining % nominalTickLength;
13142     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13143
13144     return nextTickLength;
13145 }
13146
13147 /* Adjust clock one minute up or down */
13148 void
13149 AdjustClock(Boolean which, int dir)
13150 {
13151     if(which) blackTimeRemaining += 60000*dir;
13152     else      whiteTimeRemaining += 60000*dir;
13153     DisplayBothClocks();
13154 }
13155
13156 /* Stop clocks and reset to a fresh time control */
13157 void
13158 ResetClocks() 
13159 {
13160     (void) StopClockTimer();
13161     if (appData.icsActive) {
13162         whiteTimeRemaining = blackTimeRemaining = 0;
13163     } else { /* [HGM] correct new time quote for time odds */
13164         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13165         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13166     }
13167     if (whiteFlag || blackFlag) {
13168         DisplayTitle("");
13169         whiteFlag = blackFlag = FALSE;
13170     }
13171     DisplayBothClocks();
13172 }
13173
13174 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13175
13176 /* Decrement running clock by amount of time that has passed */
13177 void
13178 DecrementClocks()
13179 {
13180     long timeRemaining;
13181     long lastTickLength, fudge;
13182     TimeMark now;
13183
13184     if (!appData.clockMode) return;
13185     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13186         
13187     GetTimeMark(&now);
13188
13189     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13190
13191     /* Fudge if we woke up a little too soon */
13192     fudge = intendedTickLength - lastTickLength;
13193     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13194
13195     if (WhiteOnMove(forwardMostMove)) {
13196         if(whiteNPS >= 0) lastTickLength = 0;
13197         timeRemaining = whiteTimeRemaining -= lastTickLength;
13198         DisplayWhiteClock(whiteTimeRemaining - fudge,
13199                           WhiteOnMove(currentMove));
13200     } else {
13201         if(blackNPS >= 0) lastTickLength = 0;
13202         timeRemaining = blackTimeRemaining -= lastTickLength;
13203         DisplayBlackClock(blackTimeRemaining - fudge,
13204                           !WhiteOnMove(currentMove));
13205     }
13206
13207     if (CheckFlags()) return;
13208         
13209     tickStartTM = now;
13210     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13211     StartClockTimer(intendedTickLength);
13212
13213     /* if the time remaining has fallen below the alarm threshold, sound the
13214      * alarm. if the alarm has sounded and (due to a takeback or time control
13215      * with increment) the time remaining has increased to a level above the
13216      * threshold, reset the alarm so it can sound again. 
13217      */
13218     
13219     if (appData.icsActive && appData.icsAlarm) {
13220
13221         /* make sure we are dealing with the user's clock */
13222         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13223                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13224            )) return;
13225
13226         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13227             alarmSounded = FALSE;
13228         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13229             PlayAlarmSound();
13230             alarmSounded = TRUE;
13231         }
13232     }
13233 }
13234
13235
13236 /* A player has just moved, so stop the previously running
13237    clock and (if in clock mode) start the other one.
13238    We redisplay both clocks in case we're in ICS mode, because
13239    ICS gives us an update to both clocks after every move.
13240    Note that this routine is called *after* forwardMostMove
13241    is updated, so the last fractional tick must be subtracted
13242    from the color that is *not* on move now.
13243 */
13244 void
13245 SwitchClocks()
13246 {
13247     long lastTickLength;
13248     TimeMark now;
13249     int flagged = FALSE;
13250
13251     GetTimeMark(&now);
13252
13253     if (StopClockTimer() && appData.clockMode) {
13254         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13255         if (WhiteOnMove(forwardMostMove)) {
13256             if(blackNPS >= 0) lastTickLength = 0;
13257             blackTimeRemaining -= lastTickLength;
13258            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13259 //         if(pvInfoList[forwardMostMove-1].time == -1)
13260                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13261                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13262         } else {
13263            if(whiteNPS >= 0) lastTickLength = 0;
13264            whiteTimeRemaining -= lastTickLength;
13265            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13266 //         if(pvInfoList[forwardMostMove-1].time == -1)
13267                  pvInfoList[forwardMostMove-1].time = 
13268                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13269         }
13270         flagged = CheckFlags();
13271     }
13272     CheckTimeControl();
13273
13274     if (flagged || !appData.clockMode) return;
13275
13276     switch (gameMode) {
13277       case MachinePlaysBlack:
13278       case MachinePlaysWhite:
13279       case BeginningOfGame:
13280         if (pausing) return;
13281         break;
13282
13283       case EditGame:
13284       case PlayFromGameFile:
13285       case IcsExamining:
13286         return;
13287
13288       default:
13289         break;
13290     }
13291
13292     tickStartTM = now;
13293     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13294       whiteTimeRemaining : blackTimeRemaining);
13295     StartClockTimer(intendedTickLength);
13296 }
13297         
13298
13299 /* Stop both clocks */
13300 void
13301 StopClocks()
13302 {       
13303     long lastTickLength;
13304     TimeMark now;
13305
13306     if (!StopClockTimer()) return;
13307     if (!appData.clockMode) return;
13308
13309     GetTimeMark(&now);
13310
13311     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13312     if (WhiteOnMove(forwardMostMove)) {
13313         if(whiteNPS >= 0) lastTickLength = 0;
13314         whiteTimeRemaining -= lastTickLength;
13315         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13316     } else {
13317         if(blackNPS >= 0) lastTickLength = 0;
13318         blackTimeRemaining -= lastTickLength;
13319         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13320     }
13321     CheckFlags();
13322 }
13323         
13324 /* Start clock of player on move.  Time may have been reset, so
13325    if clock is already running, stop and restart it. */
13326 void
13327 StartClocks()
13328 {
13329     (void) StopClockTimer(); /* in case it was running already */
13330     DisplayBothClocks();
13331     if (CheckFlags()) return;
13332
13333     if (!appData.clockMode) return;
13334     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13335
13336     GetTimeMark(&tickStartTM);
13337     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13338       whiteTimeRemaining : blackTimeRemaining);
13339
13340    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13341     whiteNPS = blackNPS = -1; 
13342     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13343        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13344         whiteNPS = first.nps;
13345     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13346        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13347         blackNPS = first.nps;
13348     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13349         whiteNPS = second.nps;
13350     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13351         blackNPS = second.nps;
13352     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13353
13354     StartClockTimer(intendedTickLength);
13355 }
13356
13357 char *
13358 TimeString(ms)
13359      long ms;
13360 {
13361     long second, minute, hour, day;
13362     char *sign = "";
13363     static char buf[32];
13364     
13365     if (ms > 0 && ms <= 9900) {
13366       /* convert milliseconds to tenths, rounding up */
13367       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13368
13369       sprintf(buf, " %03.1f ", tenths/10.0);
13370       return buf;
13371     }
13372
13373     /* convert milliseconds to seconds, rounding up */
13374     /* use floating point to avoid strangeness of integer division
13375        with negative dividends on many machines */
13376     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13377
13378     if (second < 0) {
13379         sign = "-";
13380         second = -second;
13381     }
13382     
13383     day = second / (60 * 60 * 24);
13384     second = second % (60 * 60 * 24);
13385     hour = second / (60 * 60);
13386     second = second % (60 * 60);
13387     minute = second / 60;
13388     second = second % 60;
13389     
13390     if (day > 0)
13391       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13392               sign, day, hour, minute, second);
13393     else if (hour > 0)
13394       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13395     else
13396       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13397     
13398     return buf;
13399 }
13400
13401
13402 /*
13403  * This is necessary because some C libraries aren't ANSI C compliant yet.
13404  */
13405 char *
13406 StrStr(string, match)
13407      char *string, *match;
13408 {
13409     int i, length;
13410     
13411     length = strlen(match);
13412     
13413     for (i = strlen(string) - length; i >= 0; i--, string++)
13414       if (!strncmp(match, string, length))
13415         return string;
13416     
13417     return NULL;
13418 }
13419
13420 char *
13421 StrCaseStr(string, match)
13422      char *string, *match;
13423 {
13424     int i, j, length;
13425     
13426     length = strlen(match);
13427     
13428     for (i = strlen(string) - length; i >= 0; i--, string++) {
13429         for (j = 0; j < length; j++) {
13430             if (ToLower(match[j]) != ToLower(string[j]))
13431               break;
13432         }
13433         if (j == length) return string;
13434     }
13435
13436     return NULL;
13437 }
13438
13439 #ifndef _amigados
13440 int
13441 StrCaseCmp(s1, s2)
13442      char *s1, *s2;
13443 {
13444     char c1, c2;
13445     
13446     for (;;) {
13447         c1 = ToLower(*s1++);
13448         c2 = ToLower(*s2++);
13449         if (c1 > c2) return 1;
13450         if (c1 < c2) return -1;
13451         if (c1 == NULLCHAR) return 0;
13452     }
13453 }
13454
13455
13456 int
13457 ToLower(c)
13458      int c;
13459 {
13460     return isupper(c) ? tolower(c) : c;
13461 }
13462
13463
13464 int
13465 ToUpper(c)
13466      int c;
13467 {
13468     return islower(c) ? toupper(c) : c;
13469 }
13470 #endif /* !_amigados    */
13471
13472 char *
13473 StrSave(s)
13474      char *s;
13475 {
13476     char *ret;
13477
13478     if ((ret = (char *) malloc(strlen(s) + 1))) {
13479         strcpy(ret, s);
13480     }
13481     return ret;
13482 }
13483
13484 char *
13485 StrSavePtr(s, savePtr)
13486      char *s, **savePtr;
13487 {
13488     if (*savePtr) {
13489         free(*savePtr);
13490     }
13491     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13492         strcpy(*savePtr, s);
13493     }
13494     return(*savePtr);
13495 }
13496
13497 char *
13498 PGNDate()
13499 {
13500     time_t clock;
13501     struct tm *tm;
13502     char buf[MSG_SIZ];
13503
13504     clock = time((time_t *)NULL);
13505     tm = localtime(&clock);
13506     sprintf(buf, "%04d.%02d.%02d",
13507             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13508     return StrSave(buf);
13509 }
13510
13511
13512 char *
13513 PositionToFEN(move, overrideCastling)
13514      int move;
13515      char *overrideCastling;
13516 {
13517     int i, j, fromX, fromY, toX, toY;
13518     int whiteToPlay;
13519     char buf[128];
13520     char *p, *q;
13521     int emptycount;
13522     ChessSquare piece;
13523
13524     whiteToPlay = (gameMode == EditPosition) ?
13525       !blackPlaysFirst : (move % 2 == 0);
13526     p = buf;
13527
13528     /* Piece placement data */
13529     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13530         emptycount = 0;
13531         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13532             if (boards[move][i][j] == EmptySquare) {
13533                 emptycount++;
13534             } else { ChessSquare piece = boards[move][i][j];
13535                 if (emptycount > 0) {
13536                     if(emptycount<10) /* [HGM] can be >= 10 */
13537                         *p++ = '0' + emptycount;
13538                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13539                     emptycount = 0;
13540                 }
13541                 if(PieceToChar(piece) == '+') {
13542                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13543                     *p++ = '+';
13544                     piece = (ChessSquare)(DEMOTED piece);
13545                 } 
13546                 *p++ = PieceToChar(piece);
13547                 if(p[-1] == '~') {
13548                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13549                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13550                     *p++ = '~';
13551                 }
13552             }
13553         }
13554         if (emptycount > 0) {
13555             if(emptycount<10) /* [HGM] can be >= 10 */
13556                 *p++ = '0' + emptycount;
13557             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13558             emptycount = 0;
13559         }
13560         *p++ = '/';
13561     }
13562     *(p - 1) = ' ';
13563
13564     /* [HGM] print Crazyhouse or Shogi holdings */
13565     if( gameInfo.holdingsWidth ) {
13566         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13567         q = p;
13568         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13569             piece = boards[move][i][BOARD_WIDTH-1];
13570             if( piece != EmptySquare )
13571               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13572                   *p++ = PieceToChar(piece);
13573         }
13574         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13575             piece = boards[move][BOARD_HEIGHT-i-1][0];
13576             if( piece != EmptySquare )
13577               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13578                   *p++ = PieceToChar(piece);
13579         }
13580
13581         if( q == p ) *p++ = '-';
13582         *p++ = ']';
13583         *p++ = ' ';
13584     }
13585
13586     /* Active color */
13587     *p++ = whiteToPlay ? 'w' : 'b';
13588     *p++ = ' ';
13589
13590   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13591     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13592   } else {
13593   if(nrCastlingRights) {
13594      q = p;
13595      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13596        /* [HGM] write directly from rights */
13597            if(castlingRights[move][2] >= 0 &&
13598               castlingRights[move][0] >= 0   )
13599                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13600            if(castlingRights[move][2] >= 0 &&
13601               castlingRights[move][1] >= 0   )
13602                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13603            if(castlingRights[move][5] >= 0 &&
13604               castlingRights[move][3] >= 0   )
13605                 *p++ = castlingRights[move][3] + AAA;
13606            if(castlingRights[move][5] >= 0 &&
13607               castlingRights[move][4] >= 0   )
13608                 *p++ = castlingRights[move][4] + AAA;
13609      } else {
13610
13611         /* [HGM] write true castling rights */
13612         if( nrCastlingRights == 6 ) {
13613             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13614                castlingRights[move][2] >= 0  ) *p++ = 'K';
13615             if(castlingRights[move][1] == BOARD_LEFT &&
13616                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13617             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13618                castlingRights[move][5] >= 0  ) *p++ = 'k';
13619             if(castlingRights[move][4] == BOARD_LEFT &&
13620                castlingRights[move][5] >= 0  ) *p++ = 'q';
13621         }
13622      }
13623      if (q == p) *p++ = '-'; /* No castling rights */
13624      *p++ = ' ';
13625   }
13626
13627   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13628      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13629     /* En passant target square */
13630     if (move > backwardMostMove) {
13631         fromX = moveList[move - 1][0] - AAA;
13632         fromY = moveList[move - 1][1] - ONE;
13633         toX = moveList[move - 1][2] - AAA;
13634         toY = moveList[move - 1][3] - ONE;
13635         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13636             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13637             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13638             fromX == toX) {
13639             /* 2-square pawn move just happened */
13640             *p++ = toX + AAA;
13641             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13642         } else {
13643             *p++ = '-';
13644         }
13645     } else {
13646         *p++ = '-';
13647     }
13648     *p++ = ' ';
13649   }
13650   }
13651
13652     /* [HGM] find reversible plies */
13653     {   int i = 0, j=move;
13654
13655         if (appData.debugMode) { int k;
13656             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13657             for(k=backwardMostMove; k<=forwardMostMove; k++)
13658                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13659
13660         }
13661
13662         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13663         if( j == backwardMostMove ) i += initialRulePlies;
13664         sprintf(p, "%d ", i);
13665         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13666     }
13667     /* Fullmove number */
13668     sprintf(p, "%d", (move / 2) + 1);
13669     
13670     return StrSave(buf);
13671 }
13672
13673 Boolean
13674 ParseFEN(board, blackPlaysFirst, fen)
13675     Board board;
13676      int *blackPlaysFirst;
13677      char *fen;
13678 {
13679     int i, j;
13680     char *p;
13681     int emptycount;
13682     ChessSquare piece;
13683
13684     p = fen;
13685
13686     /* [HGM] by default clear Crazyhouse holdings, if present */
13687     if(gameInfo.holdingsWidth) {
13688        for(i=0; i<BOARD_HEIGHT; i++) {
13689            board[i][0]             = EmptySquare; /* black holdings */
13690            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13691            board[i][1]             = (ChessSquare) 0; /* black counts */
13692            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13693        }
13694     }
13695
13696     /* Piece placement data */
13697     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13698         j = 0;
13699         for (;;) {
13700             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13701                 if (*p == '/') p++;
13702                 emptycount = gameInfo.boardWidth - j;
13703                 while (emptycount--)
13704                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13705                 break;
13706 #if(BOARD_SIZE >= 10)
13707             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13708                 p++; emptycount=10;
13709                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13710                 while (emptycount--)
13711                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13712 #endif
13713             } else if (isdigit(*p)) {
13714                 emptycount = *p++ - '0';
13715                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13716                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13717                 while (emptycount--)
13718                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13719             } else if (*p == '+' || isalpha(*p)) {
13720                 if (j >= gameInfo.boardWidth) return FALSE;
13721                 if(*p=='+') {
13722                     piece = CharToPiece(*++p);
13723                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13724                     piece = (ChessSquare) (PROMOTED piece ); p++;
13725                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13726                 } else piece = CharToPiece(*p++);
13727
13728                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13729                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13730                     piece = (ChessSquare) (PROMOTED piece);
13731                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13732                     p++;
13733                 }
13734                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13735             } else {
13736                 return FALSE;
13737             }
13738         }
13739     }
13740     while (*p == '/' || *p == ' ') p++;
13741
13742     /* [HGM] look for Crazyhouse holdings here */
13743     while(*p==' ') p++;
13744     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13745         if(*p == '[') p++;
13746         if(*p == '-' ) *p++; /* empty holdings */ else {
13747             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13748             /* if we would allow FEN reading to set board size, we would   */
13749             /* have to add holdings and shift the board read so far here   */
13750             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13751                 *p++;
13752                 if((int) piece >= (int) BlackPawn ) {
13753                     i = (int)piece - (int)BlackPawn;
13754                     i = PieceToNumber((ChessSquare)i);
13755                     if( i >= gameInfo.holdingsSize ) return FALSE;
13756                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13757                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13758                 } else {
13759                     i = (int)piece - (int)WhitePawn;
13760                     i = PieceToNumber((ChessSquare)i);
13761                     if( i >= gameInfo.holdingsSize ) return FALSE;
13762                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13763                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13764                 }
13765             }
13766         }
13767         if(*p == ']') *p++;
13768     }
13769
13770     while(*p == ' ') p++;
13771
13772     /* Active color */
13773     switch (*p++) {
13774       case 'w':
13775         *blackPlaysFirst = FALSE;
13776         break;
13777       case 'b': 
13778         *blackPlaysFirst = TRUE;
13779         break;
13780       default:
13781         return FALSE;
13782     }
13783
13784     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13785     /* return the extra info in global variiables             */
13786
13787     /* set defaults in case FEN is incomplete */
13788     FENepStatus = EP_UNKNOWN;
13789     for(i=0; i<nrCastlingRights; i++ ) {
13790         FENcastlingRights[i] =
13791             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13792     }   /* assume possible unless obviously impossible */
13793     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13794     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13795     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13796     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13797     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13798     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13799     FENrulePlies = 0;
13800
13801     while(*p==' ') p++;
13802     if(nrCastlingRights) {
13803       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13804           /* castling indicator present, so default becomes no castlings */
13805           for(i=0; i<nrCastlingRights; i++ ) {
13806                  FENcastlingRights[i] = -1;
13807           }
13808       }
13809       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13810              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13811              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13812              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13813         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13814
13815         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13816             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13817             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13818         }
13819         switch(c) {
13820           case'K':
13821               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13822               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13823               FENcastlingRights[2] = whiteKingFile;
13824               break;
13825           case'Q':
13826               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13827               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13828               FENcastlingRights[2] = whiteKingFile;
13829               break;
13830           case'k':
13831               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13832               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13833               FENcastlingRights[5] = blackKingFile;
13834               break;
13835           case'q':
13836               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13837               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13838               FENcastlingRights[5] = blackKingFile;
13839           case '-':
13840               break;
13841           default: /* FRC castlings */
13842               if(c >= 'a') { /* black rights */
13843                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13844                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13845                   if(i == BOARD_RGHT) break;
13846                   FENcastlingRights[5] = i;
13847                   c -= AAA;
13848                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13849                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13850                   if(c > i)
13851                       FENcastlingRights[3] = c;
13852                   else
13853                       FENcastlingRights[4] = c;
13854               } else { /* white rights */
13855                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13856                     if(board[0][i] == WhiteKing) break;
13857                   if(i == BOARD_RGHT) break;
13858                   FENcastlingRights[2] = i;
13859                   c -= AAA - 'a' + 'A';
13860                   if(board[0][c] >= WhiteKing) break;
13861                   if(c > i)
13862                       FENcastlingRights[0] = c;
13863                   else
13864                       FENcastlingRights[1] = c;
13865               }
13866         }
13867       }
13868     if (appData.debugMode) {
13869         fprintf(debugFP, "FEN castling rights:");
13870         for(i=0; i<nrCastlingRights; i++)
13871         fprintf(debugFP, " %d", FENcastlingRights[i]);
13872         fprintf(debugFP, "\n");
13873     }
13874
13875       while(*p==' ') p++;
13876     }
13877
13878     /* read e.p. field in games that know e.p. capture */
13879     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13880        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13881       if(*p=='-') {
13882         p++; FENepStatus = EP_NONE;
13883       } else {
13884          char c = *p++ - AAA;
13885
13886          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13887          if(*p >= '0' && *p <='9') *p++;
13888          FENepStatus = c;
13889       }
13890     }
13891
13892
13893     if(sscanf(p, "%d", &i) == 1) {
13894         FENrulePlies = i; /* 50-move ply counter */
13895         /* (The move number is still ignored)    */
13896     }
13897
13898     return TRUE;
13899 }
13900       
13901 void
13902 EditPositionPasteFEN(char *fen)
13903 {
13904   if (fen != NULL) {
13905     Board initial_position;
13906
13907     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13908       DisplayError(_("Bad FEN position in clipboard"), 0);
13909       return ;
13910     } else {
13911       int savedBlackPlaysFirst = blackPlaysFirst;
13912       EditPositionEvent();
13913       blackPlaysFirst = savedBlackPlaysFirst;
13914       CopyBoard(boards[0], initial_position);
13915           /* [HGM] copy FEN attributes as well */
13916           {   int i;
13917               initialRulePlies = FENrulePlies;
13918               epStatus[0] = FENepStatus;
13919               for( i=0; i<nrCastlingRights; i++ )
13920                   castlingRights[0][i] = FENcastlingRights[i];
13921           }
13922       EditPositionDone();
13923       DisplayBothClocks();
13924       DrawPosition(FALSE, boards[currentMove]);
13925     }
13926   }
13927 }