fix joining of lines split by ICS
[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                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2128                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2129             }
2130         }
2131
2132         buf[buf_len] = NULLCHAR;
2133         next_out = leftover_len;
2134         leftover_start = 0;
2135         
2136         i = 0;
2137         while (i < buf_len) {
2138             /* Deal with part of the TELNET option negotiation
2139                protocol.  We refuse to do anything beyond the
2140                defaults, except that we allow the WILL ECHO option,
2141                which ICS uses to turn off password echoing when we are
2142                directly connected to it.  We reject this option
2143                if localLineEditing mode is on (always on in xboard)
2144                and we are talking to port 23, which might be a real
2145                telnet server that will try to keep WILL ECHO on permanently.
2146              */
2147             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2148                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2149                 unsigned char option;
2150                 oldi = i;
2151                 switch ((unsigned char) buf[++i]) {
2152                   case TN_WILL:
2153                     if (appData.debugMode)
2154                       fprintf(debugFP, "\n<WILL ");
2155                     switch (option = (unsigned char) buf[++i]) {
2156                       case TN_ECHO:
2157                         if (appData.debugMode)
2158                           fprintf(debugFP, "ECHO ");
2159                         /* Reply only if this is a change, according
2160                            to the protocol rules. */
2161                         if (remoteEchoOption) break;
2162                         if (appData.localLineEditing &&
2163                             atoi(appData.icsPort) == TN_PORT) {
2164                             TelnetRequest(TN_DONT, TN_ECHO);
2165                         } else {
2166                             EchoOff();
2167                             TelnetRequest(TN_DO, TN_ECHO);
2168                             remoteEchoOption = TRUE;
2169                         }
2170                         break;
2171                       default:
2172                         if (appData.debugMode)
2173                           fprintf(debugFP, "%d ", option);
2174                         /* Whatever this is, we don't want it. */
2175                         TelnetRequest(TN_DONT, option);
2176                         break;
2177                     }
2178                     break;
2179                   case TN_WONT:
2180                     if (appData.debugMode)
2181                       fprintf(debugFP, "\n<WONT ");
2182                     switch (option = (unsigned char) buf[++i]) {
2183                       case TN_ECHO:
2184                         if (appData.debugMode)
2185                           fprintf(debugFP, "ECHO ");
2186                         /* Reply only if this is a change, according
2187                            to the protocol rules. */
2188                         if (!remoteEchoOption) break;
2189                         EchoOn();
2190                         TelnetRequest(TN_DONT, TN_ECHO);
2191                         remoteEchoOption = FALSE;
2192                         break;
2193                       default:
2194                         if (appData.debugMode)
2195                           fprintf(debugFP, "%d ", (unsigned char) option);
2196                         /* Whatever this is, it must already be turned
2197                            off, because we never agree to turn on
2198                            anything non-default, so according to the
2199                            protocol rules, we don't reply. */
2200                         break;
2201                     }
2202                     break;
2203                   case TN_DO:
2204                     if (appData.debugMode)
2205                       fprintf(debugFP, "\n<DO ");
2206                     switch (option = (unsigned char) buf[++i]) {
2207                       default:
2208                         /* Whatever this is, we refuse to do it. */
2209                         if (appData.debugMode)
2210                           fprintf(debugFP, "%d ", option);
2211                         TelnetRequest(TN_WONT, option);
2212                         break;
2213                     }
2214                     break;
2215                   case TN_DONT:
2216                     if (appData.debugMode)
2217                       fprintf(debugFP, "\n<DONT ");
2218                     switch (option = (unsigned char) buf[++i]) {
2219                       default:
2220                         if (appData.debugMode)
2221                           fprintf(debugFP, "%d ", option);
2222                         /* Whatever this is, we are already not doing
2223                            it, because we never agree to do anything
2224                            non-default, so according to the protocol
2225                            rules, we don't reply. */
2226                         break;
2227                     }
2228                     break;
2229                   case TN_IAC:
2230                     if (appData.debugMode)
2231                       fprintf(debugFP, "\n<IAC ");
2232                     /* Doubled IAC; pass it through */
2233                     i--;
2234                     break;
2235                   default:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2238                     /* Drop all other telnet commands on the floor */
2239                     break;
2240                 }
2241                 if (oldi > next_out)
2242                   SendToPlayer(&buf[next_out], oldi - next_out);
2243                 if (++i > next_out)
2244                   next_out = i;
2245                 continue;
2246             }
2247                 
2248             /* OK, this at least will *usually* work */
2249             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2250                 loggedOn = TRUE;
2251             }
2252             
2253             if (loggedOn && !intfSet) {
2254                 if (ics_type == ICS_ICC) {
2255                   sprintf(str,
2256                           "/set-quietly interface %s\n/set-quietly style 12\n",
2257                           programVersion);
2258
2259                 } else if (ics_type == ICS_CHESSNET) {
2260                   sprintf(str, "/style 12\n");
2261                 } else {
2262                   strcpy(str, "alias $ @\n$set interface ");
2263                   strcat(str, programVersion);
2264                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2265 #ifdef WIN32
2266                   strcat(str, "$iset nohighlight 1\n");
2267 #endif
2268                   strcat(str, "$iset lock 1\n$style 12\n");
2269                 }
2270                 SendToICS(str);
2271                 intfSet = TRUE;
2272             }
2273
2274             if (started == STARTED_COMMENT) {
2275                 /* Accumulate characters in comment */
2276                 parse[parse_pos++] = buf[i];
2277                 if (buf[i] == '\n') {
2278                     parse[parse_pos] = NULLCHAR;
2279                     if(chattingPartner>=0) {
2280                         char mess[MSG_SIZ];
2281                         sprintf(mess, "%s%s", talker, parse);
2282                         OutputChatMessage(chattingPartner, mess);
2283                         chattingPartner = -1;
2284                     } else
2285                     if(!suppressKibitz) // [HGM] kibitz
2286                         AppendComment(forwardMostMove, StripHighlight(parse));
2287                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2288                         int nrDigit = 0, nrAlph = 0, i;
2289                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2290                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2291                         parse[parse_pos] = NULLCHAR;
2292                         // try to be smart: if it does not look like search info, it should go to
2293                         // ICS interaction window after all, not to engine-output window.
2294                         for(i=0; i<parse_pos; i++) { // count letters and digits
2295                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2296                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2297                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2298                         }
2299                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2300                             int depth=0; float score;
2301                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2302                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2303                                 pvInfoList[forwardMostMove-1].depth = depth;
2304                                 pvInfoList[forwardMostMove-1].score = 100*score;
2305                             }
2306                             OutputKibitz(suppressKibitz, parse);
2307                         } else {
2308                             char tmp[MSG_SIZ];
2309                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2310                             SendToPlayer(tmp, strlen(tmp));
2311                         }
2312                     }
2313                     started = STARTED_NONE;
2314                 } else {
2315                     /* Don't match patterns against characters in chatter */
2316                     i++;
2317                     continue;
2318                 }
2319             }
2320             if (started == STARTED_CHATTER) {
2321                 if (buf[i] != '\n') {
2322                     /* Don't match patterns against characters in chatter */
2323                     i++;
2324                     continue;
2325                 }
2326                 started = STARTED_NONE;
2327             }
2328
2329             /* Kludge to deal with rcmd protocol */
2330             if (firstTime && looking_at(buf, &i, "\001*")) {
2331                 DisplayFatalError(&buf[1], 0, 1);
2332                 continue;
2333             } else {
2334                 firstTime = FALSE;
2335             }
2336
2337             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2338                 ics_type = ICS_ICC;
2339                 ics_prefix = "/";
2340                 if (appData.debugMode)
2341                   fprintf(debugFP, "ics_type %d\n", ics_type);
2342                 continue;
2343             }
2344             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2345                 ics_type = ICS_FICS;
2346                 ics_prefix = "$";
2347                 if (appData.debugMode)
2348                   fprintf(debugFP, "ics_type %d\n", ics_type);
2349                 continue;
2350             }
2351             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2352                 ics_type = ICS_CHESSNET;
2353                 ics_prefix = "/";
2354                 if (appData.debugMode)
2355                   fprintf(debugFP, "ics_type %d\n", ics_type);
2356                 continue;
2357             }
2358
2359             if (!loggedOn &&
2360                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2361                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2362                  looking_at(buf, &i, "will be \"*\""))) {
2363               strcpy(ics_handle, star_match[0]);
2364               continue;
2365             }
2366
2367             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2368               char buf[MSG_SIZ];
2369               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2370               DisplayIcsInteractionTitle(buf);
2371               have_set_title = TRUE;
2372             }
2373
2374             /* skip finger notes */
2375             if (started == STARTED_NONE &&
2376                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2377                  (buf[i] == '1' && buf[i+1] == '0')) &&
2378                 buf[i+2] == ':' && buf[i+3] == ' ') {
2379               started = STARTED_CHATTER;
2380               i += 3;
2381               continue;
2382             }
2383
2384             /* skip formula vars */
2385             if (started == STARTED_NONE &&
2386                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2387               started = STARTED_CHATTER;
2388               i += 3;
2389               continue;
2390             }
2391
2392             oldi = i;
2393             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2394             if (appData.autoKibitz && started == STARTED_NONE && 
2395                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2396                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2397                 if(looking_at(buf, &i, "* kibitzes: ") &&
2398                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2399                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2400                         suppressKibitz = TRUE;
2401                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2402                                 && (gameMode == IcsPlayingWhite)) ||
2403                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2404                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2405                             started = STARTED_CHATTER; // own kibitz we simply discard
2406                         else {
2407                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2408                             parse_pos = 0; parse[0] = NULLCHAR;
2409                             savingComment = TRUE;
2410                             suppressKibitz = gameMode != IcsObserving ? 2 :
2411                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2412                         } 
2413                         continue;
2414                 } else
2415                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2416                     started = STARTED_CHATTER;
2417                     suppressKibitz = TRUE;
2418                 }
2419             } // [HGM] kibitz: end of patch
2420
2421 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2422
2423             // [HGM] chat: intercept tells by users for which we have an open chat window
2424             channel = -1;
2425             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2426                                            looking_at(buf, &i, "* whispers:") ||
2427                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2428                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2429                 int p;
2430                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2431                 chattingPartner = -1;
2432
2433                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2434                 for(p=0; p<MAX_CHAT; p++) {
2435                     if(channel == atoi(chatPartner[p])) {
2436                     talker[0] = '['; strcat(talker, "]");
2437                     chattingPartner = p; break;
2438                     }
2439                 } else
2440                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2441                 for(p=0; p<MAX_CHAT; p++) {
2442                     if(!strcmp("WHISPER", chatPartner[p])) {
2443                         talker[0] = '['; strcat(talker, "]");
2444                         chattingPartner = p; break;
2445                     }
2446                 }
2447                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2448                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2449                     talker[0] = 0;
2450                     chattingPartner = p; break;
2451                 }
2452                 if(chattingPartner<0) i = oldi; else {
2453                     started = STARTED_COMMENT;
2454                     parse_pos = 0; parse[0] = NULLCHAR;
2455                     savingComment = TRUE;
2456                     suppressKibitz = TRUE;
2457                 }
2458             } // [HGM] chat: end of patch
2459
2460             if (appData.zippyTalk || appData.zippyPlay) {
2461                 /* [DM] Backup address for color zippy lines */
2462                 backup = i;
2463 #if ZIPPY
2464        #ifdef WIN32
2465                if (loggedOn == TRUE)
2466                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2467                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2468        #else
2469                 if (ZippyControl(buf, &i) ||
2470                     ZippyConverse(buf, &i) ||
2471                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2472                       loggedOn = TRUE;
2473                       if (!appData.colorize) continue;
2474                 }
2475        #endif
2476 #endif
2477             } // [DM] 'else { ' deleted
2478                 if (
2479                     /* Regular tells and says */
2480                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2481                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2482                     looking_at(buf, &i, "* says: ") ||
2483                     /* Don't color "message" or "messages" output */
2484                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2485                     looking_at(buf, &i, "*. * at *:*: ") ||
2486                     looking_at(buf, &i, "--* (*:*): ") ||
2487                     /* Message notifications (same color as tells) */
2488                     looking_at(buf, &i, "* has left a message ") ||
2489                     looking_at(buf, &i, "* just sent you a message:\n") ||
2490                     /* Whispers and kibitzes */
2491                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2492                     looking_at(buf, &i, "* kibitzes: ") ||
2493                     /* Channel tells */
2494                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2495
2496                   if (tkind == 1 && strchr(star_match[0], ':')) {
2497                       /* Avoid "tells you:" spoofs in channels */
2498                      tkind = 3;
2499                   }
2500                   if (star_match[0][0] == NULLCHAR ||
2501                       strchr(star_match[0], ' ') ||
2502                       (tkind == 3 && strchr(star_match[1], ' '))) {
2503                     /* Reject bogus matches */
2504                     i = oldi;
2505                   } else {
2506                     if (appData.colorize) {
2507                       if (oldi > next_out) {
2508                         SendToPlayer(&buf[next_out], oldi - next_out);
2509                         next_out = oldi;
2510                       }
2511                       switch (tkind) {
2512                       case 1:
2513                         Colorize(ColorTell, FALSE);
2514                         curColor = ColorTell;
2515                         break;
2516                       case 2:
2517                         Colorize(ColorKibitz, FALSE);
2518                         curColor = ColorKibitz;
2519                         break;
2520                       case 3:
2521                         p = strrchr(star_match[1], '(');
2522                         if (p == NULL) {
2523                           p = star_match[1];
2524                         } else {
2525                           p++;
2526                         }
2527                         if (atoi(p) == 1) {
2528                           Colorize(ColorChannel1, FALSE);
2529                           curColor = ColorChannel1;
2530                         } else {
2531                           Colorize(ColorChannel, FALSE);
2532                           curColor = ColorChannel;
2533                         }
2534                         break;
2535                       case 5:
2536                         curColor = ColorNormal;
2537                         break;
2538                       }
2539                     }
2540                     if (started == STARTED_NONE && appData.autoComment &&
2541                         (gameMode == IcsObserving ||
2542                          gameMode == IcsPlayingWhite ||
2543                          gameMode == IcsPlayingBlack)) {
2544                       parse_pos = i - oldi;
2545                       memcpy(parse, &buf[oldi], parse_pos);
2546                       parse[parse_pos] = NULLCHAR;
2547                       started = STARTED_COMMENT;
2548                       savingComment = TRUE;
2549                     } else {
2550                       started = STARTED_CHATTER;
2551                       savingComment = FALSE;
2552                     }
2553                     loggedOn = TRUE;
2554                     continue;
2555                   }
2556                 }
2557
2558                 if (looking_at(buf, &i, "* s-shouts: ") ||
2559                     looking_at(buf, &i, "* c-shouts: ")) {
2560                     if (appData.colorize) {
2561                         if (oldi > next_out) {
2562                             SendToPlayer(&buf[next_out], oldi - next_out);
2563                             next_out = oldi;
2564                         }
2565                         Colorize(ColorSShout, FALSE);
2566                         curColor = ColorSShout;
2567                     }
2568                     loggedOn = TRUE;
2569                     started = STARTED_CHATTER;
2570                     continue;
2571                 }
2572
2573                 if (looking_at(buf, &i, "--->")) {
2574                     loggedOn = TRUE;
2575                     continue;
2576                 }
2577
2578                 if (looking_at(buf, &i, "* shouts: ") ||
2579                     looking_at(buf, &i, "--> ")) {
2580                     if (appData.colorize) {
2581                         if (oldi > next_out) {
2582                             SendToPlayer(&buf[next_out], oldi - next_out);
2583                             next_out = oldi;
2584                         }
2585                         Colorize(ColorShout, FALSE);
2586                         curColor = ColorShout;
2587                     }
2588                     loggedOn = TRUE;
2589                     started = STARTED_CHATTER;
2590                     continue;
2591                 }
2592
2593                 if (looking_at( buf, &i, "Challenge:")) {
2594                     if (appData.colorize) {
2595                         if (oldi > next_out) {
2596                             SendToPlayer(&buf[next_out], oldi - next_out);
2597                             next_out = oldi;
2598                         }
2599                         Colorize(ColorChallenge, FALSE);
2600                         curColor = ColorChallenge;
2601                     }
2602                     loggedOn = TRUE;
2603                     continue;
2604                 }
2605
2606                 if (looking_at(buf, &i, "* offers you") ||
2607                     looking_at(buf, &i, "* offers to be") ||
2608                     looking_at(buf, &i, "* would like to") ||
2609                     looking_at(buf, &i, "* requests to") ||
2610                     looking_at(buf, &i, "Your opponent offers") ||
2611                     looking_at(buf, &i, "Your opponent requests")) {
2612
2613                     if (appData.colorize) {
2614                         if (oldi > next_out) {
2615                             SendToPlayer(&buf[next_out], oldi - next_out);
2616                             next_out = oldi;
2617                         }
2618                         Colorize(ColorRequest, FALSE);
2619                         curColor = ColorRequest;
2620                     }
2621                     continue;
2622                 }
2623
2624                 if (looking_at(buf, &i, "* (*) seeking")) {
2625                     if (appData.colorize) {
2626                         if (oldi > next_out) {
2627                             SendToPlayer(&buf[next_out], oldi - next_out);
2628                             next_out = oldi;
2629                         }
2630                         Colorize(ColorSeek, FALSE);
2631                         curColor = ColorSeek;
2632                     }
2633                     continue;
2634             }
2635
2636             if (looking_at(buf, &i, "\\   ")) {
2637                 if (prevColor != ColorNormal) {
2638                     if (oldi > next_out) {
2639                         SendToPlayer(&buf[next_out], oldi - next_out);
2640                         next_out = oldi;
2641                     }
2642                     Colorize(prevColor, TRUE);
2643                     curColor = prevColor;
2644                 }
2645                 if (savingComment) {
2646                     parse_pos = i - oldi;
2647                     memcpy(parse, &buf[oldi], parse_pos);
2648                     parse[parse_pos] = NULLCHAR;
2649                     started = STARTED_COMMENT;
2650                 } else {
2651                     started = STARTED_CHATTER;
2652                 }
2653                 continue;
2654             }
2655
2656             if (looking_at(buf, &i, "Black Strength :") ||
2657                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2658                 looking_at(buf, &i, "<10>") ||
2659                 looking_at(buf, &i, "#@#")) {
2660                 /* Wrong board style */
2661                 loggedOn = TRUE;
2662                 SendToICS(ics_prefix);
2663                 SendToICS("set style 12\n");
2664                 SendToICS(ics_prefix);
2665                 SendToICS("refresh\n");
2666                 continue;
2667             }
2668             
2669             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2670                 ICSInitScript();
2671                 have_sent_ICS_logon = 1;
2672                 continue;
2673             }
2674               
2675             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2676                 (looking_at(buf, &i, "\n<12> ") ||
2677                  looking_at(buf, &i, "<12> "))) {
2678                 loggedOn = TRUE;
2679                 if (oldi > next_out) {
2680                     SendToPlayer(&buf[next_out], oldi - next_out);
2681                 }
2682                 next_out = i;
2683                 started = STARTED_BOARD;
2684                 parse_pos = 0;
2685                 continue;
2686             }
2687
2688             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2689                 looking_at(buf, &i, "<b1> ")) {
2690                 if (oldi > next_out) {
2691                     SendToPlayer(&buf[next_out], oldi - next_out);
2692                 }
2693                 next_out = i;
2694                 started = STARTED_HOLDINGS;
2695                 parse_pos = 0;
2696                 continue;
2697             }
2698
2699             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2700                 loggedOn = TRUE;
2701                 /* Header for a move list -- first line */
2702
2703                 switch (ics_getting_history) {
2704                   case H_FALSE:
2705                     switch (gameMode) {
2706                       case IcsIdle:
2707                       case BeginningOfGame:
2708                         /* User typed "moves" or "oldmoves" while we
2709                            were idle.  Pretend we asked for these
2710                            moves and soak them up so user can step
2711                            through them and/or save them.
2712                            */
2713                         Reset(FALSE, TRUE);
2714                         gameMode = IcsObserving;
2715                         ModeHighlight();
2716                         ics_gamenum = -1;
2717                         ics_getting_history = H_GOT_UNREQ_HEADER;
2718                         break;
2719                       case EditGame: /*?*/
2720                       case EditPosition: /*?*/
2721                         /* Should above feature work in these modes too? */
2722                         /* For now it doesn't */
2723                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2724                         break;
2725                       default:
2726                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2727                         break;
2728                     }
2729                     break;
2730                   case H_REQUESTED:
2731                     /* Is this the right one? */
2732                     if (gameInfo.white && gameInfo.black &&
2733                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2734                         strcmp(gameInfo.black, star_match[2]) == 0) {
2735                         /* All is well */
2736                         ics_getting_history = H_GOT_REQ_HEADER;
2737                     }
2738                     break;
2739                   case H_GOT_REQ_HEADER:
2740                   case H_GOT_UNREQ_HEADER:
2741                   case H_GOT_UNWANTED_HEADER:
2742                   case H_GETTING_MOVES:
2743                     /* Should not happen */
2744                     DisplayError(_("Error gathering move list: two headers"), 0);
2745                     ics_getting_history = H_FALSE;
2746                     break;
2747                 }
2748
2749                 /* Save player ratings into gameInfo if needed */
2750                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2751                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2752                     (gameInfo.whiteRating == -1 ||
2753                      gameInfo.blackRating == -1)) {
2754
2755                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2756                     gameInfo.blackRating = string_to_rating(star_match[3]);
2757                     if (appData.debugMode)
2758                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2759                               gameInfo.whiteRating, gameInfo.blackRating);
2760                 }
2761                 continue;
2762             }
2763
2764             if (looking_at(buf, &i,
2765               "* * match, initial time: * minute*, increment: * second")) {
2766                 /* Header for a move list -- second line */
2767                 /* Initial board will follow if this is a wild game */
2768                 if (gameInfo.event != NULL) free(gameInfo.event);
2769                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2770                 gameInfo.event = StrSave(str);
2771                 /* [HGM] we switched variant. Translate boards if needed. */
2772                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2773                 continue;
2774             }
2775
2776             if (looking_at(buf, &i, "Move  ")) {
2777                 /* Beginning of a move list */
2778                 switch (ics_getting_history) {
2779                   case H_FALSE:
2780                     /* Normally should not happen */
2781                     /* Maybe user hit reset while we were parsing */
2782                     break;
2783                   case H_REQUESTED:
2784                     /* Happens if we are ignoring a move list that is not
2785                      * the one we just requested.  Common if the user
2786                      * tries to observe two games without turning off
2787                      * getMoveList */
2788                     break;
2789                   case H_GETTING_MOVES:
2790                     /* Should not happen */
2791                     DisplayError(_("Error gathering move list: nested"), 0);
2792                     ics_getting_history = H_FALSE;
2793                     break;
2794                   case H_GOT_REQ_HEADER:
2795                     ics_getting_history = H_GETTING_MOVES;
2796                     started = STARTED_MOVES;
2797                     parse_pos = 0;
2798                     if (oldi > next_out) {
2799                         SendToPlayer(&buf[next_out], oldi - next_out);
2800                     }
2801                     break;
2802                   case H_GOT_UNREQ_HEADER:
2803                     ics_getting_history = H_GETTING_MOVES;
2804                     started = STARTED_MOVES_NOHIDE;
2805                     parse_pos = 0;
2806                     break;
2807                   case H_GOT_UNWANTED_HEADER:
2808                     ics_getting_history = H_FALSE;
2809                     break;
2810                 }
2811                 continue;
2812             }                           
2813             
2814             if (looking_at(buf, &i, "% ") ||
2815                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2816                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2817                 savingComment = FALSE;
2818                 switch (started) {
2819                   case STARTED_MOVES:
2820                   case STARTED_MOVES_NOHIDE:
2821                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2822                     parse[parse_pos + i - oldi] = NULLCHAR;
2823                     ParseGameHistory(parse);
2824 #if ZIPPY
2825                     if (appData.zippyPlay && first.initDone) {
2826                         FeedMovesToProgram(&first, forwardMostMove);
2827                         if (gameMode == IcsPlayingWhite) {
2828                             if (WhiteOnMove(forwardMostMove)) {
2829                                 if (first.sendTime) {
2830                                   if (first.useColors) {
2831                                     SendToProgram("black\n", &first); 
2832                                   }
2833                                   SendTimeRemaining(&first, TRUE);
2834                                 }
2835 #if 0
2836                                 if (first.useColors) {
2837                                   SendToProgram("white\ngo\n", &first);
2838                                 } else {
2839                                   SendToProgram("go\n", &first);
2840                                 }
2841 #else
2842                                 if (first.useColors) {
2843                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2844                                 }
2845                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2846 #endif
2847                                 first.maybeThinking = TRUE;
2848                             } else {
2849                                 if (first.usePlayother) {
2850                                   if (first.sendTime) {
2851                                     SendTimeRemaining(&first, TRUE);
2852                                   }
2853                                   SendToProgram("playother\n", &first);
2854                                   firstMove = FALSE;
2855                                 } else {
2856                                   firstMove = TRUE;
2857                                 }
2858                             }
2859                         } else if (gameMode == IcsPlayingBlack) {
2860                             if (!WhiteOnMove(forwardMostMove)) {
2861                                 if (first.sendTime) {
2862                                   if (first.useColors) {
2863                                     SendToProgram("white\n", &first);
2864                                   }
2865                                   SendTimeRemaining(&first, FALSE);
2866                                 }
2867 #if 0
2868                                 if (first.useColors) {
2869                                   SendToProgram("black\ngo\n", &first);
2870                                 } else {
2871                                   SendToProgram("go\n", &first);
2872                                 }
2873 #else
2874                                 if (first.useColors) {
2875                                   SendToProgram("black\n", &first);
2876                                 }
2877                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2878 #endif
2879                                 first.maybeThinking = TRUE;
2880                             } else {
2881                                 if (first.usePlayother) {
2882                                   if (first.sendTime) {
2883                                     SendTimeRemaining(&first, FALSE);
2884                                   }
2885                                   SendToProgram("playother\n", &first);
2886                                   firstMove = FALSE;
2887                                 } else {
2888                                   firstMove = TRUE;
2889                                 }
2890                             }
2891                         }                       
2892                     }
2893 #endif
2894                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2895                         /* Moves came from oldmoves or moves command
2896                            while we weren't doing anything else.
2897                            */
2898                         currentMove = forwardMostMove;
2899                         ClearHighlights();/*!!could figure this out*/
2900                         flipView = appData.flipView;
2901                         DrawPosition(FALSE, boards[currentMove]);
2902                         DisplayBothClocks();
2903                         sprintf(str, "%s vs. %s",
2904                                 gameInfo.white, gameInfo.black);
2905                         DisplayTitle(str);
2906                         gameMode = IcsIdle;
2907                     } else {
2908                         /* Moves were history of an active game */
2909                         if (gameInfo.resultDetails != NULL) {
2910                             free(gameInfo.resultDetails);
2911                             gameInfo.resultDetails = NULL;
2912                         }
2913                     }
2914                     HistorySet(parseList, backwardMostMove,
2915                                forwardMostMove, currentMove-1);
2916                     DisplayMove(currentMove - 1);
2917                     if (started == STARTED_MOVES) next_out = i;
2918                     started = STARTED_NONE;
2919                     ics_getting_history = H_FALSE;
2920                     break;
2921
2922                   case STARTED_OBSERVE:
2923                     started = STARTED_NONE;
2924                     SendToICS(ics_prefix);
2925                     SendToICS("refresh\n");
2926                     break;
2927
2928                   default:
2929                     break;
2930                 }
2931                 if(bookHit) { // [HGM] book: simulate book reply
2932                     static char bookMove[MSG_SIZ]; // a bit generous?
2933
2934                     programStats.nodes = programStats.depth = programStats.time = 
2935                     programStats.score = programStats.got_only_move = 0;
2936                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2937
2938                     strcpy(bookMove, "move ");
2939                     strcat(bookMove, bookHit);
2940                     HandleMachineMove(bookMove, &first);
2941                 }
2942                 continue;
2943             }
2944             
2945             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2946                  started == STARTED_HOLDINGS ||
2947                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2948                 /* Accumulate characters in move list or board */
2949                 parse[parse_pos++] = buf[i];
2950             }
2951             
2952             /* Start of game messages.  Mostly we detect start of game
2953                when the first board image arrives.  On some versions
2954                of the ICS, though, we need to do a "refresh" after starting
2955                to observe in order to get the current board right away. */
2956             if (looking_at(buf, &i, "Adding game * to observation list")) {
2957                 started = STARTED_OBSERVE;
2958                 continue;
2959             }
2960
2961             /* Handle auto-observe */
2962             if (appData.autoObserve &&
2963                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2964                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2965                 char *player;
2966                 /* Choose the player that was highlighted, if any. */
2967                 if (star_match[0][0] == '\033' ||
2968                     star_match[1][0] != '\033') {
2969                     player = star_match[0];
2970                 } else {
2971                     player = star_match[2];
2972                 }
2973                 sprintf(str, "%sobserve %s\n",
2974                         ics_prefix, StripHighlightAndTitle(player));
2975                 SendToICS(str);
2976
2977                 /* Save ratings from notify string */
2978                 strcpy(player1Name, star_match[0]);
2979                 player1Rating = string_to_rating(star_match[1]);
2980                 strcpy(player2Name, star_match[2]);
2981                 player2Rating = string_to_rating(star_match[3]);
2982
2983                 if (appData.debugMode)
2984                   fprintf(debugFP, 
2985                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2986                           player1Name, player1Rating,
2987                           player2Name, player2Rating);
2988
2989                 continue;
2990             }
2991
2992             /* Deal with automatic examine mode after a game,
2993                and with IcsObserving -> IcsExamining transition */
2994             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2995                 looking_at(buf, &i, "has made you an examiner of game *")) {
2996
2997                 int gamenum = atoi(star_match[0]);
2998                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2999                     gamenum == ics_gamenum) {
3000                     /* We were already playing or observing this game;
3001                        no need to refetch history */
3002                     gameMode = IcsExamining;
3003                     if (pausing) {
3004                         pauseExamForwardMostMove = forwardMostMove;
3005                     } else if (currentMove < forwardMostMove) {
3006                         ForwardInner(forwardMostMove);
3007                     }
3008                 } else {
3009                     /* I don't think this case really can happen */
3010                     SendToICS(ics_prefix);
3011                     SendToICS("refresh\n");
3012                 }
3013                 continue;
3014             }    
3015             
3016             /* Error messages */
3017 //          if (ics_user_moved) {
3018             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3019                 if (looking_at(buf, &i, "Illegal move") ||
3020                     looking_at(buf, &i, "Not a legal move") ||
3021                     looking_at(buf, &i, "Your king is in check") ||
3022                     looking_at(buf, &i, "It isn't your turn") ||
3023                     looking_at(buf, &i, "It is not your move")) {
3024                     /* Illegal move */
3025                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3026                         currentMove = --forwardMostMove;
3027                         DisplayMove(currentMove - 1); /* before DMError */
3028                         DrawPosition(FALSE, boards[currentMove]);
3029                         SwitchClocks();
3030                         DisplayBothClocks();
3031                     }
3032                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3033                     ics_user_moved = 0;
3034                     continue;
3035                 }
3036             }
3037
3038             if (looking_at(buf, &i, "still have time") ||
3039                 looking_at(buf, &i, "not out of time") ||
3040                 looking_at(buf, &i, "either player is out of time") ||
3041                 looking_at(buf, &i, "has timeseal; checking")) {
3042                 /* We must have called his flag a little too soon */
3043                 whiteFlag = blackFlag = FALSE;
3044                 continue;
3045             }
3046
3047             if (looking_at(buf, &i, "added * seconds to") ||
3048                 looking_at(buf, &i, "seconds were added to")) {
3049                 /* Update the clocks */
3050                 SendToICS(ics_prefix);
3051                 SendToICS("refresh\n");
3052                 continue;
3053             }
3054
3055             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3056                 ics_clock_paused = TRUE;
3057                 StopClocks();
3058                 continue;
3059             }
3060
3061             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3062                 ics_clock_paused = FALSE;
3063                 StartClocks();
3064                 continue;
3065             }
3066
3067             /* Grab player ratings from the Creating: message.
3068                Note we have to check for the special case when
3069                the ICS inserts things like [white] or [black]. */
3070             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3071                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3072                 /* star_matches:
3073                    0    player 1 name (not necessarily white)
3074                    1    player 1 rating
3075                    2    empty, white, or black (IGNORED)
3076                    3    player 2 name (not necessarily black)
3077                    4    player 2 rating
3078                    
3079                    The names/ratings are sorted out when the game
3080                    actually starts (below).
3081                 */
3082                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3083                 player1Rating = string_to_rating(star_match[1]);
3084                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3085                 player2Rating = string_to_rating(star_match[4]);
3086
3087                 if (appData.debugMode)
3088                   fprintf(debugFP, 
3089                           "Ratings from 'Creating:' %s %d, %s %d\n",
3090                           player1Name, player1Rating,
3091                           player2Name, player2Rating);
3092
3093                 continue;
3094             }
3095             
3096             /* Improved generic start/end-of-game messages */
3097             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3098                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3099                 /* If tkind == 0: */
3100                 /* star_match[0] is the game number */
3101                 /*           [1] is the white player's name */
3102                 /*           [2] is the black player's name */
3103                 /* For end-of-game: */
3104                 /*           [3] is the reason for the game end */
3105                 /*           [4] is a PGN end game-token, preceded by " " */
3106                 /* For start-of-game: */
3107                 /*           [3] begins with "Creating" or "Continuing" */
3108                 /*           [4] is " *" or empty (don't care). */
3109                 int gamenum = atoi(star_match[0]);
3110                 char *whitename, *blackname, *why, *endtoken;
3111                 ChessMove endtype = (ChessMove) 0;
3112
3113                 if (tkind == 0) {
3114                   whitename = star_match[1];
3115                   blackname = star_match[2];
3116                   why = star_match[3];
3117                   endtoken = star_match[4];
3118                 } else {
3119                   whitename = star_match[1];
3120                   blackname = star_match[3];
3121                   why = star_match[5];
3122                   endtoken = star_match[6];
3123                 }
3124
3125                 /* Game start messages */
3126                 if (strncmp(why, "Creating ", 9) == 0 ||
3127                     strncmp(why, "Continuing ", 11) == 0) {
3128                     gs_gamenum = gamenum;
3129                     strcpy(gs_kind, strchr(why, ' ') + 1);
3130 #if ZIPPY
3131                     if (appData.zippyPlay) {
3132                         ZippyGameStart(whitename, blackname);
3133                     }
3134 #endif /*ZIPPY*/
3135                     continue;
3136                 }
3137
3138                 /* Game end messages */
3139                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3140                     ics_gamenum != gamenum) {
3141                     continue;
3142                 }
3143                 while (endtoken[0] == ' ') endtoken++;
3144                 switch (endtoken[0]) {
3145                   case '*':
3146                   default:
3147                     endtype = GameUnfinished;
3148                     break;
3149                   case '0':
3150                     endtype = BlackWins;
3151                     break;
3152                   case '1':
3153                     if (endtoken[1] == '/')
3154                       endtype = GameIsDrawn;
3155                     else
3156                       endtype = WhiteWins;
3157                     break;
3158                 }
3159                 GameEnds(endtype, why, GE_ICS);
3160 #if ZIPPY
3161                 if (appData.zippyPlay && first.initDone) {
3162                     ZippyGameEnd(endtype, why);
3163                     if (first.pr == NULL) {
3164                       /* Start the next process early so that we'll
3165                          be ready for the next challenge */
3166                       StartChessProgram(&first);
3167                     }
3168                     /* Send "new" early, in case this command takes
3169                        a long time to finish, so that we'll be ready
3170                        for the next challenge. */
3171                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3172                     Reset(TRUE, TRUE);
3173                 }
3174 #endif /*ZIPPY*/
3175                 continue;
3176             }
3177
3178             if (looking_at(buf, &i, "Removing game * from observation") ||
3179                 looking_at(buf, &i, "no longer observing game *") ||
3180                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3181                 if (gameMode == IcsObserving &&
3182                     atoi(star_match[0]) == ics_gamenum)
3183                   {
3184                       /* icsEngineAnalyze */
3185                       if (appData.icsEngineAnalyze) {
3186                             ExitAnalyzeMode();
3187                             ModeHighlight();
3188                       }
3189                       StopClocks();
3190                       gameMode = IcsIdle;
3191                       ics_gamenum = -1;
3192                       ics_user_moved = FALSE;
3193                   }
3194                 continue;
3195             }
3196
3197             if (looking_at(buf, &i, "no longer examining game *")) {
3198                 if (gameMode == IcsExamining &&
3199                     atoi(star_match[0]) == ics_gamenum)
3200                   {
3201                       gameMode = IcsIdle;
3202                       ics_gamenum = -1;
3203                       ics_user_moved = FALSE;
3204                   }
3205                 continue;
3206             }
3207
3208             /* Advance leftover_start past any newlines we find,
3209                so only partial lines can get reparsed */
3210             if (looking_at(buf, &i, "\n")) {
3211                 prevColor = curColor;
3212                 if (curColor != ColorNormal) {
3213                     if (oldi > next_out) {
3214                         SendToPlayer(&buf[next_out], oldi - next_out);
3215                         next_out = oldi;
3216                     }
3217                     Colorize(ColorNormal, FALSE);
3218                     curColor = ColorNormal;
3219                 }
3220                 if (started == STARTED_BOARD) {
3221                     started = STARTED_NONE;
3222                     parse[parse_pos] = NULLCHAR;
3223                     ParseBoard12(parse);
3224                     ics_user_moved = 0;
3225
3226                     /* Send premove here */
3227                     if (appData.premove) {
3228                       char str[MSG_SIZ];
3229                       if (currentMove == 0 &&
3230                           gameMode == IcsPlayingWhite &&
3231                           appData.premoveWhite) {
3232                         sprintf(str, "%s%s\n", ics_prefix,
3233                                 appData.premoveWhiteText);
3234                         if (appData.debugMode)
3235                           fprintf(debugFP, "Sending premove:\n");
3236                         SendToICS(str);
3237                       } else if (currentMove == 1 &&
3238                                  gameMode == IcsPlayingBlack &&
3239                                  appData.premoveBlack) {
3240                         sprintf(str, "%s%s\n", ics_prefix,
3241                                 appData.premoveBlackText);
3242                         if (appData.debugMode)
3243                           fprintf(debugFP, "Sending premove:\n");
3244                         SendToICS(str);
3245                       } else if (gotPremove) {
3246                         gotPremove = 0;
3247                         ClearPremoveHighlights();
3248                         if (appData.debugMode)
3249                           fprintf(debugFP, "Sending premove:\n");
3250                           UserMoveEvent(premoveFromX, premoveFromY, 
3251                                         premoveToX, premoveToY, 
3252                                         premovePromoChar);
3253                       }
3254                     }
3255
3256                     /* Usually suppress following prompt */
3257                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3258                         if (looking_at(buf, &i, "*% ")) {
3259                             savingComment = FALSE;
3260                         }
3261                     }
3262                     next_out = i;
3263                 } else if (started == STARTED_HOLDINGS) {
3264                     int gamenum;
3265                     char new_piece[MSG_SIZ];
3266                     started = STARTED_NONE;
3267                     parse[parse_pos] = NULLCHAR;
3268                     if (appData.debugMode)
3269                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3270                                                         parse, currentMove);
3271                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3272                         gamenum == ics_gamenum) {
3273                         if (gameInfo.variant == VariantNormal) {
3274                           /* [HGM] We seem to switch variant during a game!
3275                            * Presumably no holdings were displayed, so we have
3276                            * to move the position two files to the right to
3277                            * create room for them!
3278                            */
3279                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3280                           /* Get a move list just to see the header, which
3281                              will tell us whether this is really bug or zh */
3282                           if (ics_getting_history == H_FALSE) {
3283                             ics_getting_history = H_REQUESTED;
3284                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3285                             SendToICS(str);
3286                           }
3287                         }
3288                         new_piece[0] = NULLCHAR;
3289                         sscanf(parse, "game %d white [%s black [%s <- %s",
3290                                &gamenum, white_holding, black_holding,
3291                                new_piece);
3292                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3293                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3294                         /* [HGM] copy holdings to board holdings area */
3295                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3296                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3297 #if ZIPPY
3298                         if (appData.zippyPlay && first.initDone) {
3299                             ZippyHoldings(white_holding, black_holding,
3300                                           new_piece);
3301                         }
3302 #endif /*ZIPPY*/
3303                         if (tinyLayout || smallLayout) {
3304                             char wh[16], bh[16];
3305                             PackHolding(wh, white_holding);
3306                             PackHolding(bh, black_holding);
3307                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3308                                     gameInfo.white, gameInfo.black);
3309                         } else {
3310                             sprintf(str, "%s [%s] vs. %s [%s]",
3311                                     gameInfo.white, white_holding,
3312                                     gameInfo.black, black_holding);
3313                         }
3314
3315                         DrawPosition(FALSE, boards[currentMove]);
3316                         DisplayTitle(str);
3317                     }
3318                     /* Suppress following prompt */
3319                     if (looking_at(buf, &i, "*% ")) {
3320                         savingComment = FALSE;
3321                     }
3322                     next_out = i;
3323                 }
3324                 continue;
3325             }
3326
3327             i++;                /* skip unparsed character and loop back */
3328         }
3329         
3330         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3331             started != STARTED_HOLDINGS && i > next_out) {
3332             SendToPlayer(&buf[next_out], i - next_out);
3333             next_out = i;
3334         }
3335         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3336         
3337         leftover_len = buf_len - leftover_start;
3338         /* if buffer ends with something we couldn't parse,
3339            reparse it after appending the next read */
3340         
3341     } else if (count == 0) {
3342         RemoveInputSource(isr);
3343         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3344     } else {
3345         DisplayFatalError(_("Error reading from ICS"), error, 1);
3346     }
3347 }
3348
3349
3350 /* Board style 12 looks like this:
3351    
3352    <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
3353    
3354  * The "<12> " is stripped before it gets to this routine.  The two
3355  * trailing 0's (flip state and clock ticking) are later addition, and
3356  * some chess servers may not have them, or may have only the first.
3357  * Additional trailing fields may be added in the future.  
3358  */
3359
3360 #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"
3361
3362 #define RELATION_OBSERVING_PLAYED    0
3363 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3364 #define RELATION_PLAYING_MYMOVE      1
3365 #define RELATION_PLAYING_NOTMYMOVE  -1
3366 #define RELATION_EXAMINING           2
3367 #define RELATION_ISOLATED_BOARD     -3
3368 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3369
3370 void
3371 ParseBoard12(string)
3372      char *string;
3373
3374     GameMode newGameMode;
3375     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3376     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3377     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3378     char to_play, board_chars[200];
3379     char move_str[500], str[500], elapsed_time[500];
3380     char black[32], white[32];
3381     Board board;
3382     int prevMove = currentMove;
3383     int ticking = 2;
3384     ChessMove moveType;
3385     int fromX, fromY, toX, toY;
3386     char promoChar;
3387     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3388     char *bookHit = NULL; // [HGM] book
3389
3390     fromX = fromY = toX = toY = -1;
3391     
3392     newGame = FALSE;
3393
3394     if (appData.debugMode)
3395       fprintf(debugFP, _("Parsing board: %s\n"), string);
3396
3397     move_str[0] = NULLCHAR;
3398     elapsed_time[0] = NULLCHAR;
3399     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3400         int  i = 0, j;
3401         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3402             if(string[i] == ' ') { ranks++; files = 0; }
3403             else files++;
3404             i++;
3405         }
3406         for(j = 0; j <i; j++) board_chars[j] = string[j];
3407         board_chars[i] = '\0';
3408         string += i + 1;
3409     }
3410     n = sscanf(string, PATTERN, &to_play, &double_push,
3411                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3412                &gamenum, white, black, &relation, &basetime, &increment,
3413                &white_stren, &black_stren, &white_time, &black_time,
3414                &moveNum, str, elapsed_time, move_str, &ics_flip,
3415                &ticking);
3416
3417     if (n < 21) {
3418         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3419         DisplayError(str, 0);
3420         return;
3421     }
3422
3423     /* Convert the move number to internal form */
3424     moveNum = (moveNum - 1) * 2;
3425     if (to_play == 'B') moveNum++;
3426     if (moveNum >= MAX_MOVES) {
3427       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3428                         0, 1);
3429       return;
3430     }
3431     
3432     switch (relation) {
3433       case RELATION_OBSERVING_PLAYED:
3434       case RELATION_OBSERVING_STATIC:
3435         if (gamenum == -1) {
3436             /* Old ICC buglet */
3437             relation = RELATION_OBSERVING_STATIC;
3438         }
3439         newGameMode = IcsObserving;
3440         break;
3441       case RELATION_PLAYING_MYMOVE:
3442       case RELATION_PLAYING_NOTMYMOVE:
3443         newGameMode =
3444           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3445             IcsPlayingWhite : IcsPlayingBlack;
3446         break;
3447       case RELATION_EXAMINING:
3448         newGameMode = IcsExamining;
3449         break;
3450       case RELATION_ISOLATED_BOARD:
3451       default:
3452         /* Just display this board.  If user was doing something else,
3453            we will forget about it until the next board comes. */ 
3454         newGameMode = IcsIdle;
3455         break;
3456       case RELATION_STARTING_POSITION:
3457         newGameMode = gameMode;
3458         break;
3459     }
3460     
3461     /* Modify behavior for initial board display on move listing
3462        of wild games.
3463        */
3464     switch (ics_getting_history) {
3465       case H_FALSE:
3466       case H_REQUESTED:
3467         break;
3468       case H_GOT_REQ_HEADER:
3469       case H_GOT_UNREQ_HEADER:
3470         /* This is the initial position of the current game */
3471         gamenum = ics_gamenum;
3472         moveNum = 0;            /* old ICS bug workaround */
3473         if (to_play == 'B') {
3474           startedFromSetupPosition = TRUE;
3475           blackPlaysFirst = TRUE;
3476           moveNum = 1;
3477           if (forwardMostMove == 0) forwardMostMove = 1;
3478           if (backwardMostMove == 0) backwardMostMove = 1;
3479           if (currentMove == 0) currentMove = 1;
3480         }
3481         newGameMode = gameMode;
3482         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3483         break;
3484       case H_GOT_UNWANTED_HEADER:
3485         /* This is an initial board that we don't want */
3486         return;
3487       case H_GETTING_MOVES:
3488         /* Should not happen */
3489         DisplayError(_("Error gathering move list: extra board"), 0);
3490         ics_getting_history = H_FALSE;
3491         return;
3492     }
3493     
3494     /* Take action if this is the first board of a new game, or of a
3495        different game than is currently being displayed.  */
3496     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3497         relation == RELATION_ISOLATED_BOARD) {
3498         
3499         /* Forget the old game and get the history (if any) of the new one */
3500         if (gameMode != BeginningOfGame) {
3501           Reset(FALSE, TRUE);
3502         }
3503         newGame = TRUE;
3504         if (appData.autoRaiseBoard) BoardToTop();
3505         prevMove = -3;
3506         if (gamenum == -1) {
3507             newGameMode = IcsIdle;
3508         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3509                    appData.getMoveList) {
3510             /* Need to get game history */
3511             ics_getting_history = H_REQUESTED;
3512             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3513             SendToICS(str);
3514         }
3515         
3516         /* Initially flip the board to have black on the bottom if playing
3517            black or if the ICS flip flag is set, but let the user change
3518            it with the Flip View button. */
3519         flipView = appData.autoFlipView ? 
3520           (newGameMode == IcsPlayingBlack) || ics_flip :
3521           appData.flipView;
3522         
3523         /* Done with values from previous mode; copy in new ones */
3524         gameMode = newGameMode;
3525         ModeHighlight();
3526         ics_gamenum = gamenum;
3527         if (gamenum == gs_gamenum) {
3528             int klen = strlen(gs_kind);
3529             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3530             sprintf(str, "ICS %s", gs_kind);
3531             gameInfo.event = StrSave(str);
3532         } else {
3533             gameInfo.event = StrSave("ICS game");
3534         }
3535         gameInfo.site = StrSave(appData.icsHost);
3536         gameInfo.date = PGNDate();
3537         gameInfo.round = StrSave("-");
3538         gameInfo.white = StrSave(white);
3539         gameInfo.black = StrSave(black);
3540         timeControl = basetime * 60 * 1000;
3541         timeControl_2 = 0;
3542         timeIncrement = increment * 1000;
3543         movesPerSession = 0;
3544         gameInfo.timeControl = TimeControlTagValue();
3545         VariantSwitch(board, StringToVariant(gameInfo.event) );
3546   if (appData.debugMode) {
3547     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3548     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3549     setbuf(debugFP, NULL);
3550   }
3551
3552         gameInfo.outOfBook = NULL;
3553         
3554         /* Do we have the ratings? */
3555         if (strcmp(player1Name, white) == 0 &&
3556             strcmp(player2Name, black) == 0) {
3557             if (appData.debugMode)
3558               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3559                       player1Rating, player2Rating);
3560             gameInfo.whiteRating = player1Rating;
3561             gameInfo.blackRating = player2Rating;
3562         } else if (strcmp(player2Name, white) == 0 &&
3563                    strcmp(player1Name, black) == 0) {
3564             if (appData.debugMode)
3565               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3566                       player2Rating, player1Rating);
3567             gameInfo.whiteRating = player2Rating;
3568             gameInfo.blackRating = player1Rating;
3569         }
3570         player1Name[0] = player2Name[0] = NULLCHAR;
3571
3572         /* Silence shouts if requested */
3573         if (appData.quietPlay &&
3574             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3575             SendToICS(ics_prefix);
3576             SendToICS("set shout 0\n");
3577         }
3578     }
3579     
3580     /* Deal with midgame name changes */
3581     if (!newGame) {
3582         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3583             if (gameInfo.white) free(gameInfo.white);
3584             gameInfo.white = StrSave(white);
3585         }
3586         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3587             if (gameInfo.black) free(gameInfo.black);
3588             gameInfo.black = StrSave(black);
3589         }
3590     }
3591     
3592     /* Throw away game result if anything actually changes in examine mode */
3593     if (gameMode == IcsExamining && !newGame) {
3594         gameInfo.result = GameUnfinished;
3595         if (gameInfo.resultDetails != NULL) {
3596             free(gameInfo.resultDetails);
3597             gameInfo.resultDetails = NULL;
3598         }
3599     }
3600     
3601     /* In pausing && IcsExamining mode, we ignore boards coming
3602        in if they are in a different variation than we are. */
3603     if (pauseExamInvalid) return;
3604     if (pausing && gameMode == IcsExamining) {
3605         if (moveNum <= pauseExamForwardMostMove) {
3606             pauseExamInvalid = TRUE;
3607             forwardMostMove = pauseExamForwardMostMove;
3608             return;
3609         }
3610     }
3611     
3612   if (appData.debugMode) {
3613     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3614   }
3615     /* Parse the board */
3616     for (k = 0; k < ranks; k++) {
3617       for (j = 0; j < files; j++)
3618         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3619       if(gameInfo.holdingsWidth > 1) {
3620            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3621            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3622       }
3623     }
3624     CopyBoard(boards[moveNum], board);
3625     if (moveNum == 0) {
3626         startedFromSetupPosition =
3627           !CompareBoards(board, initialPosition);
3628         if(startedFromSetupPosition)
3629             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3630     }
3631
3632     /* [HGM] Set castling rights. Take the outermost Rooks,
3633        to make it also work for FRC opening positions. Note that board12
3634        is really defective for later FRC positions, as it has no way to
3635        indicate which Rook can castle if they are on the same side of King.
3636        For the initial position we grant rights to the outermost Rooks,
3637        and remember thos rights, and we then copy them on positions
3638        later in an FRC game. This means WB might not recognize castlings with
3639        Rooks that have moved back to their original position as illegal,
3640        but in ICS mode that is not its job anyway.
3641     */
3642     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3643     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3644
3645         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3646             if(board[0][i] == WhiteRook) j = i;
3647         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3648         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3649             if(board[0][i] == WhiteRook) j = i;
3650         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3651         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3652             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3653         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3654         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3655             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3656         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3657
3658         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3659         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3660             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3661         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3662             if(board[BOARD_HEIGHT-1][k] == bKing)
3663                 initialRights[5] = castlingRights[moveNum][5] = k;
3664     } else { int r;
3665         r = castlingRights[moveNum][0] = initialRights[0];
3666         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3667         r = castlingRights[moveNum][1] = initialRights[1];
3668         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3669         r = castlingRights[moveNum][3] = initialRights[3];
3670         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3671         r = castlingRights[moveNum][4] = initialRights[4];
3672         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3673         /* wildcastle kludge: always assume King has rights */
3674         r = castlingRights[moveNum][2] = initialRights[2];
3675         r = castlingRights[moveNum][5] = initialRights[5];
3676     }
3677     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3678     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3679
3680     
3681     if (ics_getting_history == H_GOT_REQ_HEADER ||
3682         ics_getting_history == H_GOT_UNREQ_HEADER) {
3683         /* This was an initial position from a move list, not
3684            the current position */
3685         return;
3686     }
3687     
3688     /* Update currentMove and known move number limits */
3689     newMove = newGame || moveNum > forwardMostMove;
3690
3691     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3692     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3693         takeback = forwardMostMove - moveNum;
3694         for (i = 0; i < takeback; i++) {
3695              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3696              SendToProgram("undo\n", &first);
3697         }
3698     }
3699
3700     if (newGame) {
3701         forwardMostMove = backwardMostMove = currentMove = moveNum;
3702         if (gameMode == IcsExamining && moveNum == 0) {
3703           /* Workaround for ICS limitation: we are not told the wild
3704              type when starting to examine a game.  But if we ask for
3705              the move list, the move list header will tell us */
3706             ics_getting_history = H_REQUESTED;
3707             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3708             SendToICS(str);
3709         }
3710     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3711                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3712         forwardMostMove = moveNum;
3713         if (!pausing || currentMove > forwardMostMove)
3714           currentMove = forwardMostMove;
3715     } else {
3716         /* New part of history that is not contiguous with old part */ 
3717         if (pausing && gameMode == IcsExamining) {
3718             pauseExamInvalid = TRUE;
3719             forwardMostMove = pauseExamForwardMostMove;
3720             return;
3721         }
3722         forwardMostMove = backwardMostMove = currentMove = moveNum;
3723         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3724             ics_getting_history = H_REQUESTED;
3725             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3726             SendToICS(str);
3727         }
3728     }
3729     
3730     /* Update the clocks */
3731     if (strchr(elapsed_time, '.')) {
3732       /* Time is in ms */
3733       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3734       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3735     } else {
3736       /* Time is in seconds */
3737       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3738       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3739     }
3740       
3741
3742 #if ZIPPY
3743     if (appData.zippyPlay && newGame &&
3744         gameMode != IcsObserving && gameMode != IcsIdle &&
3745         gameMode != IcsExamining)
3746       ZippyFirstBoard(moveNum, basetime, increment);
3747 #endif
3748     
3749     /* Put the move on the move list, first converting
3750        to canonical algebraic form. */
3751     if (moveNum > 0) {
3752   if (appData.debugMode) {
3753     if (appData.debugMode) { int f = forwardMostMove;
3754         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3755                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3756     }
3757     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3758     fprintf(debugFP, "moveNum = %d\n", moveNum);
3759     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3760     setbuf(debugFP, NULL);
3761   }
3762         if (moveNum <= backwardMostMove) {
3763             /* We don't know what the board looked like before
3764                this move.  Punt. */
3765             strcpy(parseList[moveNum - 1], move_str);
3766             strcat(parseList[moveNum - 1], " ");
3767             strcat(parseList[moveNum - 1], elapsed_time);
3768             moveList[moveNum - 1][0] = NULLCHAR;
3769         } else if (strcmp(move_str, "none") == 0) {
3770             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3771             /* Again, we don't know what the board looked like;
3772                this is really the start of the game. */
3773             parseList[moveNum - 1][0] = NULLCHAR;
3774             moveList[moveNum - 1][0] = NULLCHAR;
3775             backwardMostMove = moveNum;
3776             startedFromSetupPosition = TRUE;
3777             fromX = fromY = toX = toY = -1;
3778         } else {
3779           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3780           //                 So we parse the long-algebraic move string in stead of the SAN move
3781           int valid; char buf[MSG_SIZ], *prom;
3782
3783           // str looks something like "Q/a1-a2"; kill the slash
3784           if(str[1] == '/') 
3785                 sprintf(buf, "%c%s", str[0], str+2);
3786           else  strcpy(buf, str); // might be castling
3787           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3788                 strcat(buf, prom); // long move lacks promo specification!
3789           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3790                 if(appData.debugMode) 
3791                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3792                 strcpy(move_str, buf);
3793           }
3794           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3795                                 &fromX, &fromY, &toX, &toY, &promoChar)
3796                || ParseOneMove(buf, moveNum - 1, &moveType,
3797                                 &fromX, &fromY, &toX, &toY, &promoChar);
3798           // end of long SAN patch
3799           if (valid) {
3800             (void) CoordsToAlgebraic(boards[moveNum - 1],
3801                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3802                                      fromY, fromX, toY, toX, promoChar,
3803                                      parseList[moveNum-1]);
3804             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3805                              castlingRights[moveNum]) ) {
3806               case MT_NONE:
3807               case MT_STALEMATE:
3808               default:
3809                 break;
3810               case MT_CHECK:
3811                 if(gameInfo.variant != VariantShogi)
3812                     strcat(parseList[moveNum - 1], "+");
3813                 break;
3814               case MT_CHECKMATE:
3815               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3816                 strcat(parseList[moveNum - 1], "#");
3817                 break;
3818             }
3819             strcat(parseList[moveNum - 1], " ");
3820             strcat(parseList[moveNum - 1], elapsed_time);
3821             /* currentMoveString is set as a side-effect of ParseOneMove */
3822             strcpy(moveList[moveNum - 1], currentMoveString);
3823             strcat(moveList[moveNum - 1], "\n");
3824           } else {
3825             /* Move from ICS was illegal!?  Punt. */
3826   if (appData.debugMode) {
3827     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3828     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3829   }
3830 #if 0
3831             if (appData.testLegality && appData.debugMode) {
3832                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3833                 DisplayError(str, 0);
3834             }
3835 #endif
3836             strcpy(parseList[moveNum - 1], move_str);
3837             strcat(parseList[moveNum - 1], " ");
3838             strcat(parseList[moveNum - 1], elapsed_time);
3839             moveList[moveNum - 1][0] = NULLCHAR;
3840             fromX = fromY = toX = toY = -1;
3841           }
3842         }
3843   if (appData.debugMode) {
3844     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3845     setbuf(debugFP, NULL);
3846   }
3847
3848 #if ZIPPY
3849         /* Send move to chess program (BEFORE animating it). */
3850         if (appData.zippyPlay && !newGame && newMove && 
3851            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3852
3853             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3854                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3855                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3856                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3857                             move_str);
3858                     DisplayError(str, 0);
3859                 } else {
3860                     if (first.sendTime) {
3861                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3862                     }
3863                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3864                     if (firstMove && !bookHit) {
3865                         firstMove = FALSE;
3866                         if (first.useColors) {
3867                           SendToProgram(gameMode == IcsPlayingWhite ?
3868                                         "white\ngo\n" :
3869                                         "black\ngo\n", &first);
3870                         } else {
3871                           SendToProgram("go\n", &first);
3872                         }
3873                         first.maybeThinking = TRUE;
3874                     }
3875                 }
3876             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3877               if (moveList[moveNum - 1][0] == NULLCHAR) {
3878                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3879                 DisplayError(str, 0);
3880               } else {
3881                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3882                 SendMoveToProgram(moveNum - 1, &first);
3883               }
3884             }
3885         }
3886 #endif
3887     }
3888
3889     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3890         /* If move comes from a remote source, animate it.  If it
3891            isn't remote, it will have already been animated. */
3892         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3893             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3894         }
3895         if (!pausing && appData.highlightLastMove) {
3896             SetHighlights(fromX, fromY, toX, toY);
3897         }
3898     }
3899     
3900     /* Start the clocks */
3901     whiteFlag = blackFlag = FALSE;
3902     appData.clockMode = !(basetime == 0 && increment == 0);
3903     if (ticking == 0) {
3904       ics_clock_paused = TRUE;
3905       StopClocks();
3906     } else if (ticking == 1) {
3907       ics_clock_paused = FALSE;
3908     }
3909     if (gameMode == IcsIdle ||
3910         relation == RELATION_OBSERVING_STATIC ||
3911         relation == RELATION_EXAMINING ||
3912         ics_clock_paused)
3913       DisplayBothClocks();
3914     else
3915       StartClocks();
3916     
3917     /* Display opponents and material strengths */
3918     if (gameInfo.variant != VariantBughouse &&
3919         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3920         if (tinyLayout || smallLayout) {
3921             if(gameInfo.variant == VariantNormal)
3922                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3923                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3924                     basetime, increment);
3925             else
3926                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3927                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3928                     basetime, increment, (int) gameInfo.variant);
3929         } else {
3930             if(gameInfo.variant == VariantNormal)
3931                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3932                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3933                     basetime, increment);
3934             else
3935                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3936                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3937                     basetime, increment, VariantName(gameInfo.variant));
3938         }
3939         DisplayTitle(str);
3940   if (appData.debugMode) {
3941     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3942   }
3943     }
3944
3945    
3946     /* Display the board */
3947     if (!pausing && !appData.noGUI) {
3948       
3949       if (appData.premove)
3950           if (!gotPremove || 
3951              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3952              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3953               ClearPremoveHighlights();
3954
3955       DrawPosition(FALSE, boards[currentMove]);
3956       DisplayMove(moveNum - 1);
3957       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3958             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3959               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3960         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3961       }
3962     }
3963
3964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3965 #if ZIPPY
3966     if(bookHit) { // [HGM] book: simulate book reply
3967         static char bookMove[MSG_SIZ]; // a bit generous?
3968
3969         programStats.nodes = programStats.depth = programStats.time = 
3970         programStats.score = programStats.got_only_move = 0;
3971         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3972
3973         strcpy(bookMove, "move ");
3974         strcat(bookMove, bookHit);
3975         HandleMachineMove(bookMove, &first);
3976     }
3977 #endif
3978 }
3979
3980 void
3981 GetMoveListEvent()
3982 {
3983     char buf[MSG_SIZ];
3984     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3985         ics_getting_history = H_REQUESTED;
3986         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3987         SendToICS(buf);
3988     }
3989 }
3990
3991 void
3992 AnalysisPeriodicEvent(force)
3993      int force;
3994 {
3995     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3996          && !force) || !appData.periodicUpdates)
3997       return;
3998
3999     /* Send . command to Crafty to collect stats */
4000     SendToProgram(".\n", &first);
4001
4002     /* Don't send another until we get a response (this makes
4003        us stop sending to old Crafty's which don't understand
4004        the "." command (sending illegal cmds resets node count & time,
4005        which looks bad)) */
4006     programStats.ok_to_send = 0;
4007 }
4008
4009 void
4010 SendMoveToProgram(moveNum, cps)
4011      int moveNum;
4012      ChessProgramState *cps;
4013 {
4014     char buf[MSG_SIZ];
4015
4016     if (cps->useUsermove) {
4017       SendToProgram("usermove ", cps);
4018     }
4019     if (cps->useSAN) {
4020       char *space;
4021       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4022         int len = space - parseList[moveNum];
4023         memcpy(buf, parseList[moveNum], len);
4024         buf[len++] = '\n';
4025         buf[len] = NULLCHAR;
4026       } else {
4027         sprintf(buf, "%s\n", parseList[moveNum]);
4028       }
4029       SendToProgram(buf, cps);
4030     } else {
4031       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4032         AlphaRank(moveList[moveNum], 4);
4033         SendToProgram(moveList[moveNum], cps);
4034         AlphaRank(moveList[moveNum], 4); // and back
4035       } else
4036       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4037        * the engine. It would be nice to have a better way to identify castle 
4038        * moves here. */
4039       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4040                                                                          && cps->useOOCastle) {
4041         int fromX = moveList[moveNum][0] - AAA; 
4042         int fromY = moveList[moveNum][1] - ONE;
4043         int toX = moveList[moveNum][2] - AAA; 
4044         int toY = moveList[moveNum][3] - ONE;
4045         if((boards[moveNum][fromY][fromX] == WhiteKing 
4046             && boards[moveNum][toY][toX] == WhiteRook)
4047            || (boards[moveNum][fromY][fromX] == BlackKing 
4048                && boards[moveNum][toY][toX] == BlackRook)) {
4049           if(toX > fromX) SendToProgram("O-O\n", cps);
4050           else SendToProgram("O-O-O\n", cps);
4051         }
4052         else SendToProgram(moveList[moveNum], cps);
4053       }
4054       else SendToProgram(moveList[moveNum], cps);
4055       /* End of additions by Tord */
4056     }
4057
4058     /* [HGM] setting up the opening has brought engine in force mode! */
4059     /*       Send 'go' if we are in a mode where machine should play. */
4060     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4061         (gameMode == TwoMachinesPlay   ||
4062 #ifdef ZIPPY
4063          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4064 #endif
4065          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4066         SendToProgram("go\n", cps);
4067   if (appData.debugMode) {
4068     fprintf(debugFP, "(extra)\n");
4069   }
4070     }
4071     setboardSpoiledMachineBlack = 0;
4072 }
4073
4074 void
4075 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4076      ChessMove moveType;
4077      int fromX, fromY, toX, toY;
4078 {
4079     char user_move[MSG_SIZ];
4080
4081     switch (moveType) {
4082       default:
4083         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4084                 (int)moveType, fromX, fromY, toX, toY);
4085         DisplayError(user_move + strlen("say "), 0);
4086         break;
4087       case WhiteKingSideCastle:
4088       case BlackKingSideCastle:
4089       case WhiteQueenSideCastleWild:
4090       case BlackQueenSideCastleWild:
4091       /* PUSH Fabien */
4092       case WhiteHSideCastleFR:
4093       case BlackHSideCastleFR:
4094       /* POP Fabien */
4095         sprintf(user_move, "o-o\n");
4096         break;
4097       case WhiteQueenSideCastle:
4098       case BlackQueenSideCastle:
4099       case WhiteKingSideCastleWild:
4100       case BlackKingSideCastleWild:
4101       /* PUSH Fabien */
4102       case WhiteASideCastleFR:
4103       case BlackASideCastleFR:
4104       /* POP Fabien */
4105         sprintf(user_move, "o-o-o\n");
4106         break;
4107       case WhitePromotionQueen:
4108       case BlackPromotionQueen:
4109       case WhitePromotionRook:
4110       case BlackPromotionRook:
4111       case WhitePromotionBishop:
4112       case BlackPromotionBishop:
4113       case WhitePromotionKnight:
4114       case BlackPromotionKnight:
4115       case WhitePromotionKing:
4116       case BlackPromotionKing:
4117       case WhitePromotionChancellor:
4118       case BlackPromotionChancellor:
4119       case WhitePromotionArchbishop:
4120       case BlackPromotionArchbishop:
4121         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4122             sprintf(user_move, "%c%c%c%c=%c\n",
4123                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4124                 PieceToChar(WhiteFerz));
4125         else if(gameInfo.variant == VariantGreat)
4126             sprintf(user_move, "%c%c%c%c=%c\n",
4127                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4128                 PieceToChar(WhiteMan));
4129         else
4130             sprintf(user_move, "%c%c%c%c=%c\n",
4131                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4132                 PieceToChar(PromoPiece(moveType)));
4133         break;
4134       case WhiteDrop:
4135       case BlackDrop:
4136         sprintf(user_move, "%c@%c%c\n",
4137                 ToUpper(PieceToChar((ChessSquare) fromX)),
4138                 AAA + toX, ONE + toY);
4139         break;
4140       case NormalMove:
4141       case WhiteCapturesEnPassant:
4142       case BlackCapturesEnPassant:
4143       case IllegalMove:  /* could be a variant we don't quite understand */
4144         sprintf(user_move, "%c%c%c%c\n",
4145                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4146         break;
4147     }
4148     SendToICS(user_move);
4149     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4150         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4151 }
4152
4153 void
4154 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4155      int rf, ff, rt, ft;
4156      char promoChar;
4157      char move[7];
4158 {
4159     if (rf == DROP_RANK) {
4160         sprintf(move, "%c@%c%c\n",
4161                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4162     } else {
4163         if (promoChar == 'x' || promoChar == NULLCHAR) {
4164             sprintf(move, "%c%c%c%c\n",
4165                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4166         } else {
4167             sprintf(move, "%c%c%c%c%c\n",
4168                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4169         }
4170     }
4171 }
4172
4173 void
4174 ProcessICSInitScript(f)
4175      FILE *f;
4176 {
4177     char buf[MSG_SIZ];
4178
4179     while (fgets(buf, MSG_SIZ, f)) {
4180         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4181     }
4182
4183     fclose(f);
4184 }
4185
4186
4187 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4188 void
4189 AlphaRank(char *move, int n)
4190 {
4191 //    char *p = move, c; int x, y;
4192
4193     if (appData.debugMode) {
4194         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4195     }
4196
4197     if(move[1]=='*' && 
4198        move[2]>='0' && move[2]<='9' &&
4199        move[3]>='a' && move[3]<='x'    ) {
4200         move[1] = '@';
4201         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4202         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4203     } else
4204     if(move[0]>='0' && move[0]<='9' &&
4205        move[1]>='a' && move[1]<='x' &&
4206        move[2]>='0' && move[2]<='9' &&
4207        move[3]>='a' && move[3]<='x'    ) {
4208         /* input move, Shogi -> normal */
4209         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4210         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4211         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4212         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4213     } else
4214     if(move[1]=='@' &&
4215        move[3]>='0' && move[3]<='9' &&
4216        move[2]>='a' && move[2]<='x'    ) {
4217         move[1] = '*';
4218         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4219         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4220     } else
4221     if(
4222        move[0]>='a' && move[0]<='x' &&
4223        move[3]>='0' && move[3]<='9' &&
4224        move[2]>='a' && move[2]<='x'    ) {
4225          /* output move, normal -> Shogi */
4226         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4227         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4228         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4229         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4230         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4231     }
4232     if (appData.debugMode) {
4233         fprintf(debugFP, "   out = '%s'\n", move);
4234     }
4235 }
4236
4237 /* Parser for moves from gnuchess, ICS, or user typein box */
4238 Boolean
4239 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4240      char *move;
4241      int moveNum;
4242      ChessMove *moveType;
4243      int *fromX, *fromY, *toX, *toY;
4244      char *promoChar;
4245 {       
4246     if (appData.debugMode) {
4247         fprintf(debugFP, "move to parse: %s\n", move);
4248     }
4249     *moveType = yylexstr(moveNum, move);
4250
4251     switch (*moveType) {
4252       case WhitePromotionChancellor:
4253       case BlackPromotionChancellor:
4254       case WhitePromotionArchbishop:
4255       case BlackPromotionArchbishop:
4256       case WhitePromotionQueen:
4257       case BlackPromotionQueen:
4258       case WhitePromotionRook:
4259       case BlackPromotionRook:
4260       case WhitePromotionBishop:
4261       case BlackPromotionBishop:
4262       case WhitePromotionKnight:
4263       case BlackPromotionKnight:
4264       case WhitePromotionKing:
4265       case BlackPromotionKing:
4266       case NormalMove:
4267       case WhiteCapturesEnPassant:
4268       case BlackCapturesEnPassant:
4269       case WhiteKingSideCastle:
4270       case WhiteQueenSideCastle:
4271       case BlackKingSideCastle:
4272       case BlackQueenSideCastle:
4273       case WhiteKingSideCastleWild:
4274       case WhiteQueenSideCastleWild:
4275       case BlackKingSideCastleWild:
4276       case BlackQueenSideCastleWild:
4277       /* Code added by Tord: */
4278       case WhiteHSideCastleFR:
4279       case WhiteASideCastleFR:
4280       case BlackHSideCastleFR:
4281       case BlackASideCastleFR:
4282       /* End of code added by Tord */
4283       case IllegalMove:         /* bug or odd chess variant */
4284         *fromX = currentMoveString[0] - AAA;
4285         *fromY = currentMoveString[1] - ONE;
4286         *toX = currentMoveString[2] - AAA;
4287         *toY = currentMoveString[3] - ONE;
4288         *promoChar = currentMoveString[4];
4289         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4290             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4291     if (appData.debugMode) {
4292         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4293     }
4294             *fromX = *fromY = *toX = *toY = 0;
4295             return FALSE;
4296         }
4297         if (appData.testLegality) {
4298           return (*moveType != IllegalMove);
4299         } else {
4300           return !(fromX == fromY && toX == toY);
4301         }
4302
4303       case WhiteDrop:
4304       case BlackDrop:
4305         *fromX = *moveType == WhiteDrop ?
4306           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4307           (int) CharToPiece(ToLower(currentMoveString[0]));
4308         *fromY = DROP_RANK;
4309         *toX = currentMoveString[2] - AAA;
4310         *toY = currentMoveString[3] - ONE;
4311         *promoChar = NULLCHAR;
4312         return TRUE;
4313
4314       case AmbiguousMove:
4315       case ImpossibleMove:
4316       case (ChessMove) 0:       /* end of file */
4317       case ElapsedTime:
4318       case Comment:
4319       case PGNTag:
4320       case NAG:
4321       case WhiteWins:
4322       case BlackWins:
4323       case GameIsDrawn:
4324       default:
4325     if (appData.debugMode) {
4326         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4327     }
4328         /* bug? */
4329         *fromX = *fromY = *toX = *toY = 0;
4330         *promoChar = NULLCHAR;
4331         return FALSE;
4332     }
4333 }
4334
4335 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4336 // All positions will have equal probability, but the current method will not provide a unique
4337 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4338 #define DARK 1
4339 #define LITE 2
4340 #define ANY 3
4341
4342 int squaresLeft[4];
4343 int piecesLeft[(int)BlackPawn];
4344 int seed, nrOfShuffles;
4345
4346 void GetPositionNumber()
4347 {       // sets global variable seed
4348         int i;
4349
4350         seed = appData.defaultFrcPosition;
4351         if(seed < 0) { // randomize based on time for negative FRC position numbers
4352                 for(i=0; i<50; i++) seed += random();
4353                 seed = random() ^ random() >> 8 ^ random() << 8;
4354                 if(seed<0) seed = -seed;
4355         }
4356 }
4357
4358 int put(Board board, int pieceType, int rank, int n, int shade)
4359 // put the piece on the (n-1)-th empty squares of the given shade
4360 {
4361         int i;
4362
4363         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4364                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4365                         board[rank][i] = (ChessSquare) pieceType;
4366                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4367                         squaresLeft[ANY]--;
4368                         piecesLeft[pieceType]--; 
4369                         return i;
4370                 }
4371         }
4372         return -1;
4373 }
4374
4375
4376 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4377 // calculate where the next piece goes, (any empty square), and put it there
4378 {
4379         int i;
4380
4381         i = seed % squaresLeft[shade];
4382         nrOfShuffles *= squaresLeft[shade];
4383         seed /= squaresLeft[shade];
4384         put(board, pieceType, rank, i, shade);
4385 }
4386
4387 void AddTwoPieces(Board board, int pieceType, int rank)
4388 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4389 {
4390         int i, n=squaresLeft[ANY], j=n-1, k;
4391
4392         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4393         i = seed % k;  // pick one
4394         nrOfShuffles *= k;
4395         seed /= k;
4396         while(i >= j) i -= j--;
4397         j = n - 1 - j; i += j;
4398         put(board, pieceType, rank, j, ANY);
4399         put(board, pieceType, rank, i, ANY);
4400 }
4401
4402 void SetUpShuffle(Board board, int number)
4403 {
4404         int i, p, first=1;
4405
4406         GetPositionNumber(); nrOfShuffles = 1;
4407
4408         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4409         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4410         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4411
4412         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4413
4414         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4415             p = (int) board[0][i];
4416             if(p < (int) BlackPawn) piecesLeft[p] ++;
4417             board[0][i] = EmptySquare;
4418         }
4419
4420         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4421             // shuffles restricted to allow normal castling put KRR first
4422             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4423                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4424             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4425                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4426             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4427                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4428             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4429                 put(board, WhiteRook, 0, 0, ANY);
4430             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4431         }
4432
4433         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4434             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4435             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4436                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4437                 while(piecesLeft[p] >= 2) {
4438                     AddOnePiece(board, p, 0, LITE);
4439                     AddOnePiece(board, p, 0, DARK);
4440                 }
4441                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4442             }
4443
4444         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4445             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4446             // but we leave King and Rooks for last, to possibly obey FRC restriction
4447             if(p == (int)WhiteRook) continue;
4448             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4449             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4450         }
4451
4452         // now everything is placed, except perhaps King (Unicorn) and Rooks
4453
4454         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4455             // Last King gets castling rights
4456             while(piecesLeft[(int)WhiteUnicorn]) {
4457                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4458                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4459             }
4460
4461             while(piecesLeft[(int)WhiteKing]) {
4462                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4463                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4464             }
4465
4466
4467         } else {
4468             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4469             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4470         }
4471
4472         // Only Rooks can be left; simply place them all
4473         while(piecesLeft[(int)WhiteRook]) {
4474                 i = put(board, WhiteRook, 0, 0, ANY);
4475                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4476                         if(first) {
4477                                 first=0;
4478                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4479                         }
4480                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4481                 }
4482         }
4483         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4484             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4485         }
4486
4487         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4488 }
4489
4490 int SetCharTable( char *table, const char * map )
4491 /* [HGM] moved here from winboard.c because of its general usefulness */
4492 /*       Basically a safe strcpy that uses the last character as King */
4493 {
4494     int result = FALSE; int NrPieces;
4495
4496     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4497                     && NrPieces >= 12 && !(NrPieces&1)) {
4498         int i; /* [HGM] Accept even length from 12 to 34 */
4499
4500         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4501         for( i=0; i<NrPieces/2-1; i++ ) {
4502             table[i] = map[i];
4503             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4504         }
4505         table[(int) WhiteKing]  = map[NrPieces/2-1];
4506         table[(int) BlackKing]  = map[NrPieces-1];
4507
4508         result = TRUE;
4509     }
4510
4511     return result;
4512 }
4513
4514 void Prelude(Board board)
4515 {       // [HGM] superchess: random selection of exo-pieces
4516         int i, j, k; ChessSquare p; 
4517         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4518
4519         GetPositionNumber(); // use FRC position number
4520
4521         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4522             SetCharTable(pieceToChar, appData.pieceToCharTable);
4523             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4524                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4525         }
4526
4527         j = seed%4;                 seed /= 4; 
4528         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4529         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4530         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4531         j = seed%3 + (seed%3 >= j); seed /= 3; 
4532         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4533         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4534         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4535         j = seed%3;                 seed /= 3; 
4536         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4537         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4538         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4539         j = seed%2 + (seed%2 >= j); seed /= 2; 
4540         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4541         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4542         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4543         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4544         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4545         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4546         put(board, exoPieces[0],    0, 0, ANY);
4547         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4548 }
4549
4550 void
4551 InitPosition(redraw)
4552      int redraw;
4553 {
4554     ChessSquare (* pieces)[BOARD_SIZE];
4555     int i, j, pawnRow, overrule,
4556     oldx = gameInfo.boardWidth,
4557     oldy = gameInfo.boardHeight,
4558     oldh = gameInfo.holdingsWidth,
4559     oldv = gameInfo.variant;
4560
4561     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4562
4563     /* [AS] Initialize pv info list [HGM] and game status */
4564     {
4565         for( i=0; i<MAX_MOVES; i++ ) {
4566             pvInfoList[i].depth = 0;
4567             epStatus[i]=EP_NONE;
4568             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4569         }
4570
4571         initialRulePlies = 0; /* 50-move counter start */
4572
4573         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4574         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4575     }
4576
4577     
4578     /* [HGM] logic here is completely changed. In stead of full positions */
4579     /* the initialized data only consist of the two backranks. The switch */
4580     /* selects which one we will use, which is than copied to the Board   */
4581     /* initialPosition, which for the rest is initialized by Pawns and    */
4582     /* empty squares. This initial position is then copied to boards[0],  */
4583     /* possibly after shuffling, so that it remains available.            */
4584
4585     gameInfo.holdingsWidth = 0; /* default board sizes */
4586     gameInfo.boardWidth    = 8;
4587     gameInfo.boardHeight   = 8;
4588     gameInfo.holdingsSize  = 0;
4589     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4590     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4591     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4592
4593     switch (gameInfo.variant) {
4594     case VariantFischeRandom:
4595       shuffleOpenings = TRUE;
4596     default:
4597       pieces = FIDEArray;
4598       break;
4599     case VariantShatranj:
4600       pieces = ShatranjArray;
4601       nrCastlingRights = 0;
4602       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4603       break;
4604     case VariantTwoKings:
4605       pieces = twoKingsArray;
4606       break;
4607     case VariantCapaRandom:
4608       shuffleOpenings = TRUE;
4609     case VariantCapablanca:
4610       pieces = CapablancaArray;
4611       gameInfo.boardWidth = 10;
4612       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4613       break;
4614     case VariantGothic:
4615       pieces = GothicArray;
4616       gameInfo.boardWidth = 10;
4617       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4618       break;
4619     case VariantJanus:
4620       pieces = JanusArray;
4621       gameInfo.boardWidth = 10;
4622       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4623       nrCastlingRights = 6;
4624         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4625         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4626         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4627         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4628         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4629         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4630       break;
4631     case VariantFalcon:
4632       pieces = FalconArray;
4633       gameInfo.boardWidth = 10;
4634       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4635       break;
4636     case VariantXiangqi:
4637       pieces = XiangqiArray;
4638       gameInfo.boardWidth  = 9;
4639       gameInfo.boardHeight = 10;
4640       nrCastlingRights = 0;
4641       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4642       break;
4643     case VariantShogi:
4644       pieces = ShogiArray;
4645       gameInfo.boardWidth  = 9;
4646       gameInfo.boardHeight = 9;
4647       gameInfo.holdingsSize = 7;
4648       nrCastlingRights = 0;
4649       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4650       break;
4651     case VariantCourier:
4652       pieces = CourierArray;
4653       gameInfo.boardWidth  = 12;
4654       nrCastlingRights = 0;
4655       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4656       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4657       break;
4658     case VariantKnightmate:
4659       pieces = KnightmateArray;
4660       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4661       break;
4662     case VariantFairy:
4663       pieces = fairyArray;
4664       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4665       break;
4666     case VariantGreat:
4667       pieces = GreatArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4670       gameInfo.holdingsSize = 8;
4671       break;
4672     case VariantSuper:
4673       pieces = FIDEArray;
4674       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4675       gameInfo.holdingsSize = 8;
4676       startedFromSetupPosition = TRUE;
4677       break;
4678     case VariantCrazyhouse:
4679     case VariantBughouse:
4680       pieces = FIDEArray;
4681       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4682       gameInfo.holdingsSize = 5;
4683       break;
4684     case VariantWildCastle:
4685       pieces = FIDEArray;
4686       /* !!?shuffle with kings guaranteed to be on d or e file */
4687       shuffleOpenings = 1;
4688       break;
4689     case VariantNoCastle:
4690       pieces = FIDEArray;
4691       nrCastlingRights = 0;
4692       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4693       /* !!?unconstrained back-rank shuffle */
4694       shuffleOpenings = 1;
4695       break;
4696     }
4697
4698     overrule = 0;
4699     if(appData.NrFiles >= 0) {
4700         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4701         gameInfo.boardWidth = appData.NrFiles;
4702     }
4703     if(appData.NrRanks >= 0) {
4704         gameInfo.boardHeight = appData.NrRanks;
4705     }
4706     if(appData.holdingsSize >= 0) {
4707         i = appData.holdingsSize;
4708         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4709         gameInfo.holdingsSize = i;
4710     }
4711     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4712     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4713         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4714
4715     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4716     if(pawnRow < 1) pawnRow = 1;
4717
4718     /* User pieceToChar list overrules defaults */
4719     if(appData.pieceToCharTable != NULL)
4720         SetCharTable(pieceToChar, appData.pieceToCharTable);
4721
4722     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4723
4724         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4725             s = (ChessSquare) 0; /* account holding counts in guard band */
4726         for( i=0; i<BOARD_HEIGHT; i++ )
4727             initialPosition[i][j] = s;
4728
4729         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4730         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4731         initialPosition[pawnRow][j] = WhitePawn;
4732         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4733         if(gameInfo.variant == VariantXiangqi) {
4734             if(j&1) {
4735                 initialPosition[pawnRow][j] = 
4736                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4737                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4738                    initialPosition[2][j] = WhiteCannon;
4739                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4740                 }
4741             }
4742         }
4743         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4744     }
4745     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4746
4747             j=BOARD_LEFT+1;
4748             initialPosition[1][j] = WhiteBishop;
4749             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4750             j=BOARD_RGHT-2;
4751             initialPosition[1][j] = WhiteRook;
4752             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4753     }
4754
4755     if( nrCastlingRights == -1) {
4756         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4757         /*       This sets default castling rights from none to normal corners   */
4758         /* Variants with other castling rights must set them themselves above    */
4759         nrCastlingRights = 6;
4760        
4761         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4762         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4763         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4764         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4765         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4766         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4767      }
4768
4769      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4770      if(gameInfo.variant == VariantGreat) { // promotion commoners
4771         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4772         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4773         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4774         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4775      }
4776 #if 0
4777     if(gameInfo.variant == VariantFischeRandom) {
4778       if( appData.defaultFrcPosition < 0 ) {
4779         ShuffleFRC( initialPosition );
4780       }
4781       else {
4782         SetupFRC( initialPosition, appData.defaultFrcPosition );
4783       }
4784       startedFromSetupPosition = TRUE;
4785     } else 
4786 #else
4787   if (appData.debugMode) {
4788     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4789   }
4790     if(shuffleOpenings) {
4791         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4792         startedFromSetupPosition = TRUE;
4793     }
4794 #endif
4795     if(startedFromPositionFile) {
4796       /* [HGM] loadPos: use PositionFile for every new game */
4797       CopyBoard(initialPosition, filePosition);
4798       for(i=0; i<nrCastlingRights; i++)
4799           castlingRights[0][i] = initialRights[i] = fileRights[i];
4800       startedFromSetupPosition = TRUE;
4801     }
4802
4803     CopyBoard(boards[0], initialPosition);
4804
4805     if(oldx != gameInfo.boardWidth ||
4806        oldy != gameInfo.boardHeight ||
4807        oldh != gameInfo.holdingsWidth
4808 #ifdef GOTHIC
4809        || oldv == VariantGothic ||        // For licensing popups
4810        gameInfo.variant == VariantGothic
4811 #endif
4812 #ifdef FALCON
4813        || oldv == VariantFalcon ||
4814        gameInfo.variant == VariantFalcon
4815 #endif
4816                                          )
4817             InitDrawingSizes(-2 ,0);
4818
4819     if (redraw)
4820       DrawPosition(TRUE, boards[currentMove]);
4821 }
4822
4823 void
4824 SendBoard(cps, moveNum)
4825      ChessProgramState *cps;
4826      int moveNum;
4827 {
4828     char message[MSG_SIZ];
4829     
4830     if (cps->useSetboard) {
4831       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4832       sprintf(message, "setboard %s\n", fen);
4833       SendToProgram(message, cps);
4834       free(fen);
4835
4836     } else {
4837       ChessSquare *bp;
4838       int i, j;
4839       /* Kludge to set black to move, avoiding the troublesome and now
4840        * deprecated "black" command.
4841        */
4842       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843
4844       SendToProgram("edit\n", cps);
4845       SendToProgram("#\n", cps);
4846       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4847         bp = &boards[moveNum][i][BOARD_LEFT];
4848         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4849           if ((int) *bp < (int) BlackPawn) {
4850             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4851                     AAA + j, ONE + i);
4852             if(message[0] == '+' || message[0] == '~') {
4853                 sprintf(message, "%c%c%c+\n",
4854                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4855                         AAA + j, ONE + i);
4856             }
4857             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858                 message[1] = BOARD_RGHT   - 1 - j + '1';
4859                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860             }
4861             SendToProgram(message, cps);
4862           }
4863         }
4864       }
4865     
4866       SendToProgram("c\n", cps);
4867       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868         bp = &boards[moveNum][i][BOARD_LEFT];
4869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870           if (((int) *bp != (int) EmptySquare)
4871               && ((int) *bp >= (int) BlackPawn)) {
4872             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873                     AAA + j, ONE + i);
4874             if(message[0] == '+' || message[0] == '~') {
4875                 sprintf(message, "%c%c%c+\n",
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4877                         AAA + j, ONE + i);
4878             }
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882             }
4883             SendToProgram(message, cps);
4884           }
4885         }
4886       }
4887     
4888       SendToProgram(".\n", cps);
4889     }
4890     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4891 }
4892
4893 int
4894 IsPromotion(fromX, fromY, toX, toY)
4895      int fromX, fromY, toX, toY;
4896 {
4897     /* [HGM] add Shogi promotions */
4898     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4899     ChessSquare piece;
4900
4901     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4902       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4903    /* [HGM] Note to self: line above also weeds out drops */
4904     piece = boards[currentMove][fromY][fromX];
4905     if(gameInfo.variant == VariantShogi) {
4906         promotionZoneSize = 3;
4907         highestPromotingPiece = (int)WhiteKing;
4908         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4909            and if in normal chess we then allow promotion to King, why not
4910            allow promotion of other piece in Shogi?                         */
4911     }
4912     if((int)piece >= BlackPawn) {
4913         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914              return FALSE;
4915         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916     } else {
4917         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4918            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919     }
4920     return ( (int)piece <= highestPromotingPiece );
4921 }
4922
4923 int
4924 InPalace(row, column)
4925      int row, column;
4926 {   /* [HGM] for Xiangqi */
4927     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4928          column < (BOARD_WIDTH + 4)/2 &&
4929          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4930     return FALSE;
4931 }
4932
4933 int
4934 PieceForSquare (x, y)
4935      int x;
4936      int y;
4937 {
4938   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4939      return -1;
4940   else
4941      return boards[currentMove][y][x];
4942 }
4943
4944 int
4945 OKToStartUserMove(x, y)
4946      int x, y;
4947 {
4948     ChessSquare from_piece;
4949     int white_piece;
4950
4951     if (matchMode) return FALSE;
4952     if (gameMode == EditPosition) return TRUE;
4953
4954     if (x >= 0 && y >= 0)
4955       from_piece = boards[currentMove][y][x];
4956     else
4957       from_piece = EmptySquare;
4958
4959     if (from_piece == EmptySquare) return FALSE;
4960
4961     white_piece = (int)from_piece >= (int)WhitePawn &&
4962       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4963
4964     switch (gameMode) {
4965       case PlayFromGameFile:
4966       case AnalyzeFile:
4967       case TwoMachinesPlay:
4968       case EndOfGame:
4969         return FALSE;
4970
4971       case IcsObserving:
4972       case IcsIdle:
4973         return FALSE;
4974
4975       case MachinePlaysWhite:
4976       case IcsPlayingBlack:
4977         if (appData.zippyPlay) return FALSE;
4978         if (white_piece) {
4979             DisplayMoveError(_("You are playing Black"));
4980             return FALSE;
4981         }
4982         break;
4983
4984       case MachinePlaysBlack:
4985       case IcsPlayingWhite:
4986         if (appData.zippyPlay) return FALSE;
4987         if (!white_piece) {
4988             DisplayMoveError(_("You are playing White"));
4989             return FALSE;
4990         }
4991         break;
4992
4993       case EditGame:
4994         if (!white_piece && WhiteOnMove(currentMove)) {
4995             DisplayMoveError(_("It is White's turn"));
4996             return FALSE;
4997         }           
4998         if (white_piece && !WhiteOnMove(currentMove)) {
4999             DisplayMoveError(_("It is Black's turn"));
5000             return FALSE;
5001         }           
5002         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5003             /* Editing correspondence game history */
5004             /* Could disallow this or prompt for confirmation */
5005             cmailOldMove = -1;
5006         }
5007         if (currentMove < forwardMostMove) {
5008             /* Discarding moves */
5009             /* Could prompt for confirmation here,
5010                but I don't think that's such a good idea */
5011             forwardMostMove = currentMove;
5012         }
5013         break;
5014
5015       case BeginningOfGame:
5016         if (appData.icsActive) return FALSE;
5017         if (!appData.noChessProgram) {
5018             if (!white_piece) {
5019                 DisplayMoveError(_("You are playing White"));
5020                 return FALSE;
5021             }
5022         }
5023         break;
5024         
5025       case Training:
5026         if (!white_piece && WhiteOnMove(currentMove)) {
5027             DisplayMoveError(_("It is White's turn"));
5028             return FALSE;
5029         }           
5030         if (white_piece && !WhiteOnMove(currentMove)) {
5031             DisplayMoveError(_("It is Black's turn"));
5032             return FALSE;
5033         }           
5034         break;
5035
5036       default:
5037       case IcsExamining:
5038         break;
5039     }
5040     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5041         && gameMode != AnalyzeFile && gameMode != Training) {
5042         DisplayMoveError(_("Displayed position is not current"));
5043         return FALSE;
5044     }
5045     return TRUE;
5046 }
5047
5048 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5049 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5050 int lastLoadGameUseList = FALSE;
5051 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5052 ChessMove lastLoadGameStart = (ChessMove) 0;
5053
5054 ChessMove
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5056      int fromX, fromY, toX, toY;
5057      int promoChar;
5058      Boolean captureOwn;
5059 {
5060     ChessMove moveType;
5061     ChessSquare pdown, pup;
5062
5063     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064
5065     /* [HGM] suppress all moves into holdings area and guard band */
5066     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5067             return ImpossibleMove;
5068
5069     /* [HGM] <sameColor> moved to here from winboard.c */
5070     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5071     pdown = boards[currentMove][fromY][fromX];
5072     pup = boards[currentMove][toY][toX];
5073     if (    gameMode != EditPosition && !captureOwn &&
5074             (WhitePawn <= pdown && pdown < BlackPawn &&
5075              WhitePawn <= pup && pup < BlackPawn  ||
5076              BlackPawn <= pdown && pdown < EmptySquare &&
5077              BlackPawn <= pup && pup < EmptySquare 
5078             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5079                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5080                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5081                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5082                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5083         )           )
5084          return Comment;
5085
5086     /* Check if the user is playing in turn.  This is complicated because we
5087        let the user "pick up" a piece before it is his turn.  So the piece he
5088        tried to pick up may have been captured by the time he puts it down!
5089        Therefore we use the color the user is supposed to be playing in this
5090        test, not the color of the piece that is currently on the starting
5091        square---except in EditGame mode, where the user is playing both
5092        sides; fortunately there the capture race can't happen.  (It can
5093        now happen in IcsExamining mode, but that's just too bad.  The user
5094        will get a somewhat confusing message in that case.)
5095        */
5096
5097     switch (gameMode) {
5098       case PlayFromGameFile:
5099       case AnalyzeFile:
5100       case TwoMachinesPlay:
5101       case EndOfGame:
5102       case IcsObserving:
5103       case IcsIdle:
5104         /* We switched into a game mode where moves are not accepted,
5105            perhaps while the mouse button was down. */
5106         return ImpossibleMove;
5107
5108       case MachinePlaysWhite:
5109         /* User is moving for Black */
5110         if (WhiteOnMove(currentMove)) {
5111             DisplayMoveError(_("It is White's turn"));
5112             return ImpossibleMove;
5113         }
5114         break;
5115
5116       case MachinePlaysBlack:
5117         /* User is moving for White */
5118         if (!WhiteOnMove(currentMove)) {
5119             DisplayMoveError(_("It is Black's turn"));
5120             return ImpossibleMove;
5121         }
5122         break;
5123
5124       case EditGame:
5125       case IcsExamining:
5126       case BeginningOfGame:
5127       case AnalyzeMode:
5128       case Training:
5129         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131             /* User is moving for Black */
5132             if (WhiteOnMove(currentMove)) {
5133                 DisplayMoveError(_("It is White's turn"));
5134                 return ImpossibleMove;
5135             }
5136         } else {
5137             /* User is moving for White */
5138             if (!WhiteOnMove(currentMove)) {
5139                 DisplayMoveError(_("It is Black's turn"));
5140                 return ImpossibleMove;
5141             }
5142         }
5143         break;
5144
5145       case IcsPlayingBlack:
5146         /* User is moving for Black */
5147         if (WhiteOnMove(currentMove)) {
5148             if (!appData.premove) {
5149                 DisplayMoveError(_("It is White's turn"));
5150             } else if (toX >= 0 && toY >= 0) {
5151                 premoveToX = toX;
5152                 premoveToY = toY;
5153                 premoveFromX = fromX;
5154                 premoveFromY = fromY;
5155                 premovePromoChar = promoChar;
5156                 gotPremove = 1;
5157                 if (appData.debugMode) 
5158                     fprintf(debugFP, "Got premove: fromX %d,"
5159                             "fromY %d, toX %d, toY %d\n",
5160                             fromX, fromY, toX, toY);
5161             }
5162             return ImpossibleMove;
5163         }
5164         break;
5165
5166       case IcsPlayingWhite:
5167         /* User is moving for White */
5168         if (!WhiteOnMove(currentMove)) {
5169             if (!appData.premove) {
5170                 DisplayMoveError(_("It is Black's turn"));
5171             } else if (toX >= 0 && toY >= 0) {
5172                 premoveToX = toX;
5173                 premoveToY = toY;
5174                 premoveFromX = fromX;
5175                 premoveFromY = fromY;
5176                 premovePromoChar = promoChar;
5177                 gotPremove = 1;
5178                 if (appData.debugMode) 
5179                     fprintf(debugFP, "Got premove: fromX %d,"
5180                             "fromY %d, toX %d, toY %d\n",
5181                             fromX, fromY, toX, toY);
5182             }
5183             return ImpossibleMove;
5184         }
5185         break;
5186
5187       default:
5188         break;
5189
5190       case EditPosition:
5191         /* EditPosition, empty square, or different color piece;
5192            click-click move is possible */
5193         if (toX == -2 || toY == -2) {
5194             boards[0][fromY][fromX] = EmptySquare;
5195             return AmbiguousMove;
5196         } else if (toX >= 0 && toY >= 0) {
5197             boards[0][toY][toX] = boards[0][fromY][fromX];
5198             boards[0][fromY][fromX] = EmptySquare;
5199             return AmbiguousMove;
5200         }
5201         return ImpossibleMove;
5202     }
5203
5204     /* [HGM] If move started in holdings, it means a drop */
5205     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5206          if( pup != EmptySquare ) return ImpossibleMove;
5207          if(appData.testLegality) {
5208              /* it would be more logical if LegalityTest() also figured out
5209               * which drops are legal. For now we forbid pawns on back rank.
5210               * Shogi is on its own here...
5211               */
5212              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5213                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5214                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5215          }
5216          return WhiteDrop; /* Not needed to specify white or black yet */
5217     }
5218
5219     userOfferedDraw = FALSE;
5220         
5221     /* [HGM] always test for legality, to get promotion info */
5222     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5223                           epStatus[currentMove], castlingRights[currentMove],
5224                                          fromY, fromX, toY, toX, promoChar);
5225     /* [HGM] but possibly ignore an IllegalMove result */
5226     if (appData.testLegality) {
5227         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5228             DisplayMoveError(_("Illegal move"));
5229             return ImpossibleMove;
5230         }
5231     }
5232 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5233     return moveType;
5234     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5235        function is made into one that returns an OK move type if FinishMove
5236        should be called. This to give the calling driver routine the
5237        opportunity to finish the userMove input with a promotion popup,
5238        without bothering the user with this for invalid or illegal moves */
5239
5240 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5241 }
5242
5243 /* Common tail of UserMoveEvent and DropMenuEvent */
5244 int
5245 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5246      ChessMove moveType;
5247      int fromX, fromY, toX, toY;
5248      /*char*/int promoChar;
5249 {
5250     char *bookHit = 0;
5251 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5252     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5253         // [HGM] superchess: suppress promotions to non-available piece
5254         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5255         if(WhiteOnMove(currentMove)) {
5256             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5257         } else {
5258             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5259         }
5260     }
5261
5262     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5263        move type in caller when we know the move is a legal promotion */
5264     if(moveType == NormalMove && promoChar)
5265         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5266 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5267     /* [HGM] convert drag-and-drop piece drops to standard form */
5268     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5269          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5270            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5271                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5272 //         fromX = boards[currentMove][fromY][fromX];
5273            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5274            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5275            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5276            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5277          fromY = DROP_RANK;
5278     }
5279
5280     /* [HGM] <popupFix> The following if has been moved here from
5281        UserMoveEvent(). Because it seemed to belon here (why not allow
5282        piece drops in training games?), and because it can only be
5283        performed after it is known to what we promote. */
5284     if (gameMode == Training) {
5285       /* compare the move played on the board to the next move in the
5286        * game. If they match, display the move and the opponent's response. 
5287        * If they don't match, display an error message.
5288        */
5289       int saveAnimate;
5290       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5291       CopyBoard(testBoard, boards[currentMove]);
5292       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5293
5294       if (CompareBoards(testBoard, boards[currentMove+1])) {
5295         ForwardInner(currentMove+1);
5296
5297         /* Autoplay the opponent's response.
5298          * if appData.animate was TRUE when Training mode was entered,
5299          * the response will be animated.
5300          */
5301         saveAnimate = appData.animate;
5302         appData.animate = animateTraining;
5303         ForwardInner(currentMove+1);
5304         appData.animate = saveAnimate;
5305
5306         /* check for the end of the game */
5307         if (currentMove >= forwardMostMove) {
5308           gameMode = PlayFromGameFile;
5309           ModeHighlight();
5310           SetTrainingModeOff();
5311           DisplayInformation(_("End of game"));
5312         }
5313       } else {
5314         DisplayError(_("Incorrect move"), 0);
5315       }
5316       return 1;
5317     }
5318
5319   /* Ok, now we know that the move is good, so we can kill
5320      the previous line in Analysis Mode */
5321   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5322     forwardMostMove = currentMove;
5323   }
5324
5325   /* If we need the chess program but it's dead, restart it */
5326   ResurrectChessProgram();
5327
5328   /* A user move restarts a paused game*/
5329   if (pausing)
5330     PauseEvent();
5331
5332   thinkOutput[0] = NULLCHAR;
5333
5334   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5335
5336   if (gameMode == BeginningOfGame) {
5337     if (appData.noChessProgram) {
5338       gameMode = EditGame;
5339       SetGameInfo();
5340     } else {
5341       char buf[MSG_SIZ];
5342       gameMode = MachinePlaysBlack;
5343       StartClocks();
5344       SetGameInfo();
5345       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5346       DisplayTitle(buf);
5347       if (first.sendName) {
5348         sprintf(buf, "name %s\n", gameInfo.white);
5349         SendToProgram(buf, &first);
5350       }
5351       StartClocks();
5352     }
5353     ModeHighlight();
5354   }
5355 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5356   /* Relay move to ICS or chess engine */
5357   if (appData.icsActive) {
5358     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5359         gameMode == IcsExamining) {
5360       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5361       ics_user_moved = 1;
5362     }
5363   } else {
5364     if (first.sendTime && (gameMode == BeginningOfGame ||
5365                            gameMode == MachinePlaysWhite ||
5366                            gameMode == MachinePlaysBlack)) {
5367       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5368     }
5369     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5370          // [HGM] book: if program might be playing, let it use book
5371         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5372         first.maybeThinking = TRUE;
5373     } else SendMoveToProgram(forwardMostMove-1, &first);
5374     if (currentMove == cmailOldMove + 1) {
5375       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5376     }
5377   }
5378
5379   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5380
5381   switch (gameMode) {
5382   case EditGame:
5383     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5384                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5385     case MT_NONE:
5386     case MT_CHECK:
5387       break;
5388     case MT_CHECKMATE:
5389     case MT_STAINMATE:
5390       if (WhiteOnMove(currentMove)) {
5391         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5392       } else {
5393         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5394       }
5395       break;
5396     case MT_STALEMATE:
5397       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5398       break;
5399     }
5400     break;
5401     
5402   case MachinePlaysBlack:
5403   case MachinePlaysWhite:
5404     /* disable certain menu options while machine is thinking */
5405     SetMachineThinkingEnables();
5406     break;
5407
5408   default:
5409     break;
5410   }
5411
5412   if(bookHit) { // [HGM] book: simulate book reply
5413         static char bookMove[MSG_SIZ]; // a bit generous?
5414
5415         programStats.nodes = programStats.depth = programStats.time = 
5416         programStats.score = programStats.got_only_move = 0;
5417         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5418
5419         strcpy(bookMove, "move ");
5420         strcat(bookMove, bookHit);
5421         HandleMachineMove(bookMove, &first);
5422   }
5423   return 1;
5424 }
5425
5426 void
5427 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5428      int fromX, fromY, toX, toY;
5429      int promoChar;
5430 {
5431     /* [HGM] This routine was added to allow calling of its two logical
5432        parts from other modules in the old way. Before, UserMoveEvent()
5433        automatically called FinishMove() if the move was OK, and returned
5434        otherwise. I separated the two, in order to make it possible to
5435        slip a promotion popup in between. But that it always needs two
5436        calls, to the first part, (now called UserMoveTest() ), and to
5437        FinishMove if the first part succeeded. Calls that do not need
5438        to do anything in between, can call this routine the old way. 
5439     */
5440     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5441 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5442     if(moveType == AmbiguousMove)
5443         DrawPosition(FALSE, boards[currentMove]);
5444     else if(moveType != ImpossibleMove && moveType != Comment)
5445         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5446 }
5447
5448 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5449 {
5450 //    char * hint = lastHint;
5451     FrontEndProgramStats stats;
5452
5453     stats.which = cps == &first ? 0 : 1;
5454     stats.depth = cpstats->depth;
5455     stats.nodes = cpstats->nodes;
5456     stats.score = cpstats->score;
5457     stats.time = cpstats->time;
5458     stats.pv = cpstats->movelist;
5459     stats.hint = lastHint;
5460     stats.an_move_index = 0;
5461     stats.an_move_count = 0;
5462
5463     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5464         stats.hint = cpstats->move_name;
5465         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5466         stats.an_move_count = cpstats->nr_moves;
5467     }
5468
5469     SetProgramStats( &stats );
5470 }
5471
5472 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5473 {   // [HGM] book: this routine intercepts moves to simulate book replies
5474     char *bookHit = NULL;
5475
5476     //first determine if the incoming move brings opponent into his book
5477     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5478         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5479     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5480     if(bookHit != NULL && !cps->bookSuspend) {
5481         // make sure opponent is not going to reply after receiving move to book position
5482         SendToProgram("force\n", cps);
5483         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5484     }
5485     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5486     // now arrange restart after book miss
5487     if(bookHit) {
5488         // after a book hit we never send 'go', and the code after the call to this routine
5489         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5490         char buf[MSG_SIZ];
5491         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5492         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5493         SendToProgram(buf, cps);
5494         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5495     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5496         SendToProgram("go\n", cps);
5497         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5498     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5499         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5500             SendToProgram("go\n", cps); 
5501         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5502     }
5503     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5504 }
5505
5506 char *savedMessage;
5507 ChessProgramState *savedState;
5508 void DeferredBookMove(void)
5509 {
5510         if(savedState->lastPing != savedState->lastPong)
5511                     ScheduleDelayedEvent(DeferredBookMove, 10);
5512         else
5513         HandleMachineMove(savedMessage, savedState);
5514 }
5515
5516 void
5517 HandleMachineMove(message, cps)
5518      char *message;
5519      ChessProgramState *cps;
5520 {
5521     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5522     char realname[MSG_SIZ];
5523     int fromX, fromY, toX, toY;
5524     ChessMove moveType;
5525     char promoChar;
5526     char *p;
5527     int machineWhite;
5528     char *bookHit;
5529
5530 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5531     /*
5532      * Kludge to ignore BEL characters
5533      */
5534     while (*message == '\007') message++;
5535
5536     /*
5537      * [HGM] engine debug message: ignore lines starting with '#' character
5538      */
5539     if(cps->debug && *message == '#') return;
5540
5541     /*
5542      * Look for book output
5543      */
5544     if (cps == &first && bookRequested) {
5545         if (message[0] == '\t' || message[0] == ' ') {
5546             /* Part of the book output is here; append it */
5547             strcat(bookOutput, message);
5548             strcat(bookOutput, "  \n");
5549             return;
5550         } else if (bookOutput[0] != NULLCHAR) {
5551             /* All of book output has arrived; display it */
5552             char *p = bookOutput;
5553             while (*p != NULLCHAR) {
5554                 if (*p == '\t') *p = ' ';
5555                 p++;
5556             }
5557             DisplayInformation(bookOutput);
5558             bookRequested = FALSE;
5559             /* Fall through to parse the current output */
5560         }
5561     }
5562
5563     /*
5564      * Look for machine move.
5565      */
5566     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5567         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5568     {
5569         /* This method is only useful on engines that support ping */
5570         if (cps->lastPing != cps->lastPong) {
5571           if (gameMode == BeginningOfGame) {
5572             /* Extra move from before last new; ignore */
5573             if (appData.debugMode) {
5574                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5575             }
5576           } else {
5577             if (appData.debugMode) {
5578                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5579                         cps->which, gameMode);
5580             }
5581
5582             SendToProgram("undo\n", cps);
5583           }
5584           return;
5585         }
5586
5587         switch (gameMode) {
5588           case BeginningOfGame:
5589             /* Extra move from before last reset; ignore */
5590             if (appData.debugMode) {
5591                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5592             }
5593             return;
5594
5595           case EndOfGame:
5596           case IcsIdle:
5597           default:
5598             /* Extra move after we tried to stop.  The mode test is
5599                not a reliable way of detecting this problem, but it's
5600                the best we can do on engines that don't support ping.
5601             */
5602             if (appData.debugMode) {
5603                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5604                         cps->which, gameMode);
5605             }
5606             SendToProgram("undo\n", cps);
5607             return;
5608
5609           case MachinePlaysWhite:
5610           case IcsPlayingWhite:
5611             machineWhite = TRUE;
5612             break;
5613
5614           case MachinePlaysBlack:
5615           case IcsPlayingBlack:
5616             machineWhite = FALSE;
5617             break;
5618
5619           case TwoMachinesPlay:
5620             machineWhite = (cps->twoMachinesColor[0] == 'w');
5621             break;
5622         }
5623         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5624             if (appData.debugMode) {
5625                 fprintf(debugFP,
5626                         "Ignoring move out of turn by %s, gameMode %d"
5627                         ", forwardMost %d\n",
5628                         cps->which, gameMode, forwardMostMove);
5629             }
5630             return;
5631         }
5632
5633     if (appData.debugMode) { int f = forwardMostMove;
5634         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5635                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5636     }
5637         if(cps->alphaRank) AlphaRank(machineMove, 4);
5638         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5639                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5640             /* Machine move could not be parsed; ignore it. */
5641             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5642                     machineMove, cps->which);
5643             DisplayError(buf1, 0);
5644             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5645                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5646             if (gameMode == TwoMachinesPlay) {
5647               GameEnds(machineWhite ? BlackWins : WhiteWins,
5648                        buf1, GE_XBOARD);
5649             }
5650             return;
5651         }
5652
5653         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5654         /* So we have to redo legality test with true e.p. status here,  */
5655         /* to make sure an illegal e.p. capture does not slip through,   */
5656         /* to cause a forfeit on a justified illegal-move complaint      */
5657         /* of the opponent.                                              */
5658         if( gameMode==TwoMachinesPlay && appData.testLegality
5659             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5660                                                               ) {
5661            ChessMove moveType;
5662            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5663                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5664                              fromY, fromX, toY, toX, promoChar);
5665             if (appData.debugMode) {
5666                 int i;
5667                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5668                     castlingRights[forwardMostMove][i], castlingRank[i]);
5669                 fprintf(debugFP, "castling rights\n");
5670             }
5671             if(moveType == IllegalMove) {
5672                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5673                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5674                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5675                            buf1, GE_XBOARD);
5676                 return;
5677            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5678            /* [HGM] Kludge to handle engines that send FRC-style castling
5679               when they shouldn't (like TSCP-Gothic) */
5680            switch(moveType) {
5681              case WhiteASideCastleFR:
5682              case BlackASideCastleFR:
5683                toX+=2;
5684                currentMoveString[2]++;
5685                break;
5686              case WhiteHSideCastleFR:
5687              case BlackHSideCastleFR:
5688                toX--;
5689                currentMoveString[2]--;
5690                break;
5691              default: ; // nothing to do, but suppresses warning of pedantic compilers
5692            }
5693         }
5694         hintRequested = FALSE;
5695         lastHint[0] = NULLCHAR;
5696         bookRequested = FALSE;
5697         /* Program may be pondering now */
5698         cps->maybeThinking = TRUE;
5699         if (cps->sendTime == 2) cps->sendTime = 1;
5700         if (cps->offeredDraw) cps->offeredDraw--;
5701
5702 #if ZIPPY
5703         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5704             first.initDone) {
5705           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5706           ics_user_moved = 1;
5707           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5708                 char buf[3*MSG_SIZ];
5709
5710                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5711                         programStats.score / 100.,
5712                         programStats.depth,
5713                         programStats.time / 100.,
5714                         (unsigned int)programStats.nodes,
5715                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5716                         programStats.movelist);
5717                 SendToICS(buf);
5718 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5719           }
5720         }
5721 #endif
5722         /* currentMoveString is set as a side-effect of ParseOneMove */
5723         strcpy(machineMove, currentMoveString);
5724         strcat(machineMove, "\n");
5725         strcpy(moveList[forwardMostMove], machineMove);
5726
5727         /* [AS] Save move info and clear stats for next move */
5728         pvInfoList[ forwardMostMove ].score = programStats.score;
5729         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5730         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5731         ClearProgramStats();
5732         thinkOutput[0] = NULLCHAR;
5733         hiddenThinkOutputState = 0;
5734
5735         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5736
5737         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5738         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5739             int count = 0;
5740
5741             while( count < adjudicateLossPlies ) {
5742                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5743
5744                 if( count & 1 ) {
5745                     score = -score; /* Flip score for winning side */
5746                 }
5747
5748                 if( score > adjudicateLossThreshold ) {
5749                     break;
5750                 }
5751
5752                 count++;
5753             }
5754
5755             if( count >= adjudicateLossPlies ) {
5756                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5757
5758                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5759                     "Xboard adjudication", 
5760                     GE_XBOARD );
5761
5762                 return;
5763             }
5764         }
5765
5766         if( gameMode == TwoMachinesPlay ) {
5767           // [HGM] some adjudications useful with buggy engines
5768             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5769           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5770
5771
5772             if( appData.testLegality )
5773             {   /* [HGM] Some more adjudications for obstinate engines */
5774                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5775                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5776                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5777                 static int moveCount = 6;
5778                 ChessMove result;
5779                 char *reason = NULL;
5780
5781                 /* Count what is on board. */
5782                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5783                 {   ChessSquare p = boards[forwardMostMove][i][j];
5784                     int m=i;
5785
5786                     switch((int) p)
5787                     {   /* count B,N,R and other of each side */
5788                         case WhiteKing:
5789                         case BlackKing:
5790                              NrK++; break; // [HGM] atomic: count Kings
5791                         case WhiteKnight:
5792                              NrWN++; break;
5793                         case WhiteBishop:
5794                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5795                              bishopsColor |= 1 << ((i^j)&1);
5796                              NrWB++; break;
5797                         case BlackKnight:
5798                              NrBN++; break;
5799                         case BlackBishop:
5800                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5801                              bishopsColor |= 1 << ((i^j)&1);
5802                              NrBB++; break;
5803                         case WhiteRook:
5804                              NrWR++; break;
5805                         case BlackRook:
5806                              NrBR++; break;
5807                         case WhiteQueen:
5808                              NrWQ++; break;
5809                         case BlackQueen:
5810                              NrBQ++; break;
5811                         case EmptySquare: 
5812                              break;
5813                         case BlackPawn:
5814                              m = 7-i;
5815                         case WhitePawn:
5816                              PawnAdvance += m; NrPawns++;
5817                     }
5818                     NrPieces += (p != EmptySquare);
5819                     NrW += ((int)p < (int)BlackPawn);
5820                     if(gameInfo.variant == VariantXiangqi && 
5821                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5822                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5823                         NrW -= ((int)p < (int)BlackPawn);
5824                     }
5825                 }
5826
5827                 /* Some material-based adjudications that have to be made before stalemate test */
5828                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5829                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5830                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5831                      if(appData.checkMates) {
5832                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5833                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5834                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5835                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5836                          return;
5837                      }
5838                 }
5839
5840                 /* Bare King in Shatranj (loses) or Losers (wins) */
5841                 if( NrW == 1 || NrPieces - NrW == 1) {
5842                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5843                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5844                      if(appData.checkMates) {
5845                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5846                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5848                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5849                          return;
5850                      }
5851                   } else
5852                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5853                   {    /* bare King */
5854                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5855                         if(appData.checkMates) {
5856                             /* but only adjudicate if adjudication enabled */
5857                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5858                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5860                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5861                             return;
5862                         }
5863                   }
5864                 } else bare = 1;
5865
5866
5867             // don't wait for engine to announce game end if we can judge ourselves
5868             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5869                                        castlingRights[forwardMostMove]) ) {
5870               case MT_CHECK:
5871                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5872                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5873                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5874                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5875                             checkCnt++;
5876                         if(checkCnt >= 2) {
5877                             reason = "Xboard adjudication: 3rd check";
5878                             epStatus[forwardMostMove] = EP_CHECKMATE;
5879                             break;
5880                         }
5881                     }
5882                 }
5883               case MT_NONE:
5884               default:
5885                 break;
5886               case MT_STALEMATE:
5887               case MT_STAINMATE:
5888                 reason = "Xboard adjudication: Stalemate";
5889                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5890                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5891                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5892                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5893                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5894                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5895                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5896                                                                         EP_CHECKMATE : EP_WINS);
5897                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5898                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5899                 }
5900                 break;
5901               case MT_CHECKMATE:
5902                 reason = "Xboard adjudication: Checkmate";
5903                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5904                 break;
5905             }
5906
5907                 switch(i = epStatus[forwardMostMove]) {
5908                     case EP_STALEMATE:
5909                         result = GameIsDrawn; break;
5910                     case EP_CHECKMATE:
5911                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5912                     case EP_WINS:
5913                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5914                     default:
5915                         result = (ChessMove) 0;
5916                 }
5917                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5918                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5919                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5920                     GameEnds( result, reason, GE_XBOARD );
5921                     return;
5922                 }
5923
5924                 /* Next absolutely insufficient mating material. */
5925                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5926                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5927                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5928                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5929                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5930
5931                      /* always flag draws, for judging claims */
5932                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5933
5934                      if(appData.materialDraws) {
5935                          /* but only adjudicate them if adjudication enabled */
5936                          SendToProgram("force\n", cps->other); // suppress reply
5937                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5938                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5939                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5940                          return;
5941                      }
5942                 }
5943
5944                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5945                 if(NrPieces == 4 && 
5946                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5947                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5948                    || NrWN==2 || NrBN==2     /* KNNK */
5949                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5950                   ) ) {
5951                      if(--moveCount < 0 && appData.trivialDraws)
5952                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5953                           SendToProgram("force\n", cps->other); // suppress reply
5954                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5955                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5956                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5957                           return;
5958                      }
5959                 } else moveCount = 6;
5960             }
5961           }
5962 #if 1
5963     if (appData.debugMode) { int i;
5964       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5965               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5966               appData.drawRepeats);
5967       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5968            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5969
5970     }
5971 #endif
5972                 /* Check for rep-draws */
5973                 count = 0;
5974                 for(k = forwardMostMove-2;
5975                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5976                         epStatus[k] < EP_UNKNOWN &&
5977                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5978                     k-=2)
5979                 {   int rights=0;
5980 #if 0
5981     if (appData.debugMode) {
5982       fprintf(debugFP, " loop\n");
5983     }
5984 #endif
5985                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5986 #if 0
5987     if (appData.debugMode) {
5988       fprintf(debugFP, "match\n");
5989     }
5990 #endif
5991                         /* compare castling rights */
5992                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5993                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5994                                 rights++; /* King lost rights, while rook still had them */
5995                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5996                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5997                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5998                                    rights++; /* but at least one rook lost them */
5999                         }
6000                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6001                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6002                                 rights++; 
6003                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6004                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6005                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6006                                    rights++;
6007                         }
6008 #if 0
6009     if (appData.debugMode) {
6010       for(i=0; i<nrCastlingRights; i++)
6011       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6012     }
6013
6014     if (appData.debugMode) {
6015       fprintf(debugFP, " %d %d\n", rights, k);
6016     }
6017 #endif
6018                         if( rights == 0 && ++count > appData.drawRepeats-2
6019                             && appData.drawRepeats > 1) {
6020                              /* adjudicate after user-specified nr of repeats */
6021                              SendToProgram("force\n", cps->other); // suppress reply
6022                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6023                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6024                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6025                                 // [HGM] xiangqi: check for forbidden perpetuals
6026                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6027                                 for(m=forwardMostMove; m>k; m-=2) {
6028                                     if(MateTest(boards[m], PosFlags(m), 
6029                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6030                                         ourPerpetual = 0; // the current mover did not always check
6031                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6032                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6033                                         hisPerpetual = 0; // the opponent did not always check
6034                                 }
6035                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6036                                                                         ourPerpetual, hisPerpetual);
6037                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6038                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6039                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6040                                     return;
6041                                 }
6042                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6043                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6044                                 // Now check for perpetual chases
6045                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6046                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6047                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6048                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6049                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6050                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6051                                         return;
6052                                     }
6053                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6054                                         break; // Abort repetition-checking loop.
6055                                 }
6056                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6057                              }
6058                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6059                              return;
6060                         }
6061                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6062                              epStatus[forwardMostMove] = EP_REP_DRAW;
6063                     }
6064                 }
6065
6066                 /* Now we test for 50-move draws. Determine ply count */
6067                 count = forwardMostMove;
6068                 /* look for last irreversble move */
6069                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6070                     count--;
6071                 /* if we hit starting position, add initial plies */
6072                 if( count == backwardMostMove )
6073                     count -= initialRulePlies;
6074                 count = forwardMostMove - count; 
6075                 if( count >= 100)
6076                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6077                          /* this is used to judge if draw claims are legal */
6078                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6079                          SendToProgram("force\n", cps->other); // suppress reply
6080                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6081                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6082                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6083                          return;
6084                 }
6085
6086                 /* if draw offer is pending, treat it as a draw claim
6087                  * when draw condition present, to allow engines a way to
6088                  * claim draws before making their move to avoid a race
6089                  * condition occurring after their move
6090                  */
6091                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6092                          char *p = NULL;
6093                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6094                              p = "Draw claim: 50-move rule";
6095                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6096                              p = "Draw claim: 3-fold repetition";
6097                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6098                              p = "Draw claim: insufficient mating material";
6099                          if( p != NULL ) {
6100                              SendToProgram("force\n", cps->other); // suppress reply
6101                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6102                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6103                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6104                              return;
6105                          }
6106                 }
6107
6108
6109                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6110                     SendToProgram("force\n", cps->other); // suppress reply
6111                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6112                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6113
6114                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6115
6116                     return;
6117                 }
6118         }
6119
6120         bookHit = NULL;
6121         if (gameMode == TwoMachinesPlay) {
6122             /* [HGM] relaying draw offers moved to after reception of move */
6123             /* and interpreting offer as claim if it brings draw condition */
6124             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6125                 SendToProgram("draw\n", cps->other);
6126             }
6127             if (cps->other->sendTime) {
6128                 SendTimeRemaining(cps->other,
6129                                   cps->other->twoMachinesColor[0] == 'w');
6130             }
6131             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6132             if (firstMove && !bookHit) {
6133                 firstMove = FALSE;
6134                 if (cps->other->useColors) {
6135                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6136                 }
6137                 SendToProgram("go\n", cps->other);
6138             }
6139             cps->other->maybeThinking = TRUE;
6140         }
6141
6142         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143         
6144         if (!pausing && appData.ringBellAfterMoves) {
6145             RingBell();
6146         }
6147
6148         /* 
6149          * Reenable menu items that were disabled while
6150          * machine was thinking
6151          */
6152         if (gameMode != TwoMachinesPlay)
6153             SetUserThinkingEnables();
6154
6155         // [HGM] book: after book hit opponent has received move and is now in force mode
6156         // force the book reply into it, and then fake that it outputted this move by jumping
6157         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6158         if(bookHit) {
6159                 static char bookMove[MSG_SIZ]; // a bit generous?
6160
6161                 strcpy(bookMove, "move ");
6162                 strcat(bookMove, bookHit);
6163                 message = bookMove;
6164                 cps = cps->other;
6165                 programStats.nodes = programStats.depth = programStats.time = 
6166                 programStats.score = programStats.got_only_move = 0;
6167                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6168
6169                 if(cps->lastPing != cps->lastPong) {
6170                     savedMessage = message; // args for deferred call
6171                     savedState = cps;
6172                     ScheduleDelayedEvent(DeferredBookMove, 10);
6173                     return;
6174                 }
6175                 goto FakeBookMove;
6176         }
6177
6178         return;
6179     }
6180
6181     /* Set special modes for chess engines.  Later something general
6182      *  could be added here; for now there is just one kludge feature,
6183      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6184      *  when "xboard" is given as an interactive command.
6185      */
6186     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6187         cps->useSigint = FALSE;
6188         cps->useSigterm = FALSE;
6189     }
6190     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6191       ParseFeatures(message+8, cps);
6192       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6193     }
6194
6195     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6196      * want this, I was asked to put it in, and obliged.
6197      */
6198     if (!strncmp(message, "setboard ", 9)) {
6199         Board initial_position; int i;
6200
6201         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6202
6203         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6204             DisplayError(_("Bad FEN received from engine"), 0);
6205             return ;
6206         } else {
6207            Reset(FALSE, FALSE);
6208            CopyBoard(boards[0], initial_position);
6209            initialRulePlies = FENrulePlies;
6210            epStatus[0] = FENepStatus;
6211            for( i=0; i<nrCastlingRights; i++ )
6212                 castlingRights[0][i] = FENcastlingRights[i];
6213            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6214            else gameMode = MachinePlaysBlack;                 
6215            DrawPosition(FALSE, boards[currentMove]);
6216         }
6217         return;
6218     }
6219
6220     /*
6221      * Look for communication commands
6222      */
6223     if (!strncmp(message, "telluser ", 9)) {
6224         DisplayNote(message + 9);
6225         return;
6226     }
6227     if (!strncmp(message, "tellusererror ", 14)) {
6228         DisplayError(message + 14, 0);
6229         return;
6230     }
6231     if (!strncmp(message, "tellopponent ", 13)) {
6232       if (appData.icsActive) {
6233         if (loggedOn) {
6234           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6235           SendToICS(buf1);
6236         }
6237       } else {
6238         DisplayNote(message + 13);
6239       }
6240       return;
6241     }
6242     if (!strncmp(message, "tellothers ", 11)) {
6243       if (appData.icsActive) {
6244         if (loggedOn) {
6245           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6246           SendToICS(buf1);
6247         }
6248       }
6249       return;
6250     }
6251     if (!strncmp(message, "tellall ", 8)) {
6252       if (appData.icsActive) {
6253         if (loggedOn) {
6254           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6255           SendToICS(buf1);
6256         }
6257       } else {
6258         DisplayNote(message + 8);
6259       }
6260       return;
6261     }
6262     if (strncmp(message, "warning", 7) == 0) {
6263         /* Undocumented feature, use tellusererror in new code */
6264         DisplayError(message, 0);
6265         return;
6266     }
6267     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6268         strcpy(realname, cps->tidy);
6269         strcat(realname, " query");
6270         AskQuestion(realname, buf2, buf1, cps->pr);
6271         return;
6272     }
6273     /* Commands from the engine directly to ICS.  We don't allow these to be 
6274      *  sent until we are logged on. Crafty kibitzes have been known to 
6275      *  interfere with the login process.
6276      */
6277     if (loggedOn) {
6278         if (!strncmp(message, "tellics ", 8)) {
6279             SendToICS(message + 8);
6280             SendToICS("\n");
6281             return;
6282         }
6283         if (!strncmp(message, "tellicsnoalias ", 15)) {
6284             SendToICS(ics_prefix);
6285             SendToICS(message + 15);
6286             SendToICS("\n");
6287             return;
6288         }
6289         /* The following are for backward compatibility only */
6290         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6291             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6292             SendToICS(ics_prefix);
6293             SendToICS(message);
6294             SendToICS("\n");
6295             return;
6296         }
6297     }
6298     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6299         return;
6300     }
6301     /*
6302      * If the move is illegal, cancel it and redraw the board.
6303      * Also deal with other error cases.  Matching is rather loose
6304      * here to accommodate engines written before the spec.
6305      */
6306     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6307         strncmp(message, "Error", 5) == 0) {
6308         if (StrStr(message, "name") || 
6309             StrStr(message, "rating") || StrStr(message, "?") ||
6310             StrStr(message, "result") || StrStr(message, "board") ||
6311             StrStr(message, "bk") || StrStr(message, "computer") ||
6312             StrStr(message, "variant") || StrStr(message, "hint") ||
6313             StrStr(message, "random") || StrStr(message, "depth") ||
6314             StrStr(message, "accepted")) {
6315             return;
6316         }
6317         if (StrStr(message, "protover")) {
6318           /* Program is responding to input, so it's apparently done
6319              initializing, and this error message indicates it is
6320              protocol version 1.  So we don't need to wait any longer
6321              for it to initialize and send feature commands. */
6322           FeatureDone(cps, 1);
6323           cps->protocolVersion = 1;
6324           return;
6325         }
6326         cps->maybeThinking = FALSE;
6327
6328         if (StrStr(message, "draw")) {
6329             /* Program doesn't have "draw" command */
6330             cps->sendDrawOffers = 0;
6331             return;
6332         }
6333         if (cps->sendTime != 1 &&
6334             (StrStr(message, "time") || StrStr(message, "otim"))) {
6335           /* Program apparently doesn't have "time" or "otim" command */
6336           cps->sendTime = 0;
6337           return;
6338         }
6339         if (StrStr(message, "analyze")) {
6340             cps->analysisSupport = FALSE;
6341             cps->analyzing = FALSE;
6342             Reset(FALSE, TRUE);
6343             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6344             DisplayError(buf2, 0);
6345             return;
6346         }
6347         if (StrStr(message, "(no matching move)st")) {
6348           /* Special kludge for GNU Chess 4 only */
6349           cps->stKludge = TRUE;
6350           SendTimeControl(cps, movesPerSession, timeControl,
6351                           timeIncrement, appData.searchDepth,
6352                           searchTime);
6353           return;
6354         }
6355         if (StrStr(message, "(no matching move)sd")) {
6356           /* Special kludge for GNU Chess 4 only */
6357           cps->sdKludge = TRUE;
6358           SendTimeControl(cps, movesPerSession, timeControl,
6359                           timeIncrement, appData.searchDepth,
6360                           searchTime);
6361           return;
6362         }
6363         if (!StrStr(message, "llegal")) {
6364             return;
6365         }
6366         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6367             gameMode == IcsIdle) return;
6368         if (forwardMostMove <= backwardMostMove) return;
6369 #if 0
6370         /* Following removed: it caused a bug where a real illegal move
6371            message in analyze mored would be ignored. */
6372         if (cps == &first && programStats.ok_to_send == 0) {
6373             /* Bogus message from Crafty responding to "."  This filtering
6374                can miss some of the bad messages, but fortunately the bug 
6375                is fixed in current Crafty versions, so it doesn't matter. */
6376             return;
6377         }
6378 #endif
6379         if (pausing) PauseEvent();
6380       if(appData.forceIllegal) {
6381             // [HGM] illegal: machine refused move; force position after move into it
6382           SendToProgram("force\n", cps);
6383           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6384                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6385                 // when black is to move, while there might be nothing on a2 or black
6386                 // might already have the move. So send the board as if white has the move.
6387                 // But first we must change the stm of the engine, as it refused the last move
6388                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6389                 if(WhiteOnMove(forwardMostMove)) {
6390                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6391                     SendBoard(cps, forwardMostMove); // kludgeless board
6392                 } else {
6393                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6394                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6395                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6396                 }
6397           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6398             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6399                  gameMode == TwoMachinesPlay)
6400               SendToProgram("go\n", cps);
6401             return;
6402       } else
6403         if (gameMode == PlayFromGameFile) {
6404             /* Stop reading this game file */
6405             gameMode = EditGame;
6406             ModeHighlight();
6407         }
6408         currentMove = --forwardMostMove;
6409         DisplayMove(currentMove-1); /* before DisplayMoveError */
6410         SwitchClocks();
6411         DisplayBothClocks();
6412         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6413                 parseList[currentMove], cps->which);
6414         DisplayMoveError(buf1);
6415         DrawPosition(FALSE, boards[currentMove]);
6416
6417         /* [HGM] illegal-move claim should forfeit game when Xboard */
6418         /* only passes fully legal moves                            */
6419         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6420             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6421                                 "False illegal-move claim", GE_XBOARD );
6422         }
6423         return;
6424     }
6425     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6426         /* Program has a broken "time" command that
6427            outputs a string not ending in newline.
6428            Don't use it. */
6429         cps->sendTime = 0;
6430     }
6431     
6432     /*
6433      * If chess program startup fails, exit with an error message.
6434      * Attempts to recover here are futile.
6435      */
6436     if ((StrStr(message, "unknown host") != NULL)
6437         || (StrStr(message, "No remote directory") != NULL)
6438         || (StrStr(message, "not found") != NULL)
6439         || (StrStr(message, "No such file") != NULL)
6440         || (StrStr(message, "can't alloc") != NULL)
6441         || (StrStr(message, "Permission denied") != NULL)) {
6442
6443         cps->maybeThinking = FALSE;
6444         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6445                 cps->which, cps->program, cps->host, message);
6446         RemoveInputSource(cps->isr);
6447         DisplayFatalError(buf1, 0, 1);
6448         return;
6449     }
6450     
6451     /* 
6452      * Look for hint output
6453      */
6454     if (sscanf(message, "Hint: %s", buf1) == 1) {
6455         if (cps == &first && hintRequested) {
6456             hintRequested = FALSE;
6457             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6458                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6459                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6460                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6461                                     fromY, fromX, toY, toX, promoChar, buf1);
6462                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6463                 DisplayInformation(buf2);
6464             } else {
6465                 /* Hint move could not be parsed!? */
6466               snprintf(buf2, sizeof(buf2),
6467                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6468                         buf1, cps->which);
6469                 DisplayError(buf2, 0);
6470             }
6471         } else {
6472             strcpy(lastHint, buf1);
6473         }
6474         return;
6475     }
6476
6477     /*
6478      * Ignore other messages if game is not in progress
6479      */
6480     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6481         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6482
6483     /*
6484      * look for win, lose, draw, or draw offer
6485      */
6486     if (strncmp(message, "1-0", 3) == 0) {
6487         char *p, *q, *r = "";
6488         p = strchr(message, '{');
6489         if (p) {
6490             q = strchr(p, '}');
6491             if (q) {
6492                 *q = NULLCHAR;
6493                 r = p + 1;
6494             }
6495         }
6496         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6497         return;
6498     } else if (strncmp(message, "0-1", 3) == 0) {
6499         char *p, *q, *r = "";
6500         p = strchr(message, '{');
6501         if (p) {
6502             q = strchr(p, '}');
6503             if (q) {
6504                 *q = NULLCHAR;
6505                 r = p + 1;
6506             }
6507         }
6508         /* Kludge for Arasan 4.1 bug */
6509         if (strcmp(r, "Black resigns") == 0) {
6510             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6511             return;
6512         }
6513         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6514         return;
6515     } else if (strncmp(message, "1/2", 3) == 0) {
6516         char *p, *q, *r = "";
6517         p = strchr(message, '{');
6518         if (p) {
6519             q = strchr(p, '}');
6520             if (q) {
6521                 *q = NULLCHAR;
6522                 r = p + 1;
6523             }
6524         }
6525             
6526         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6527         return;
6528
6529     } else if (strncmp(message, "White resign", 12) == 0) {
6530         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6531         return;
6532     } else if (strncmp(message, "Black resign", 12) == 0) {
6533         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6534         return;
6535     } else if (strncmp(message, "White matches", 13) == 0 ||
6536                strncmp(message, "Black matches", 13) == 0   ) {
6537         /* [HGM] ignore GNUShogi noises */
6538         return;
6539     } else if (strncmp(message, "White", 5) == 0 &&
6540                message[5] != '(' &&
6541                StrStr(message, "Black") == NULL) {
6542         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6543         return;
6544     } else if (strncmp(message, "Black", 5) == 0 &&
6545                message[5] != '(') {
6546         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6547         return;
6548     } else if (strcmp(message, "resign") == 0 ||
6549                strcmp(message, "computer resigns") == 0) {
6550         switch (gameMode) {
6551           case MachinePlaysBlack:
6552           case IcsPlayingBlack:
6553             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6554             break;
6555           case MachinePlaysWhite:
6556           case IcsPlayingWhite:
6557             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6558             break;
6559           case TwoMachinesPlay:
6560             if (cps->twoMachinesColor[0] == 'w')
6561               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6562             else
6563               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6564             break;
6565           default:
6566             /* can't happen */
6567             break;
6568         }
6569         return;
6570     } else if (strncmp(message, "opponent mates", 14) == 0) {
6571         switch (gameMode) {
6572           case MachinePlaysBlack:
6573           case IcsPlayingBlack:
6574             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6575             break;
6576           case MachinePlaysWhite:
6577           case IcsPlayingWhite:
6578             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6579             break;
6580           case TwoMachinesPlay:
6581             if (cps->twoMachinesColor[0] == 'w')
6582               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6583             else
6584               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6585             break;
6586           default:
6587             /* can't happen */
6588             break;
6589         }
6590         return;
6591     } else if (strncmp(message, "computer mates", 14) == 0) {
6592         switch (gameMode) {
6593           case MachinePlaysBlack:
6594           case IcsPlayingBlack:
6595             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6596             break;
6597           case MachinePlaysWhite:
6598           case IcsPlayingWhite:
6599             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6600             break;
6601           case TwoMachinesPlay:
6602             if (cps->twoMachinesColor[0] == 'w')
6603               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6604             else
6605               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6606             break;
6607           default:
6608             /* can't happen */
6609             break;
6610         }
6611         return;
6612     } else if (strncmp(message, "checkmate", 9) == 0) {
6613         if (WhiteOnMove(forwardMostMove)) {
6614             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6615         } else {
6616             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6617         }
6618         return;
6619     } else if (strstr(message, "Draw") != NULL ||
6620                strstr(message, "game is a draw") != NULL) {
6621         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6622         return;
6623     } else if (strstr(message, "offer") != NULL &&
6624                strstr(message, "draw") != NULL) {
6625 #if ZIPPY
6626         if (appData.zippyPlay && first.initDone) {
6627             /* Relay offer to ICS */
6628             SendToICS(ics_prefix);
6629             SendToICS("draw\n");
6630         }
6631 #endif
6632         cps->offeredDraw = 2; /* valid until this engine moves twice */
6633         if (gameMode == TwoMachinesPlay) {
6634             if (cps->other->offeredDraw) {
6635                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6636             /* [HGM] in two-machine mode we delay relaying draw offer      */
6637             /* until after we also have move, to see if it is really claim */
6638             }
6639 #if 0
6640               else {
6641                 if (cps->other->sendDrawOffers) {
6642                     SendToProgram("draw\n", cps->other);
6643                 }
6644             }
6645 #endif
6646         } else if (gameMode == MachinePlaysWhite ||
6647                    gameMode == MachinePlaysBlack) {
6648           if (userOfferedDraw) {
6649             DisplayInformation(_("Machine accepts your draw offer"));
6650             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6651           } else {
6652             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6653           }
6654         }
6655     }
6656
6657     
6658     /*
6659      * Look for thinking output
6660      */
6661     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6662           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6663                                 ) {
6664         int plylev, mvleft, mvtot, curscore, time;
6665         char mvname[MOVE_LEN];
6666         u64 nodes; // [DM]
6667         char plyext;
6668         int ignore = FALSE;
6669         int prefixHint = FALSE;
6670         mvname[0] = NULLCHAR;
6671
6672         switch (gameMode) {
6673           case MachinePlaysBlack:
6674           case IcsPlayingBlack:
6675             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6676             break;
6677           case MachinePlaysWhite:
6678           case IcsPlayingWhite:
6679             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6680             break;
6681           case AnalyzeMode:
6682           case AnalyzeFile:
6683             break;
6684           case IcsObserving: /* [DM] icsEngineAnalyze */
6685             if (!appData.icsEngineAnalyze) ignore = TRUE;
6686             break;
6687           case TwoMachinesPlay:
6688             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6689                 ignore = TRUE;
6690             }
6691             break;
6692           default:
6693             ignore = TRUE;
6694             break;
6695         }
6696
6697         if (!ignore) {
6698             buf1[0] = NULLCHAR;
6699             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6700                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6701
6702                 if (plyext != ' ' && plyext != '\t') {
6703                     time *= 100;
6704                 }
6705
6706                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6707                 if( cps->scoreIsAbsolute && 
6708                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6709                 {
6710                     curscore = -curscore;
6711                 }
6712
6713
6714                 programStats.depth = plylev;
6715                 programStats.nodes = nodes;
6716                 programStats.time = time;
6717                 programStats.score = curscore;
6718                 programStats.got_only_move = 0;
6719
6720                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6721                         int ticklen;
6722
6723                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6724                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6725                         if(WhiteOnMove(forwardMostMove)) 
6726                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6727                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6728                 }
6729
6730                 /* Buffer overflow protection */
6731                 if (buf1[0] != NULLCHAR) {
6732                     if (strlen(buf1) >= sizeof(programStats.movelist)
6733                         && appData.debugMode) {
6734                         fprintf(debugFP,
6735                                 "PV is too long; using the first %d bytes.\n",
6736                                 sizeof(programStats.movelist) - 1);
6737                     }
6738
6739                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6740                 } else {
6741                     sprintf(programStats.movelist, " no PV\n");
6742                 }
6743
6744                 if (programStats.seen_stat) {
6745                     programStats.ok_to_send = 1;
6746                 }
6747
6748                 if (strchr(programStats.movelist, '(') != NULL) {
6749                     programStats.line_is_book = 1;
6750                     programStats.nr_moves = 0;
6751                     programStats.moves_left = 0;
6752                 } else {
6753                     programStats.line_is_book = 0;
6754                 }
6755
6756                 SendProgramStatsToFrontend( cps, &programStats );
6757
6758                 /* 
6759                     [AS] Protect the thinkOutput buffer from overflow... this
6760                     is only useful if buf1 hasn't overflowed first!
6761                 */
6762                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6763                         plylev, 
6764                         (gameMode == TwoMachinesPlay ?
6765                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6766                         ((double) curscore) / 100.0,
6767                         prefixHint ? lastHint : "",
6768                         prefixHint ? " " : "" );
6769
6770                 if( buf1[0] != NULLCHAR ) {
6771                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6772
6773                     if( strlen(buf1) > max_len ) {
6774                         if( appData.debugMode) {
6775                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6776                         }
6777                         buf1[max_len+1] = '\0';
6778                     }
6779
6780                     strcat( thinkOutput, buf1 );
6781                 }
6782
6783                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6784                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6785                     DisplayMove(currentMove - 1);
6786                     DisplayAnalysis();
6787                 }
6788                 return;
6789
6790             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6791                 /* crafty (9.25+) says "(only move) <move>"
6792                  * if there is only 1 legal move
6793                  */
6794                 sscanf(p, "(only move) %s", buf1);
6795                 sprintf(thinkOutput, "%s (only move)", buf1);
6796                 sprintf(programStats.movelist, "%s (only move)", buf1);
6797                 programStats.depth = 1;
6798                 programStats.nr_moves = 1;
6799                 programStats.moves_left = 1;
6800                 programStats.nodes = 1;
6801                 programStats.time = 1;
6802                 programStats.got_only_move = 1;
6803
6804                 /* Not really, but we also use this member to
6805                    mean "line isn't going to change" (Crafty
6806                    isn't searching, so stats won't change) */
6807                 programStats.line_is_book = 1;
6808
6809                 SendProgramStatsToFrontend( cps, &programStats );
6810                 
6811                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6812                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6813                     DisplayMove(currentMove - 1);
6814                     DisplayAnalysis();
6815                 }
6816                 return;
6817             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6818                               &time, &nodes, &plylev, &mvleft,
6819                               &mvtot, mvname) >= 5) {
6820                 /* The stat01: line is from Crafty (9.29+) in response
6821                    to the "." command */
6822                 programStats.seen_stat = 1;
6823                 cps->maybeThinking = TRUE;
6824
6825                 if (programStats.got_only_move || !appData.periodicUpdates)
6826                   return;
6827
6828                 programStats.depth = plylev;
6829                 programStats.time = time;
6830                 programStats.nodes = nodes;
6831                 programStats.moves_left = mvleft;
6832                 programStats.nr_moves = mvtot;
6833                 strcpy(programStats.move_name, mvname);
6834                 programStats.ok_to_send = 1;
6835                 programStats.movelist[0] = '\0';
6836
6837                 SendProgramStatsToFrontend( cps, &programStats );
6838
6839                 DisplayAnalysis();
6840                 return;
6841
6842             } else if (strncmp(message,"++",2) == 0) {
6843                 /* Crafty 9.29+ outputs this */
6844                 programStats.got_fail = 2;
6845                 return;
6846
6847             } else if (strncmp(message,"--",2) == 0) {
6848                 /* Crafty 9.29+ outputs this */
6849                 programStats.got_fail = 1;
6850                 return;
6851
6852             } else if (thinkOutput[0] != NULLCHAR &&
6853                        strncmp(message, "    ", 4) == 0) {
6854                 unsigned message_len;
6855
6856                 p = message;
6857                 while (*p && *p == ' ') p++;
6858
6859                 message_len = strlen( p );
6860
6861                 /* [AS] Avoid buffer overflow */
6862                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6863                     strcat(thinkOutput, " ");
6864                     strcat(thinkOutput, p);
6865                 }
6866
6867                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6868                     strcat(programStats.movelist, " ");
6869                     strcat(programStats.movelist, p);
6870                 }
6871
6872                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6873                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6874                     DisplayMove(currentMove - 1);
6875                     DisplayAnalysis();
6876                 }
6877                 return;
6878             }
6879         }
6880         else {
6881             buf1[0] = NULLCHAR;
6882
6883             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6884                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6885             {
6886                 ChessProgramStats cpstats;
6887
6888                 if (plyext != ' ' && plyext != '\t') {
6889                     time *= 100;
6890                 }
6891
6892                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6893                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6894                     curscore = -curscore;
6895                 }
6896
6897                 cpstats.depth = plylev;
6898                 cpstats.nodes = nodes;
6899                 cpstats.time = time;
6900                 cpstats.score = curscore;
6901                 cpstats.got_only_move = 0;
6902                 cpstats.movelist[0] = '\0';
6903
6904                 if (buf1[0] != NULLCHAR) {
6905                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6906                 }
6907
6908                 cpstats.ok_to_send = 0;
6909                 cpstats.line_is_book = 0;
6910                 cpstats.nr_moves = 0;
6911                 cpstats.moves_left = 0;
6912
6913                 SendProgramStatsToFrontend( cps, &cpstats );
6914             }
6915         }
6916     }
6917 }
6918
6919
6920 /* Parse a game score from the character string "game", and
6921    record it as the history of the current game.  The game
6922    score is NOT assumed to start from the standard position. 
6923    The display is not updated in any way.
6924    */
6925 void
6926 ParseGameHistory(game)
6927      char *game;
6928 {
6929     ChessMove moveType;
6930     int fromX, fromY, toX, toY, boardIndex;
6931     char promoChar;
6932     char *p, *q;
6933     char buf[MSG_SIZ];
6934
6935     if (appData.debugMode)
6936       fprintf(debugFP, "Parsing game history: %s\n", game);
6937
6938     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6939     gameInfo.site = StrSave(appData.icsHost);
6940     gameInfo.date = PGNDate();
6941     gameInfo.round = StrSave("-");
6942
6943     /* Parse out names of players */
6944     while (*game == ' ') game++;
6945     p = buf;
6946     while (*game != ' ') *p++ = *game++;
6947     *p = NULLCHAR;
6948     gameInfo.white = StrSave(buf);
6949     while (*game == ' ') game++;
6950     p = buf;
6951     while (*game != ' ' && *game != '\n') *p++ = *game++;
6952     *p = NULLCHAR;
6953     gameInfo.black = StrSave(buf);
6954
6955     /* Parse moves */
6956     boardIndex = blackPlaysFirst ? 1 : 0;
6957     yynewstr(game);
6958     for (;;) {
6959         yyboardindex = boardIndex;
6960         moveType = (ChessMove) yylex();
6961         switch (moveType) {
6962           case IllegalMove:             /* maybe suicide chess, etc. */
6963   if (appData.debugMode) {
6964     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6965     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6966     setbuf(debugFP, NULL);
6967   }
6968           case WhitePromotionChancellor:
6969           case BlackPromotionChancellor:
6970           case WhitePromotionArchbishop:
6971           case BlackPromotionArchbishop:
6972           case WhitePromotionQueen:
6973           case BlackPromotionQueen:
6974           case WhitePromotionRook:
6975           case BlackPromotionRook:
6976           case WhitePromotionBishop:
6977           case BlackPromotionBishop:
6978           case WhitePromotionKnight:
6979           case BlackPromotionKnight:
6980           case WhitePromotionKing:
6981           case BlackPromotionKing:
6982           case NormalMove:
6983           case WhiteCapturesEnPassant:
6984           case BlackCapturesEnPassant:
6985           case WhiteKingSideCastle:
6986           case WhiteQueenSideCastle:
6987           case BlackKingSideCastle:
6988           case BlackQueenSideCastle:
6989           case WhiteKingSideCastleWild:
6990           case WhiteQueenSideCastleWild:
6991           case BlackKingSideCastleWild:
6992           case BlackQueenSideCastleWild:
6993           /* PUSH Fabien */
6994           case WhiteHSideCastleFR:
6995           case WhiteASideCastleFR:
6996           case BlackHSideCastleFR:
6997           case BlackASideCastleFR:
6998           /* POP Fabien */
6999             fromX = currentMoveString[0] - AAA;
7000             fromY = currentMoveString[1] - ONE;
7001             toX = currentMoveString[2] - AAA;
7002             toY = currentMoveString[3] - ONE;
7003             promoChar = currentMoveString[4];
7004             break;
7005           case WhiteDrop:
7006           case BlackDrop:
7007             fromX = moveType == WhiteDrop ?
7008               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7009             (int) CharToPiece(ToLower(currentMoveString[0]));
7010             fromY = DROP_RANK;
7011             toX = currentMoveString[2] - AAA;
7012             toY = currentMoveString[3] - ONE;
7013             promoChar = NULLCHAR;
7014             break;
7015           case AmbiguousMove:
7016             /* bug? */
7017             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7018   if (appData.debugMode) {
7019     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7020     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7021     setbuf(debugFP, NULL);
7022   }
7023             DisplayError(buf, 0);
7024             return;
7025           case ImpossibleMove:
7026             /* bug? */
7027             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7028   if (appData.debugMode) {
7029     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7030     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7031     setbuf(debugFP, NULL);
7032   }
7033             DisplayError(buf, 0);
7034             return;
7035           case (ChessMove) 0:   /* end of file */
7036             if (boardIndex < backwardMostMove) {
7037                 /* Oops, gap.  How did that happen? */
7038                 DisplayError(_("Gap in move list"), 0);
7039                 return;
7040             }
7041             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7042             if (boardIndex > forwardMostMove) {
7043                 forwardMostMove = boardIndex;
7044             }
7045             return;
7046           case ElapsedTime:
7047             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7048                 strcat(parseList[boardIndex-1], " ");
7049                 strcat(parseList[boardIndex-1], yy_text);
7050             }
7051             continue;
7052           case Comment:
7053           case PGNTag:
7054           case NAG:
7055           default:
7056             /* ignore */
7057             continue;
7058           case WhiteWins:
7059           case BlackWins:
7060           case GameIsDrawn:
7061           case GameUnfinished:
7062             if (gameMode == IcsExamining) {
7063                 if (boardIndex < backwardMostMove) {
7064                     /* Oops, gap.  How did that happen? */
7065                     return;
7066                 }
7067                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7068                 return;
7069             }
7070             gameInfo.result = moveType;
7071             p = strchr(yy_text, '{');
7072             if (p == NULL) p = strchr(yy_text, '(');
7073             if (p == NULL) {
7074                 p = yy_text;
7075                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7076             } else {
7077                 q = strchr(p, *p == '{' ? '}' : ')');
7078                 if (q != NULL) *q = NULLCHAR;
7079                 p++;
7080             }
7081             gameInfo.resultDetails = StrSave(p);
7082             continue;
7083         }
7084         if (boardIndex >= forwardMostMove &&
7085             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7086             backwardMostMove = blackPlaysFirst ? 1 : 0;
7087             return;
7088         }
7089         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7090                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7091                                  parseList[boardIndex]);
7092         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7093         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7094         /* currentMoveString is set as a side-effect of yylex */
7095         strcpy(moveList[boardIndex], currentMoveString);
7096         strcat(moveList[boardIndex], "\n");
7097         boardIndex++;
7098         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7099                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7100         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7101                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7102           case MT_NONE:
7103           case MT_STALEMATE:
7104           default:
7105             break;
7106           case MT_CHECK:
7107             if(gameInfo.variant != VariantShogi)
7108                 strcat(parseList[boardIndex - 1], "+");
7109             break;
7110           case MT_CHECKMATE:
7111           case MT_STAINMATE:
7112             strcat(parseList[boardIndex - 1], "#");
7113             break;
7114         }
7115     }
7116 }
7117
7118
7119 /* Apply a move to the given board  */
7120 void
7121 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7122      int fromX, fromY, toX, toY;
7123      int promoChar;
7124      Board board;
7125      char *castling;
7126      char *ep;
7127 {
7128   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7129
7130     /* [HGM] compute & store e.p. status and castling rights for new position */
7131     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7132     { int i;
7133
7134       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7135       oldEP = *ep;
7136       *ep = EP_NONE;
7137
7138       if( board[toY][toX] != EmptySquare ) 
7139            *ep = EP_CAPTURE;  
7140
7141       if( board[fromY][fromX] == WhitePawn ) {
7142            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7143                *ep = EP_PAWN_MOVE;
7144            if( toY-fromY==2) {
7145                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7146                         gameInfo.variant != VariantBerolina || toX < fromX)
7147                       *ep = toX | berolina;
7148                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7149                         gameInfo.variant != VariantBerolina || toX > fromX) 
7150                       *ep = toX;
7151            }
7152       } else 
7153       if( board[fromY][fromX] == BlackPawn ) {
7154            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7155                *ep = EP_PAWN_MOVE; 
7156            if( toY-fromY== -2) {
7157                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7158                         gameInfo.variant != VariantBerolina || toX < fromX)
7159                       *ep = toX | berolina;
7160                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7161                         gameInfo.variant != VariantBerolina || toX > fromX) 
7162                       *ep = toX;
7163            }
7164        }
7165
7166        for(i=0; i<nrCastlingRights; i++) {
7167            if(castling[i] == fromX && castlingRank[i] == fromY ||
7168               castling[i] == toX   && castlingRank[i] == toY   
7169              ) castling[i] = -1; // revoke for moved or captured piece
7170        }
7171
7172     }
7173
7174   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7175   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7176        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7177          
7178   if (fromX == toX && fromY == toY) return;
7179
7180   if (fromY == DROP_RANK) {
7181         /* must be first */
7182         piece = board[toY][toX] = (ChessSquare) fromX;
7183   } else {
7184      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7185      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7186      if(gameInfo.variant == VariantKnightmate)
7187          king += (int) WhiteUnicorn - (int) WhiteKing;
7188
7189     /* Code added by Tord: */
7190     /* FRC castling assumed when king captures friendly rook. */
7191     if (board[fromY][fromX] == WhiteKing &&
7192              board[toY][toX] == WhiteRook) {
7193       board[fromY][fromX] = EmptySquare;
7194       board[toY][toX] = EmptySquare;
7195       if(toX > fromX) {
7196         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7197       } else {
7198         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7199       }
7200     } else if (board[fromY][fromX] == BlackKing &&
7201                board[toY][toX] == BlackRook) {
7202       board[fromY][fromX] = EmptySquare;
7203       board[toY][toX] = EmptySquare;
7204       if(toX > fromX) {
7205         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7206       } else {
7207         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7208       }
7209     /* End of code added by Tord */
7210
7211     } else if (board[fromY][fromX] == king
7212         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7213         && toY == fromY && toX > fromX+1) {
7214         board[fromY][fromX] = EmptySquare;
7215         board[toY][toX] = king;
7216         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7217         board[fromY][BOARD_RGHT-1] = EmptySquare;
7218     } else if (board[fromY][fromX] == king
7219         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7220                && toY == fromY && toX < fromX-1) {
7221         board[fromY][fromX] = EmptySquare;
7222         board[toY][toX] = king;
7223         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7224         board[fromY][BOARD_LEFT] = EmptySquare;
7225     } else if (board[fromY][fromX] == WhitePawn
7226                && toY == BOARD_HEIGHT-1
7227                && gameInfo.variant != VariantXiangqi
7228                ) {
7229         /* white pawn promotion */
7230         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7231         if (board[toY][toX] == EmptySquare) {
7232             board[toY][toX] = WhiteQueen;
7233         }
7234         if(gameInfo.variant==VariantBughouse ||
7235            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7236             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7237         board[fromY][fromX] = EmptySquare;
7238     } else if ((fromY == BOARD_HEIGHT-4)
7239                && (toX != fromX)
7240                && gameInfo.variant != VariantXiangqi
7241                && gameInfo.variant != VariantBerolina
7242                && (board[fromY][fromX] == WhitePawn)
7243                && (board[toY][toX] == EmptySquare)) {
7244         board[fromY][fromX] = EmptySquare;
7245         board[toY][toX] = WhitePawn;
7246         captured = board[toY - 1][toX];
7247         board[toY - 1][toX] = EmptySquare;
7248     } else if ((fromY == BOARD_HEIGHT-4)
7249                && (toX == fromX)
7250                && gameInfo.variant == VariantBerolina
7251                && (board[fromY][fromX] == WhitePawn)
7252                && (board[toY][toX] == EmptySquare)) {
7253         board[fromY][fromX] = EmptySquare;
7254         board[toY][toX] = WhitePawn;
7255         if(oldEP & EP_BEROLIN_A) {
7256                 captured = board[fromY][fromX-1];
7257                 board[fromY][fromX-1] = EmptySquare;
7258         }else{  captured = board[fromY][fromX+1];
7259                 board[fromY][fromX+1] = EmptySquare;
7260         }
7261     } else if (board[fromY][fromX] == king
7262         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7263                && toY == fromY && toX > fromX+1) {
7264         board[fromY][fromX] = EmptySquare;
7265         board[toY][toX] = king;
7266         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7267         board[fromY][BOARD_RGHT-1] = EmptySquare;
7268     } else if (board[fromY][fromX] == king
7269         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7270                && toY == fromY && toX < fromX-1) {
7271         board[fromY][fromX] = EmptySquare;
7272         board[toY][toX] = king;
7273         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7274         board[fromY][BOARD_LEFT] = EmptySquare;
7275     } else if (fromY == 7 && fromX == 3
7276                && board[fromY][fromX] == BlackKing
7277                && toY == 7 && toX == 5) {
7278         board[fromY][fromX] = EmptySquare;
7279         board[toY][toX] = BlackKing;
7280         board[fromY][7] = EmptySquare;
7281         board[toY][4] = BlackRook;
7282     } else if (fromY == 7 && fromX == 3
7283                && board[fromY][fromX] == BlackKing
7284                && toY == 7 && toX == 1) {
7285         board[fromY][fromX] = EmptySquare;
7286         board[toY][toX] = BlackKing;
7287         board[fromY][0] = EmptySquare;
7288         board[toY][2] = BlackRook;
7289     } else if (board[fromY][fromX] == BlackPawn
7290                && toY == 0
7291                && gameInfo.variant != VariantXiangqi
7292                ) {
7293         /* black pawn promotion */
7294         board[0][toX] = CharToPiece(ToLower(promoChar));
7295         if (board[0][toX] == EmptySquare) {
7296             board[0][toX] = BlackQueen;
7297         }
7298         if(gameInfo.variant==VariantBughouse ||
7299            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7300             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7301         board[fromY][fromX] = EmptySquare;
7302     } else if ((fromY == 3)
7303                && (toX != fromX)
7304                && gameInfo.variant != VariantXiangqi
7305                && gameInfo.variant != VariantBerolina
7306                && (board[fromY][fromX] == BlackPawn)
7307                && (board[toY][toX] == EmptySquare)) {
7308         board[fromY][fromX] = EmptySquare;
7309         board[toY][toX] = BlackPawn;
7310         captured = board[toY + 1][toX];
7311         board[toY + 1][toX] = EmptySquare;
7312     } else if ((fromY == 3)
7313                && (toX == fromX)
7314                && gameInfo.variant == VariantBerolina
7315                && (board[fromY][fromX] == BlackPawn)
7316                && (board[toY][toX] == EmptySquare)) {
7317         board[fromY][fromX] = EmptySquare;
7318         board[toY][toX] = BlackPawn;
7319         if(oldEP & EP_BEROLIN_A) {
7320                 captured = board[fromY][fromX-1];
7321                 board[fromY][fromX-1] = EmptySquare;
7322         }else{  captured = board[fromY][fromX+1];
7323                 board[fromY][fromX+1] = EmptySquare;
7324         }
7325     } else {
7326         board[toY][toX] = board[fromY][fromX];
7327         board[fromY][fromX] = EmptySquare;
7328     }
7329
7330     /* [HGM] now we promote for Shogi, if needed */
7331     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7332         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7333   }
7334
7335     if (gameInfo.holdingsWidth != 0) {
7336
7337       /* !!A lot more code needs to be written to support holdings  */
7338       /* [HGM] OK, so I have written it. Holdings are stored in the */
7339       /* penultimate board files, so they are automaticlly stored   */
7340       /* in the game history.                                       */
7341       if (fromY == DROP_RANK) {
7342         /* Delete from holdings, by decreasing count */
7343         /* and erasing image if necessary            */
7344         p = (int) fromX;
7345         if(p < (int) BlackPawn) { /* white drop */
7346              p -= (int)WhitePawn;
7347              if(p >= gameInfo.holdingsSize) p = 0;
7348              if(--board[p][BOARD_WIDTH-2] == 0)
7349                   board[p][BOARD_WIDTH-1] = EmptySquare;
7350         } else {                  /* black drop */
7351              p -= (int)BlackPawn;
7352              if(p >= gameInfo.holdingsSize) p = 0;
7353              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7354                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7355         }
7356       }
7357       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7358           && gameInfo.variant != VariantBughouse        ) {
7359         /* [HGM] holdings: Add to holdings, if holdings exist */
7360         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7361                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7362                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7363         }
7364         p = (int) captured;
7365         if (p >= (int) BlackPawn) {
7366           p -= (int)BlackPawn;
7367           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7368                   /* in Shogi restore piece to its original  first */
7369                   captured = (ChessSquare) (DEMOTED captured);
7370                   p = DEMOTED p;
7371           }
7372           p = PieceToNumber((ChessSquare)p);
7373           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7374           board[p][BOARD_WIDTH-2]++;
7375           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7376         } else {
7377           p -= (int)WhitePawn;
7378           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7379                   captured = (ChessSquare) (DEMOTED captured);
7380                   p = DEMOTED p;
7381           }
7382           p = PieceToNumber((ChessSquare)p);
7383           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7384           board[BOARD_HEIGHT-1-p][1]++;
7385           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7386         }
7387       }
7388
7389     } else if (gameInfo.variant == VariantAtomic) {
7390       if (captured != EmptySquare) {
7391         int y, x;
7392         for (y = toY-1; y <= toY+1; y++) {
7393           for (x = toX-1; x <= toX+1; x++) {
7394             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7395                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7396               board[y][x] = EmptySquare;
7397             }
7398           }
7399         }
7400         board[toY][toX] = EmptySquare;
7401       }
7402     }
7403     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7404         /* [HGM] Shogi promotions */
7405         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7406     }
7407
7408     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7409                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7410         // [HGM] superchess: take promotion piece out of holdings
7411         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7412         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7413             if(!--board[k][BOARD_WIDTH-2])
7414                 board[k][BOARD_WIDTH-1] = EmptySquare;
7415         } else {
7416             if(!--board[BOARD_HEIGHT-1-k][1])
7417                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7418         }
7419     }
7420
7421 }
7422
7423 /* Updates forwardMostMove */
7424 void
7425 MakeMove(fromX, fromY, toX, toY, promoChar)
7426      int fromX, fromY, toX, toY;
7427      int promoChar;
7428 {
7429 //    forwardMostMove++; // [HGM] bare: moved downstream
7430
7431     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7432         int timeLeft; static int lastLoadFlag=0; int king, piece;
7433         piece = boards[forwardMostMove][fromY][fromX];
7434         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7435         if(gameInfo.variant == VariantKnightmate)
7436             king += (int) WhiteUnicorn - (int) WhiteKing;
7437         if(forwardMostMove == 0) {
7438             if(blackPlaysFirst) 
7439                 fprintf(serverMoves, "%s;", second.tidy);
7440             fprintf(serverMoves, "%s;", first.tidy);
7441             if(!blackPlaysFirst) 
7442                 fprintf(serverMoves, "%s;", second.tidy);
7443         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7444         lastLoadFlag = loadFlag;
7445         // print base move
7446         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7447         // print castling suffix
7448         if( toY == fromY && piece == king ) {
7449             if(toX-fromX > 1)
7450                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7451             if(fromX-toX >1)
7452                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7453         }
7454         // e.p. suffix
7455         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7456              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7457              boards[forwardMostMove][toY][toX] == EmptySquare
7458              && fromX != toX )
7459                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7460         // promotion suffix
7461         if(promoChar != NULLCHAR)
7462                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7463         if(!loadFlag) {
7464             fprintf(serverMoves, "/%d/%d",
7465                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7466             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7467             else                      timeLeft = blackTimeRemaining/1000;
7468             fprintf(serverMoves, "/%d", timeLeft);
7469         }
7470         fflush(serverMoves);
7471     }
7472
7473     if (forwardMostMove+1 >= MAX_MOVES) {
7474       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7475                         0, 1);
7476       return;
7477     }
7478     if (commentList[forwardMostMove+1] != NULL) {
7479         free(commentList[forwardMostMove+1]);
7480         commentList[forwardMostMove+1] = NULL;
7481     }
7482     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7483     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7484     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7485                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7486     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7487     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7488     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7489     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7490     gameInfo.result = GameUnfinished;
7491     if (gameInfo.resultDetails != NULL) {
7492         free(gameInfo.resultDetails);
7493         gameInfo.resultDetails = NULL;
7494     }
7495     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7496                               moveList[forwardMostMove - 1]);
7497     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7498                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7499                              fromY, fromX, toY, toX, promoChar,
7500                              parseList[forwardMostMove - 1]);
7501     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7502                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7503                             castlingRights[forwardMostMove]) ) {
7504       case MT_NONE:
7505       case MT_STALEMATE:
7506       default:
7507         break;
7508       case MT_CHECK:
7509         if(gameInfo.variant != VariantShogi)
7510             strcat(parseList[forwardMostMove - 1], "+");
7511         break;
7512       case MT_CHECKMATE:
7513       case MT_STAINMATE:
7514         strcat(parseList[forwardMostMove - 1], "#");
7515         break;
7516     }
7517     if (appData.debugMode) {
7518         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7519     }
7520
7521 }
7522
7523 /* Updates currentMove if not pausing */
7524 void
7525 ShowMove(fromX, fromY, toX, toY)
7526 {
7527     int instant = (gameMode == PlayFromGameFile) ?
7528         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7529     if(appData.noGUI) return;
7530     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7531         if (!instant) {
7532             if (forwardMostMove == currentMove + 1) {
7533                 AnimateMove(boards[forwardMostMove - 1],
7534                             fromX, fromY, toX, toY);
7535             }
7536             if (appData.highlightLastMove) {
7537                 SetHighlights(fromX, fromY, toX, toY);
7538             }
7539         }
7540         currentMove = forwardMostMove;
7541     }
7542
7543     if (instant) return;
7544
7545     DisplayMove(currentMove - 1);
7546     DrawPosition(FALSE, boards[currentMove]);
7547     DisplayBothClocks();
7548     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7549 }
7550
7551 void SendEgtPath(ChessProgramState *cps)
7552 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7553         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7554
7555         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7556
7557         while(*p) {
7558             char c, *q = name+1, *r, *s;
7559
7560             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7561             while(*p && *p != ',') *q++ = *p++;
7562             *q++ = ':'; *q = 0;
7563             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7564                 strcmp(name, ",nalimov:") == 0 ) {
7565                 // take nalimov path from the menu-changeable option first, if it is defined
7566                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7567                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7568             } else
7569             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7570                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7571                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7572                 s = r = StrStr(s, ":") + 1; // beginning of path info
7573                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7574                 c = *r; *r = 0;             // temporarily null-terminate path info
7575                     *--q = 0;               // strip of trailig ':' from name
7576                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7577                 *r = c;
7578                 SendToProgram(buf,cps);     // send egtbpath command for this format
7579             }
7580             if(*p == ',') p++; // read away comma to position for next format name
7581         }
7582 }
7583
7584 void
7585 InitChessProgram(cps, setup)
7586      ChessProgramState *cps;
7587      int setup; /* [HGM] needed to setup FRC opening position */
7588 {
7589     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7590     if (appData.noChessProgram) return;
7591     hintRequested = FALSE;
7592     bookRequested = FALSE;
7593
7594     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7595     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7596     if(cps->memSize) { /* [HGM] memory */
7597         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7598         SendToProgram(buf, cps);
7599     }
7600     SendEgtPath(cps); /* [HGM] EGT */
7601     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7602         sprintf(buf, "cores %d\n", appData.smpCores);
7603         SendToProgram(buf, cps);
7604     }
7605
7606     SendToProgram(cps->initString, cps);
7607     if (gameInfo.variant != VariantNormal &&
7608         gameInfo.variant != VariantLoadable
7609         /* [HGM] also send variant if board size non-standard */
7610         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7611                                             ) {
7612       char *v = VariantName(gameInfo.variant);
7613       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7614         /* [HGM] in protocol 1 we have to assume all variants valid */
7615         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7616         DisplayFatalError(buf, 0, 1);
7617         return;
7618       }
7619
7620       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7621       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7622       if( gameInfo.variant == VariantXiangqi )
7623            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7624       if( gameInfo.variant == VariantShogi )
7625            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7626       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7627            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7628       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7629                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7630            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7631       if( gameInfo.variant == VariantCourier )
7632            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7633       if( gameInfo.variant == VariantSuper )
7634            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7635       if( gameInfo.variant == VariantGreat )
7636            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7637
7638       if(overruled) {
7639            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7640                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7641            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7642            if(StrStr(cps->variants, b) == NULL) { 
7643                // specific sized variant not known, check if general sizing allowed
7644                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7645                    if(StrStr(cps->variants, "boardsize") == NULL) {
7646                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7647                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7648                        DisplayFatalError(buf, 0, 1);
7649                        return;
7650                    }
7651                    /* [HGM] here we really should compare with the maximum supported board size */
7652                }
7653            }
7654       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7655       sprintf(buf, "variant %s\n", b);
7656       SendToProgram(buf, cps);
7657     }
7658     currentlyInitializedVariant = gameInfo.variant;
7659
7660     /* [HGM] send opening position in FRC to first engine */
7661     if(setup) {
7662           SendToProgram("force\n", cps);
7663           SendBoard(cps, 0);
7664           /* engine is now in force mode! Set flag to wake it up after first move. */
7665           setboardSpoiledMachineBlack = 1;
7666     }
7667
7668     if (cps->sendICS) {
7669       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7670       SendToProgram(buf, cps);
7671     }
7672     cps->maybeThinking = FALSE;
7673     cps->offeredDraw = 0;
7674     if (!appData.icsActive) {
7675         SendTimeControl(cps, movesPerSession, timeControl,
7676                         timeIncrement, appData.searchDepth,
7677                         searchTime);
7678     }
7679     if (appData.showThinking 
7680         // [HGM] thinking: four options require thinking output to be sent
7681         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7682                                 ) {
7683         SendToProgram("post\n", cps);
7684     }
7685     SendToProgram("hard\n", cps);
7686     if (!appData.ponderNextMove) {
7687         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7688            it without being sure what state we are in first.  "hard"
7689            is not a toggle, so that one is OK.
7690          */
7691         SendToProgram("easy\n", cps);
7692     }
7693     if (cps->usePing) {
7694       sprintf(buf, "ping %d\n", ++cps->lastPing);
7695       SendToProgram(buf, cps);
7696     }
7697     cps->initDone = TRUE;
7698 }   
7699
7700
7701 void
7702 StartChessProgram(cps)
7703      ChessProgramState *cps;
7704 {
7705     char buf[MSG_SIZ];
7706     int err;
7707
7708     if (appData.noChessProgram) return;
7709     cps->initDone = FALSE;
7710
7711     if (strcmp(cps->host, "localhost") == 0) {
7712         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7713     } else if (*appData.remoteShell == NULLCHAR) {
7714         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7715     } else {
7716         if (*appData.remoteUser == NULLCHAR) {
7717           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7718                     cps->program);
7719         } else {
7720           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7721                     cps->host, appData.remoteUser, cps->program);
7722         }
7723         err = StartChildProcess(buf, "", &cps->pr);
7724     }
7725     
7726     if (err != 0) {
7727         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7728         DisplayFatalError(buf, err, 1);
7729         cps->pr = NoProc;
7730         cps->isr = NULL;
7731         return;
7732     }
7733     
7734     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7735     if (cps->protocolVersion > 1) {
7736       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7737       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7738       cps->comboCnt = 0;  //                and values of combo boxes
7739       SendToProgram(buf, cps);
7740     } else {
7741       SendToProgram("xboard\n", cps);
7742     }
7743 }
7744
7745
7746 void
7747 TwoMachinesEventIfReady P((void))
7748 {
7749   if (first.lastPing != first.lastPong) {
7750     DisplayMessage("", _("Waiting for first chess program"));
7751     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7752     return;
7753   }
7754   if (second.lastPing != second.lastPong) {
7755     DisplayMessage("", _("Waiting for second chess program"));
7756     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7757     return;
7758   }
7759   ThawUI();
7760   TwoMachinesEvent();
7761 }
7762
7763 void
7764 NextMatchGame P((void))
7765 {
7766     int index; /* [HGM] autoinc: step lod index during match */
7767     Reset(FALSE, TRUE);
7768     if (*appData.loadGameFile != NULLCHAR) {
7769         index = appData.loadGameIndex;
7770         if(index < 0) { // [HGM] autoinc
7771             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7772             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7773         } 
7774         LoadGameFromFile(appData.loadGameFile,
7775                          index,
7776                          appData.loadGameFile, FALSE);
7777     } else if (*appData.loadPositionFile != NULLCHAR) {
7778         index = appData.loadPositionIndex;
7779         if(index < 0) { // [HGM] autoinc
7780             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7781             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7782         } 
7783         LoadPositionFromFile(appData.loadPositionFile,
7784                              index,
7785                              appData.loadPositionFile);
7786     }
7787     TwoMachinesEventIfReady();
7788 }
7789
7790 void UserAdjudicationEvent( int result )
7791 {
7792     ChessMove gameResult = GameIsDrawn;
7793
7794     if( result > 0 ) {
7795         gameResult = WhiteWins;
7796     }
7797     else if( result < 0 ) {
7798         gameResult = BlackWins;
7799     }
7800
7801     if( gameMode == TwoMachinesPlay ) {
7802         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7803     }
7804 }
7805
7806
7807 // [HGM] save: calculate checksum of game to make games easily identifiable
7808 int StringCheckSum(char *s)
7809 {
7810         int i = 0;
7811         if(s==NULL) return 0;
7812         while(*s) i = i*259 + *s++;
7813         return i;
7814 }
7815
7816 int GameCheckSum()
7817 {
7818         int i, sum=0;
7819         for(i=backwardMostMove; i<forwardMostMove; i++) {
7820                 sum += pvInfoList[i].depth;
7821                 sum += StringCheckSum(parseList[i]);
7822                 sum += StringCheckSum(commentList[i]);
7823                 sum *= 261;
7824         }
7825         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7826         return sum + StringCheckSum(commentList[i]);
7827 } // end of save patch
7828
7829 void
7830 GameEnds(result, resultDetails, whosays)
7831      ChessMove result;
7832      char *resultDetails;
7833      int whosays;
7834 {
7835     GameMode nextGameMode;
7836     int isIcsGame;
7837     char buf[MSG_SIZ];
7838
7839     if(endingGame) return; /* [HGM] crash: forbid recursion */
7840     endingGame = 1;
7841
7842     if (appData.debugMode) {
7843       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7844               result, resultDetails ? resultDetails : "(null)", whosays);
7845     }
7846
7847     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7848         /* If we are playing on ICS, the server decides when the
7849            game is over, but the engine can offer to draw, claim 
7850            a draw, or resign. 
7851          */
7852 #if ZIPPY
7853         if (appData.zippyPlay && first.initDone) {
7854             if (result == GameIsDrawn) {
7855                 /* In case draw still needs to be claimed */
7856                 SendToICS(ics_prefix);
7857                 SendToICS("draw\n");
7858             } else if (StrCaseStr(resultDetails, "resign")) {
7859                 SendToICS(ics_prefix);
7860                 SendToICS("resign\n");
7861             }
7862         }
7863 #endif
7864         endingGame = 0; /* [HGM] crash */
7865         return;
7866     }
7867
7868     /* If we're loading the game from a file, stop */
7869     if (whosays == GE_FILE) {
7870       (void) StopLoadGameTimer();
7871       gameFileFP = NULL;
7872     }
7873
7874     /* Cancel draw offers */
7875     first.offeredDraw = second.offeredDraw = 0;
7876
7877     /* If this is an ICS game, only ICS can really say it's done;
7878        if not, anyone can. */
7879     isIcsGame = (gameMode == IcsPlayingWhite || 
7880                  gameMode == IcsPlayingBlack || 
7881                  gameMode == IcsObserving    || 
7882                  gameMode == IcsExamining);
7883
7884     if (!isIcsGame || whosays == GE_ICS) {
7885         /* OK -- not an ICS game, or ICS said it was done */
7886         StopClocks();
7887         if (!isIcsGame && !appData.noChessProgram) 
7888           SetUserThinkingEnables();
7889     
7890         /* [HGM] if a machine claims the game end we verify this claim */
7891         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7892             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7893                 char claimer;
7894                 ChessMove trueResult = (ChessMove) -1;
7895
7896                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7897                                             first.twoMachinesColor[0] :
7898                                             second.twoMachinesColor[0] ;
7899
7900                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7901                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7902                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7903                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7904                 } else
7905                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7906                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7907                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7908                 } else
7909                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7910                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7911                 }
7912
7913                 // now verify win claims, but not in drop games, as we don't understand those yet
7914                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7915                                                  || gameInfo.variant == VariantGreat) &&
7916                     (result == WhiteWins && claimer == 'w' ||
7917                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7918                       if (appData.debugMode) {
7919                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7920                                 result, epStatus[forwardMostMove], forwardMostMove);
7921                       }
7922                       if(result != trueResult) {
7923                               sprintf(buf, "False win claim: '%s'", resultDetails);
7924                               result = claimer == 'w' ? BlackWins : WhiteWins;
7925                               resultDetails = buf;
7926                       }
7927                 } else
7928                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7929                     && (forwardMostMove <= backwardMostMove ||
7930                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7931                         (claimer=='b')==(forwardMostMove&1))
7932                                                                                   ) {
7933                       /* [HGM] verify: draws that were not flagged are false claims */
7934                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7935                       result = claimer == 'w' ? BlackWins : WhiteWins;
7936                       resultDetails = buf;
7937                 }
7938                 /* (Claiming a loss is accepted no questions asked!) */
7939             }
7940             /* [HGM] bare: don't allow bare King to win */
7941             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7942                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7943                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7944                && result != GameIsDrawn)
7945             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7946                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7947                         int p = (int)boards[forwardMostMove][i][j] - color;
7948                         if(p >= 0 && p <= (int)WhiteKing) k++;
7949                 }
7950                 if (appData.debugMode) {
7951                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7952                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7953                 }
7954                 if(k <= 1) {
7955                         result = GameIsDrawn;
7956                         sprintf(buf, "%s but bare king", resultDetails);
7957                         resultDetails = buf;
7958                 }
7959             }
7960         }
7961
7962
7963         if(serverMoves != NULL && !loadFlag) { char c = '=';
7964             if(result==WhiteWins) c = '+';
7965             if(result==BlackWins) c = '-';
7966             if(resultDetails != NULL)
7967                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7968         }
7969         if (resultDetails != NULL) {
7970             gameInfo.result = result;
7971             gameInfo.resultDetails = StrSave(resultDetails);
7972
7973             /* display last move only if game was not loaded from file */
7974             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7975                 DisplayMove(currentMove - 1);
7976     
7977             if (forwardMostMove != 0) {
7978                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7979                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7980                                                                 ) {
7981                     if (*appData.saveGameFile != NULLCHAR) {
7982                         SaveGameToFile(appData.saveGameFile, TRUE);
7983                     } else if (appData.autoSaveGames) {
7984                         AutoSaveGame();
7985                     }
7986                     if (*appData.savePositionFile != NULLCHAR) {
7987                         SavePositionToFile(appData.savePositionFile);
7988                     }
7989                 }
7990             }
7991
7992             /* Tell program how game ended in case it is learning */
7993             /* [HGM] Moved this to after saving the PGN, just in case */
7994             /* engine died and we got here through time loss. In that */
7995             /* case we will get a fatal error writing the pipe, which */
7996             /* would otherwise lose us the PGN.                       */
7997             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7998             /* output during GameEnds should never be fatal anymore   */
7999             if (gameMode == MachinePlaysWhite ||
8000                 gameMode == MachinePlaysBlack ||
8001                 gameMode == TwoMachinesPlay ||
8002                 gameMode == IcsPlayingWhite ||
8003                 gameMode == IcsPlayingBlack ||
8004                 gameMode == BeginningOfGame) {
8005                 char buf[MSG_SIZ];
8006                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8007                         resultDetails);
8008                 if (first.pr != NoProc) {
8009                     SendToProgram(buf, &first);
8010                 }
8011                 if (second.pr != NoProc &&
8012                     gameMode == TwoMachinesPlay) {
8013                     SendToProgram(buf, &second);
8014                 }
8015             }
8016         }
8017
8018         if (appData.icsActive) {
8019             if (appData.quietPlay &&
8020                 (gameMode == IcsPlayingWhite ||
8021                  gameMode == IcsPlayingBlack)) {
8022                 SendToICS(ics_prefix);
8023                 SendToICS("set shout 1\n");
8024             }
8025             nextGameMode = IcsIdle;
8026             ics_user_moved = FALSE;
8027             /* clean up premove.  It's ugly when the game has ended and the
8028              * premove highlights are still on the board.
8029              */
8030             if (gotPremove) {
8031               gotPremove = FALSE;
8032               ClearPremoveHighlights();
8033               DrawPosition(FALSE, boards[currentMove]);
8034             }
8035             if (whosays == GE_ICS) {
8036                 switch (result) {
8037                 case WhiteWins:
8038                     if (gameMode == IcsPlayingWhite)
8039                         PlayIcsWinSound();
8040                     else if(gameMode == IcsPlayingBlack)
8041                         PlayIcsLossSound();
8042                     break;
8043                 case BlackWins:
8044                     if (gameMode == IcsPlayingBlack)
8045                         PlayIcsWinSound();
8046                     else if(gameMode == IcsPlayingWhite)
8047                         PlayIcsLossSound();
8048                     break;
8049                 case GameIsDrawn:
8050                     PlayIcsDrawSound();
8051                     break;
8052                 default:
8053                     PlayIcsUnfinishedSound();
8054                 }
8055             }
8056         } else if (gameMode == EditGame ||
8057                    gameMode == PlayFromGameFile || 
8058                    gameMode == AnalyzeMode || 
8059                    gameMode == AnalyzeFile) {
8060             nextGameMode = gameMode;
8061         } else {
8062             nextGameMode = EndOfGame;
8063         }
8064         pausing = FALSE;
8065         ModeHighlight();
8066     } else {
8067         nextGameMode = gameMode;
8068     }
8069
8070     if (appData.noChessProgram) {
8071         gameMode = nextGameMode;
8072         ModeHighlight();
8073         endingGame = 0; /* [HGM] crash */
8074         return;
8075     }
8076
8077     if (first.reuse) {
8078         /* Put first chess program into idle state */
8079         if (first.pr != NoProc &&
8080             (gameMode == MachinePlaysWhite ||
8081              gameMode == MachinePlaysBlack ||
8082              gameMode == TwoMachinesPlay ||
8083              gameMode == IcsPlayingWhite ||
8084              gameMode == IcsPlayingBlack ||
8085              gameMode == BeginningOfGame)) {
8086             SendToProgram("force\n", &first);
8087             if (first.usePing) {
8088               char buf[MSG_SIZ];
8089               sprintf(buf, "ping %d\n", ++first.lastPing);
8090               SendToProgram(buf, &first);
8091             }
8092         }
8093     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8094         /* Kill off first chess program */
8095         if (first.isr != NULL)
8096           RemoveInputSource(first.isr);
8097         first.isr = NULL;
8098     
8099         if (first.pr != NoProc) {
8100             ExitAnalyzeMode();
8101             DoSleep( appData.delayBeforeQuit );
8102             SendToProgram("quit\n", &first);
8103             DoSleep( appData.delayAfterQuit );
8104             DestroyChildProcess(first.pr, first.useSigterm);
8105         }
8106         first.pr = NoProc;
8107     }
8108     if (second.reuse) {
8109         /* Put second chess program into idle state */
8110         if (second.pr != NoProc &&
8111             gameMode == TwoMachinesPlay) {
8112             SendToProgram("force\n", &second);
8113             if (second.usePing) {
8114               char buf[MSG_SIZ];
8115               sprintf(buf, "ping %d\n", ++second.lastPing);
8116               SendToProgram(buf, &second);
8117             }
8118         }
8119     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8120         /* Kill off second chess program */
8121         if (second.isr != NULL)
8122           RemoveInputSource(second.isr);
8123         second.isr = NULL;
8124     
8125         if (second.pr != NoProc) {
8126             DoSleep( appData.delayBeforeQuit );
8127             SendToProgram("quit\n", &second);
8128             DoSleep( appData.delayAfterQuit );
8129             DestroyChildProcess(second.pr, second.useSigterm);
8130         }
8131         second.pr = NoProc;
8132     }
8133
8134     if (matchMode && gameMode == TwoMachinesPlay) {
8135         switch (result) {
8136         case WhiteWins:
8137           if (first.twoMachinesColor[0] == 'w') {
8138             first.matchWins++;
8139           } else {
8140             second.matchWins++;
8141           }
8142           break;
8143         case BlackWins:
8144           if (first.twoMachinesColor[0] == 'b') {
8145             first.matchWins++;
8146           } else {
8147             second.matchWins++;
8148           }
8149           break;
8150         default:
8151           break;
8152         }
8153         if (matchGame < appData.matchGames) {
8154             char *tmp;
8155             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8156                 tmp = first.twoMachinesColor;
8157                 first.twoMachinesColor = second.twoMachinesColor;
8158                 second.twoMachinesColor = tmp;
8159             }
8160             gameMode = nextGameMode;
8161             matchGame++;
8162             if(appData.matchPause>10000 || appData.matchPause<10)
8163                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8164             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8165             endingGame = 0; /* [HGM] crash */
8166             return;
8167         } else {
8168             char buf[MSG_SIZ];
8169             gameMode = nextGameMode;
8170             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8171                     first.tidy, second.tidy,
8172                     first.matchWins, second.matchWins,
8173                     appData.matchGames - (first.matchWins + second.matchWins));
8174             DisplayFatalError(buf, 0, 0);
8175         }
8176     }
8177     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8178         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8179       ExitAnalyzeMode();
8180     gameMode = nextGameMode;
8181     ModeHighlight();
8182     endingGame = 0;  /* [HGM] crash */
8183 }
8184
8185 /* Assumes program was just initialized (initString sent).
8186    Leaves program in force mode. */
8187 void
8188 FeedMovesToProgram(cps, upto) 
8189      ChessProgramState *cps;
8190      int upto;
8191 {
8192     int i;
8193     
8194     if (appData.debugMode)
8195       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8196               startedFromSetupPosition ? "position and " : "",
8197               backwardMostMove, upto, cps->which);
8198     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8199         // [HGM] variantswitch: make engine aware of new variant
8200         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8201                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8202         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8203         SendToProgram(buf, cps);
8204         currentlyInitializedVariant = gameInfo.variant;
8205     }
8206     SendToProgram("force\n", cps);
8207     if (startedFromSetupPosition) {
8208         SendBoard(cps, backwardMostMove);
8209     if (appData.debugMode) {
8210         fprintf(debugFP, "feedMoves\n");
8211     }
8212     }
8213     for (i = backwardMostMove; i < upto; i++) {
8214         SendMoveToProgram(i, cps);
8215     }
8216 }
8217
8218
8219 void
8220 ResurrectChessProgram()
8221 {
8222      /* The chess program may have exited.
8223         If so, restart it and feed it all the moves made so far. */
8224
8225     if (appData.noChessProgram || first.pr != NoProc) return;
8226     
8227     StartChessProgram(&first);
8228     InitChessProgram(&first, FALSE);
8229     FeedMovesToProgram(&first, currentMove);
8230
8231     if (!first.sendTime) {
8232         /* can't tell gnuchess what its clock should read,
8233            so we bow to its notion. */
8234         ResetClocks();
8235         timeRemaining[0][currentMove] = whiteTimeRemaining;
8236         timeRemaining[1][currentMove] = blackTimeRemaining;
8237     }
8238
8239     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8240                 appData.icsEngineAnalyze) && first.analysisSupport) {
8241       SendToProgram("analyze\n", &first);
8242       first.analyzing = TRUE;
8243     }
8244 }
8245
8246 /*
8247  * Button procedures
8248  */
8249 void
8250 Reset(redraw, init)
8251      int redraw, init;
8252 {
8253     int i;
8254
8255     if (appData.debugMode) {
8256         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8257                 redraw, init, gameMode);
8258     }
8259     pausing = pauseExamInvalid = FALSE;
8260     startedFromSetupPosition = blackPlaysFirst = FALSE;
8261     firstMove = TRUE;
8262     whiteFlag = blackFlag = FALSE;
8263     userOfferedDraw = FALSE;
8264     hintRequested = bookRequested = FALSE;
8265     first.maybeThinking = FALSE;
8266     second.maybeThinking = FALSE;
8267     first.bookSuspend = FALSE; // [HGM] book
8268     second.bookSuspend = FALSE;
8269     thinkOutput[0] = NULLCHAR;
8270     lastHint[0] = NULLCHAR;
8271     ClearGameInfo(&gameInfo);
8272     gameInfo.variant = StringToVariant(appData.variant);
8273     ics_user_moved = ics_clock_paused = FALSE;
8274     ics_getting_history = H_FALSE;
8275     ics_gamenum = -1;
8276     white_holding[0] = black_holding[0] = NULLCHAR;
8277     ClearProgramStats();
8278     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8279     
8280     ResetFrontEnd();
8281     ClearHighlights();
8282     flipView = appData.flipView;
8283     ClearPremoveHighlights();
8284     gotPremove = FALSE;
8285     alarmSounded = FALSE;
8286
8287     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8288     if(appData.serverMovesName != NULL) {
8289         /* [HGM] prepare to make moves file for broadcasting */
8290         clock_t t = clock();
8291         if(serverMoves != NULL) fclose(serverMoves);
8292         serverMoves = fopen(appData.serverMovesName, "r");
8293         if(serverMoves != NULL) {
8294             fclose(serverMoves);
8295             /* delay 15 sec before overwriting, so all clients can see end */
8296             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8297         }
8298         serverMoves = fopen(appData.serverMovesName, "w");
8299     }
8300
8301     ExitAnalyzeMode();
8302     gameMode = BeginningOfGame;
8303     ModeHighlight();
8304     if(appData.icsActive) gameInfo.variant = VariantNormal;
8305     currentMove = forwardMostMove = backwardMostMove = 0;
8306     InitPosition(redraw);
8307     for (i = 0; i < MAX_MOVES; i++) {
8308         if (commentList[i] != NULL) {
8309             free(commentList[i]);
8310             commentList[i] = NULL;
8311         }
8312     }
8313     ResetClocks();
8314     timeRemaining[0][0] = whiteTimeRemaining;
8315     timeRemaining[1][0] = blackTimeRemaining;
8316     if (first.pr == NULL) {
8317         StartChessProgram(&first);
8318     }
8319     if (init) {
8320             InitChessProgram(&first, startedFromSetupPosition);
8321     }
8322     DisplayTitle("");
8323     DisplayMessage("", "");
8324     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8325     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8326 }
8327
8328 void
8329 AutoPlayGameLoop()
8330 {
8331     for (;;) {
8332         if (!AutoPlayOneMove())
8333           return;
8334         if (matchMode || appData.timeDelay == 0)
8335           continue;
8336         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8337           return;
8338         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8339         break;
8340     }
8341 }
8342
8343
8344 int
8345 AutoPlayOneMove()
8346 {
8347     int fromX, fromY, toX, toY;
8348
8349     if (appData.debugMode) {
8350       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8351     }
8352
8353     if (gameMode != PlayFromGameFile)
8354       return FALSE;
8355
8356     if (currentMove >= forwardMostMove) {
8357       gameMode = EditGame;
8358       ModeHighlight();
8359
8360       /* [AS] Clear current move marker at the end of a game */
8361       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8362
8363       return FALSE;
8364     }
8365     
8366     toX = moveList[currentMove][2] - AAA;
8367     toY = moveList[currentMove][3] - ONE;
8368
8369     if (moveList[currentMove][1] == '@') {
8370         if (appData.highlightLastMove) {
8371             SetHighlights(-1, -1, toX, toY);
8372         }
8373     } else {
8374         fromX = moveList[currentMove][0] - AAA;
8375         fromY = moveList[currentMove][1] - ONE;
8376
8377         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8378
8379         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8380
8381         if (appData.highlightLastMove) {
8382             SetHighlights(fromX, fromY, toX, toY);
8383         }
8384     }
8385     DisplayMove(currentMove);
8386     SendMoveToProgram(currentMove++, &first);
8387     DisplayBothClocks();
8388     DrawPosition(FALSE, boards[currentMove]);
8389     // [HGM] PV info: always display, routine tests if empty
8390     DisplayComment(currentMove - 1, commentList[currentMove]);
8391     return TRUE;
8392 }
8393
8394
8395 int
8396 LoadGameOneMove(readAhead)
8397      ChessMove readAhead;
8398 {
8399     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8400     char promoChar = NULLCHAR;
8401     ChessMove moveType;
8402     char move[MSG_SIZ];
8403     char *p, *q;
8404     
8405     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8406         gameMode != AnalyzeMode && gameMode != Training) {
8407         gameFileFP = NULL;
8408         return FALSE;
8409     }
8410     
8411     yyboardindex = forwardMostMove;
8412     if (readAhead != (ChessMove)0) {
8413       moveType = readAhead;
8414     } else {
8415       if (gameFileFP == NULL)
8416           return FALSE;
8417       moveType = (ChessMove) yylex();
8418     }
8419     
8420     done = FALSE;
8421     switch (moveType) {
8422       case Comment:
8423         if (appData.debugMode) 
8424           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8425         p = yy_text;
8426         if (*p == '{' || *p == '[' || *p == '(') {
8427             p[strlen(p) - 1] = NULLCHAR;
8428             p++;
8429         }
8430
8431         /* append the comment but don't display it */
8432         while (*p == '\n') p++;
8433         AppendComment(currentMove, p);
8434         return TRUE;
8435
8436       case WhiteCapturesEnPassant:
8437       case BlackCapturesEnPassant:
8438       case WhitePromotionChancellor:
8439       case BlackPromotionChancellor:
8440       case WhitePromotionArchbishop:
8441       case BlackPromotionArchbishop:
8442       case WhitePromotionCentaur:
8443       case BlackPromotionCentaur:
8444       case WhitePromotionQueen:
8445       case BlackPromotionQueen:
8446       case WhitePromotionRook:
8447       case BlackPromotionRook:
8448       case WhitePromotionBishop:
8449       case BlackPromotionBishop:
8450       case WhitePromotionKnight:
8451       case BlackPromotionKnight:
8452       case WhitePromotionKing:
8453       case BlackPromotionKing:
8454       case NormalMove:
8455       case WhiteKingSideCastle:
8456       case WhiteQueenSideCastle:
8457       case BlackKingSideCastle:
8458       case BlackQueenSideCastle:
8459       case WhiteKingSideCastleWild:
8460       case WhiteQueenSideCastleWild:
8461       case BlackKingSideCastleWild:
8462       case BlackQueenSideCastleWild:
8463       /* PUSH Fabien */
8464       case WhiteHSideCastleFR:
8465       case WhiteASideCastleFR:
8466       case BlackHSideCastleFR:
8467       case BlackASideCastleFR:
8468       /* POP Fabien */
8469         if (appData.debugMode)
8470           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8471         fromX = currentMoveString[0] - AAA;
8472         fromY = currentMoveString[1] - ONE;
8473         toX = currentMoveString[2] - AAA;
8474         toY = currentMoveString[3] - ONE;
8475         promoChar = currentMoveString[4];
8476         break;
8477
8478       case WhiteDrop:
8479       case BlackDrop:
8480         if (appData.debugMode)
8481           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8482         fromX = moveType == WhiteDrop ?
8483           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8484         (int) CharToPiece(ToLower(currentMoveString[0]));
8485         fromY = DROP_RANK;
8486         toX = currentMoveString[2] - AAA;
8487         toY = currentMoveString[3] - ONE;
8488         break;
8489
8490       case WhiteWins:
8491       case BlackWins:
8492       case GameIsDrawn:
8493       case GameUnfinished:
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8496         p = strchr(yy_text, '{');
8497         if (p == NULL) p = strchr(yy_text, '(');
8498         if (p == NULL) {
8499             p = yy_text;
8500             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8501         } else {
8502             q = strchr(p, *p == '{' ? '}' : ')');
8503             if (q != NULL) *q = NULLCHAR;
8504             p++;
8505         }
8506         GameEnds(moveType, p, GE_FILE);
8507         done = TRUE;
8508         if (cmailMsgLoaded) {
8509             ClearHighlights();
8510             flipView = WhiteOnMove(currentMove);
8511             if (moveType == GameUnfinished) flipView = !flipView;
8512             if (appData.debugMode)
8513               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8514         }
8515         break;
8516
8517       case (ChessMove) 0:       /* end of file */
8518         if (appData.debugMode)
8519           fprintf(debugFP, "Parser hit end of file\n");
8520         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8521                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8522           case MT_NONE:
8523           case MT_CHECK:
8524             break;
8525           case MT_CHECKMATE:
8526           case MT_STAINMATE:
8527             if (WhiteOnMove(currentMove)) {
8528                 GameEnds(BlackWins, "Black mates", GE_FILE);
8529             } else {
8530                 GameEnds(WhiteWins, "White mates", GE_FILE);
8531             }
8532             break;
8533           case MT_STALEMATE:
8534             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8535             break;
8536         }
8537         done = TRUE;
8538         break;
8539
8540       case MoveNumberOne:
8541         if (lastLoadGameStart == GNUChessGame) {
8542             /* GNUChessGames have numbers, but they aren't move numbers */
8543             if (appData.debugMode)
8544               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8545                       yy_text, (int) moveType);
8546             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8547         }
8548         /* else fall thru */
8549
8550       case XBoardGame:
8551       case GNUChessGame:
8552       case PGNTag:
8553         /* Reached start of next game in file */
8554         if (appData.debugMode)
8555           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8556         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8557                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8558           case MT_NONE:
8559           case MT_CHECK:
8560             break;
8561           case MT_CHECKMATE:
8562           case MT_STAINMATE:
8563             if (WhiteOnMove(currentMove)) {
8564                 GameEnds(BlackWins, "Black mates", GE_FILE);
8565             } else {
8566                 GameEnds(WhiteWins, "White mates", GE_FILE);
8567             }
8568             break;
8569           case MT_STALEMATE:
8570             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8571             break;
8572         }
8573         done = TRUE;
8574         break;
8575
8576       case PositionDiagram:     /* should not happen; ignore */
8577       case ElapsedTime:         /* ignore */
8578       case NAG:                 /* ignore */
8579         if (appData.debugMode)
8580           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8581                   yy_text, (int) moveType);
8582         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8583
8584       case IllegalMove:
8585         if (appData.testLegality) {
8586             if (appData.debugMode)
8587               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8588             sprintf(move, _("Illegal move: %d.%s%s"),
8589                     (forwardMostMove / 2) + 1,
8590                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8591             DisplayError(move, 0);
8592             done = TRUE;
8593         } else {
8594             if (appData.debugMode)
8595               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8596                       yy_text, currentMoveString);
8597             fromX = currentMoveString[0] - AAA;
8598             fromY = currentMoveString[1] - ONE;
8599             toX = currentMoveString[2] - AAA;
8600             toY = currentMoveString[3] - ONE;
8601             promoChar = currentMoveString[4];
8602         }
8603         break;
8604
8605       case AmbiguousMove:
8606         if (appData.debugMode)
8607           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8608         sprintf(move, _("Ambiguous move: %d.%s%s"),
8609                 (forwardMostMove / 2) + 1,
8610                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8611         DisplayError(move, 0);
8612         done = TRUE;
8613         break;
8614
8615       default:
8616       case ImpossibleMove:
8617         if (appData.debugMode)
8618           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8619         sprintf(move, _("Illegal move: %d.%s%s"),
8620                 (forwardMostMove / 2) + 1,
8621                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8622         DisplayError(move, 0);
8623         done = TRUE;
8624         break;
8625     }
8626
8627     if (done) {
8628         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8629             DrawPosition(FALSE, boards[currentMove]);
8630             DisplayBothClocks();
8631             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8632               DisplayComment(currentMove - 1, commentList[currentMove]);
8633         }
8634         (void) StopLoadGameTimer();
8635         gameFileFP = NULL;
8636         cmailOldMove = forwardMostMove;
8637         return FALSE;
8638     } else {
8639         /* currentMoveString is set as a side-effect of yylex */
8640         strcat(currentMoveString, "\n");
8641         strcpy(moveList[forwardMostMove], currentMoveString);
8642         
8643         thinkOutput[0] = NULLCHAR;
8644         MakeMove(fromX, fromY, toX, toY, promoChar);
8645         currentMove = forwardMostMove;
8646         return TRUE;
8647     }
8648 }
8649
8650 /* Load the nth game from the given file */
8651 int
8652 LoadGameFromFile(filename, n, title, useList)
8653      char *filename;
8654      int n;
8655      char *title;
8656      /*Boolean*/ int useList;
8657 {
8658     FILE *f;
8659     char buf[MSG_SIZ];
8660
8661     if (strcmp(filename, "-") == 0) {
8662         f = stdin;
8663         title = "stdin";
8664     } else {
8665         f = fopen(filename, "rb");
8666         if (f == NULL) {
8667           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8668             DisplayError(buf, errno);
8669             return FALSE;
8670         }
8671     }
8672     if (fseek(f, 0, 0) == -1) {
8673         /* f is not seekable; probably a pipe */
8674         useList = FALSE;
8675     }
8676     if (useList && n == 0) {
8677         int error = GameListBuild(f);
8678         if (error) {
8679             DisplayError(_("Cannot build game list"), error);
8680         } else if (!ListEmpty(&gameList) &&
8681                    ((ListGame *) gameList.tailPred)->number > 1) {
8682             GameListPopUp(f, title);
8683             return TRUE;
8684         }
8685         GameListDestroy();
8686         n = 1;
8687     }
8688     if (n == 0) n = 1;
8689     return LoadGame(f, n, title, FALSE);
8690 }
8691
8692
8693 void
8694 MakeRegisteredMove()
8695 {
8696     int fromX, fromY, toX, toY;
8697     char promoChar;
8698     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8699         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8700           case CMAIL_MOVE:
8701           case CMAIL_DRAW:
8702             if (appData.debugMode)
8703               fprintf(debugFP, "Restoring %s for game %d\n",
8704                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8705     
8706             thinkOutput[0] = NULLCHAR;
8707             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8708             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8709             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8710             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8711             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8712             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8713             MakeMove(fromX, fromY, toX, toY, promoChar);
8714             ShowMove(fromX, fromY, toX, toY);
8715               
8716             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8717                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8718               case MT_NONE:
8719               case MT_CHECK:
8720                 break;
8721                 
8722               case MT_CHECKMATE:
8723               case MT_STAINMATE:
8724                 if (WhiteOnMove(currentMove)) {
8725                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8726                 } else {
8727                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8728                 }
8729                 break;
8730                 
8731               case MT_STALEMATE:
8732                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8733                 break;
8734             }
8735
8736             break;
8737             
8738           case CMAIL_RESIGN:
8739             if (WhiteOnMove(currentMove)) {
8740                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8741             } else {
8742                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8743             }
8744             break;
8745             
8746           case CMAIL_ACCEPT:
8747             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8748             break;
8749               
8750           default:
8751             break;
8752         }
8753     }
8754
8755     return;
8756 }
8757
8758 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8759 int
8760 CmailLoadGame(f, gameNumber, title, useList)
8761      FILE *f;
8762      int gameNumber;
8763      char *title;
8764      int useList;
8765 {
8766     int retVal;
8767
8768     if (gameNumber > nCmailGames) {
8769         DisplayError(_("No more games in this message"), 0);
8770         return FALSE;
8771     }
8772     if (f == lastLoadGameFP) {
8773         int offset = gameNumber - lastLoadGameNumber;
8774         if (offset == 0) {
8775             cmailMsg[0] = NULLCHAR;
8776             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8777                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8778                 nCmailMovesRegistered--;
8779             }
8780             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8781             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8782                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8783             }
8784         } else {
8785             if (! RegisterMove()) return FALSE;
8786         }
8787     }
8788
8789     retVal = LoadGame(f, gameNumber, title, useList);
8790
8791     /* Make move registered during previous look at this game, if any */
8792     MakeRegisteredMove();
8793
8794     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8795         commentList[currentMove]
8796           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8797         DisplayComment(currentMove - 1, commentList[currentMove]);
8798     }
8799
8800     return retVal;
8801 }
8802
8803 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8804 int
8805 ReloadGame(offset)
8806      int offset;
8807 {
8808     int gameNumber = lastLoadGameNumber + offset;
8809     if (lastLoadGameFP == NULL) {
8810         DisplayError(_("No game has been loaded yet"), 0);
8811         return FALSE;
8812     }
8813     if (gameNumber <= 0) {
8814         DisplayError(_("Can't back up any further"), 0);
8815         return FALSE;
8816     }
8817     if (cmailMsgLoaded) {
8818         return CmailLoadGame(lastLoadGameFP, gameNumber,
8819                              lastLoadGameTitle, lastLoadGameUseList);
8820     } else {
8821         return LoadGame(lastLoadGameFP, gameNumber,
8822                         lastLoadGameTitle, lastLoadGameUseList);
8823     }
8824 }
8825
8826
8827
8828 /* Load the nth game from open file f */
8829 int
8830 LoadGame(f, gameNumber, title, useList)
8831      FILE *f;
8832      int gameNumber;
8833      char *title;
8834      int useList;
8835 {
8836     ChessMove cm;
8837     char buf[MSG_SIZ];
8838     int gn = gameNumber;
8839     ListGame *lg = NULL;
8840     int numPGNTags = 0;
8841     int err;
8842     GameMode oldGameMode;
8843     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8844
8845     if (appData.debugMode) 
8846         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8847
8848     if (gameMode == Training )
8849         SetTrainingModeOff();
8850
8851     oldGameMode = gameMode;
8852     if (gameMode != BeginningOfGame) {
8853       Reset(FALSE, TRUE);
8854     }
8855
8856     gameFileFP = f;
8857     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8858         fclose(lastLoadGameFP);
8859     }
8860
8861     if (useList) {
8862         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8863         
8864         if (lg) {
8865             fseek(f, lg->offset, 0);
8866             GameListHighlight(gameNumber);
8867             gn = 1;
8868         }
8869         else {
8870             DisplayError(_("Game number out of range"), 0);
8871             return FALSE;
8872         }
8873     } else {
8874         GameListDestroy();
8875         if (fseek(f, 0, 0) == -1) {
8876             if (f == lastLoadGameFP ?
8877                 gameNumber == lastLoadGameNumber + 1 :
8878                 gameNumber == 1) {
8879                 gn = 1;
8880             } else {
8881                 DisplayError(_("Can't seek on game file"), 0);
8882                 return FALSE;
8883             }
8884         }
8885     }
8886     lastLoadGameFP = f;
8887     lastLoadGameNumber = gameNumber;
8888     strcpy(lastLoadGameTitle, title);
8889     lastLoadGameUseList = useList;
8890
8891     yynewfile(f);
8892
8893     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8894       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8895                 lg->gameInfo.black);
8896             DisplayTitle(buf);
8897     } else if (*title != NULLCHAR) {
8898         if (gameNumber > 1) {
8899             sprintf(buf, "%s %d", title, gameNumber);
8900             DisplayTitle(buf);
8901         } else {
8902             DisplayTitle(title);
8903         }
8904     }
8905
8906     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8907         gameMode = PlayFromGameFile;
8908         ModeHighlight();
8909     }
8910
8911     currentMove = forwardMostMove = backwardMostMove = 0;
8912     CopyBoard(boards[0], initialPosition);
8913     StopClocks();
8914
8915     /*
8916      * Skip the first gn-1 games in the file.
8917      * Also skip over anything that precedes an identifiable 
8918      * start of game marker, to avoid being confused by 
8919      * garbage at the start of the file.  Currently 
8920      * recognized start of game markers are the move number "1",
8921      * the pattern "gnuchess .* game", the pattern
8922      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8923      * A game that starts with one of the latter two patterns
8924      * will also have a move number 1, possibly
8925      * following a position diagram.
8926      * 5-4-02: Let's try being more lenient and allowing a game to
8927      * start with an unnumbered move.  Does that break anything?
8928      */
8929     cm = lastLoadGameStart = (ChessMove) 0;
8930     while (gn > 0) {
8931         yyboardindex = forwardMostMove;
8932         cm = (ChessMove) yylex();
8933         switch (cm) {
8934           case (ChessMove) 0:
8935             if (cmailMsgLoaded) {
8936                 nCmailGames = CMAIL_MAX_GAMES - gn;
8937             } else {
8938                 Reset(TRUE, TRUE);
8939                 DisplayError(_("Game not found in file"), 0);
8940             }
8941             return FALSE;
8942
8943           case GNUChessGame:
8944           case XBoardGame:
8945             gn--;
8946             lastLoadGameStart = cm;
8947             break;
8948             
8949           case MoveNumberOne:
8950             switch (lastLoadGameStart) {
8951               case GNUChessGame:
8952               case XBoardGame:
8953               case PGNTag:
8954                 break;
8955               case MoveNumberOne:
8956               case (ChessMove) 0:
8957                 gn--;           /* count this game */
8958                 lastLoadGameStart = cm;
8959                 break;
8960               default:
8961                 /* impossible */
8962                 break;
8963             }
8964             break;
8965
8966           case PGNTag:
8967             switch (lastLoadGameStart) {
8968               case GNUChessGame:
8969               case PGNTag:
8970               case MoveNumberOne:
8971               case (ChessMove) 0:
8972                 gn--;           /* count this game */
8973                 lastLoadGameStart = cm;
8974                 break;
8975               case XBoardGame:
8976                 lastLoadGameStart = cm; /* game counted already */
8977                 break;
8978               default:
8979                 /* impossible */
8980                 break;
8981             }
8982             if (gn > 0) {
8983                 do {
8984                     yyboardindex = forwardMostMove;
8985                     cm = (ChessMove) yylex();
8986                 } while (cm == PGNTag || cm == Comment);
8987             }
8988             break;
8989
8990           case WhiteWins:
8991           case BlackWins:
8992           case GameIsDrawn:
8993             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8994                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8995                     != CMAIL_OLD_RESULT) {
8996                     nCmailResults ++ ;
8997                     cmailResult[  CMAIL_MAX_GAMES
8998                                 - gn - 1] = CMAIL_OLD_RESULT;
8999                 }
9000             }
9001             break;
9002
9003           case NormalMove:
9004             /* Only a NormalMove can be at the start of a game
9005              * without a position diagram. */
9006             if (lastLoadGameStart == (ChessMove) 0) {
9007               gn--;
9008               lastLoadGameStart = MoveNumberOne;
9009             }
9010             break;
9011
9012           default:
9013             break;
9014         }
9015     }
9016     
9017     if (appData.debugMode)
9018       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9019
9020     if (cm == XBoardGame) {
9021         /* Skip any header junk before position diagram and/or move 1 */
9022         for (;;) {
9023             yyboardindex = forwardMostMove;
9024             cm = (ChessMove) yylex();
9025
9026             if (cm == (ChessMove) 0 ||
9027                 cm == GNUChessGame || cm == XBoardGame) {
9028                 /* Empty game; pretend end-of-file and handle later */
9029                 cm = (ChessMove) 0;
9030                 break;
9031             }
9032
9033             if (cm == MoveNumberOne || cm == PositionDiagram ||
9034                 cm == PGNTag || cm == Comment)
9035               break;
9036         }
9037     } else if (cm == GNUChessGame) {
9038         if (gameInfo.event != NULL) {
9039             free(gameInfo.event);
9040         }
9041         gameInfo.event = StrSave(yy_text);
9042     }   
9043
9044     startedFromSetupPosition = FALSE;
9045     while (cm == PGNTag) {
9046         if (appData.debugMode) 
9047           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9048         err = ParsePGNTag(yy_text, &gameInfo);
9049         if (!err) numPGNTags++;
9050
9051         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9052         if(gameInfo.variant != oldVariant) {
9053             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9054             InitPosition(TRUE);
9055             oldVariant = gameInfo.variant;
9056             if (appData.debugMode) 
9057               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9058         }
9059
9060
9061         if (gameInfo.fen != NULL) {
9062           Board initial_position;
9063           startedFromSetupPosition = TRUE;
9064           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9065             Reset(TRUE, TRUE);
9066             DisplayError(_("Bad FEN position in file"), 0);
9067             return FALSE;
9068           }
9069           CopyBoard(boards[0], initial_position);
9070           if (blackPlaysFirst) {
9071             currentMove = forwardMostMove = backwardMostMove = 1;
9072             CopyBoard(boards[1], initial_position);
9073             strcpy(moveList[0], "");
9074             strcpy(parseList[0], "");
9075             timeRemaining[0][1] = whiteTimeRemaining;
9076             timeRemaining[1][1] = blackTimeRemaining;
9077             if (commentList[0] != NULL) {
9078               commentList[1] = commentList[0];
9079               commentList[0] = NULL;
9080             }
9081           } else {
9082             currentMove = forwardMostMove = backwardMostMove = 0;
9083           }
9084           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9085           {   int i;
9086               initialRulePlies = FENrulePlies;
9087               epStatus[forwardMostMove] = FENepStatus;
9088               for( i=0; i< nrCastlingRights; i++ )
9089                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9090           }
9091           yyboardindex = forwardMostMove;
9092           free(gameInfo.fen);
9093           gameInfo.fen = NULL;
9094         }
9095
9096         yyboardindex = forwardMostMove;
9097         cm = (ChessMove) yylex();
9098
9099         /* Handle comments interspersed among the tags */
9100         while (cm == Comment) {
9101             char *p;
9102             if (appData.debugMode) 
9103               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9104             p = yy_text;
9105             if (*p == '{' || *p == '[' || *p == '(') {
9106                 p[strlen(p) - 1] = NULLCHAR;
9107                 p++;
9108             }
9109             while (*p == '\n') p++;
9110             AppendComment(currentMove, p);
9111             yyboardindex = forwardMostMove;
9112             cm = (ChessMove) yylex();
9113         }
9114     }
9115
9116     /* don't rely on existence of Event tag since if game was
9117      * pasted from clipboard the Event tag may not exist
9118      */
9119     if (numPGNTags > 0){
9120         char *tags;
9121         if (gameInfo.variant == VariantNormal) {
9122           gameInfo.variant = StringToVariant(gameInfo.event);
9123         }
9124         if (!matchMode) {
9125           if( appData.autoDisplayTags ) {
9126             tags = PGNTags(&gameInfo);
9127             TagsPopUp(tags, CmailMsg());
9128             free(tags);
9129           }
9130         }
9131     } else {
9132         /* Make something up, but don't display it now */
9133         SetGameInfo();
9134         TagsPopDown();
9135     }
9136
9137     if (cm == PositionDiagram) {
9138         int i, j;
9139         char *p;
9140         Board initial_position;
9141
9142         if (appData.debugMode)
9143           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9144
9145         if (!startedFromSetupPosition) {
9146             p = yy_text;
9147             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9148               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9149                 switch (*p) {
9150                   case '[':
9151                   case '-':
9152                   case ' ':
9153                   case '\t':
9154                   case '\n':
9155                   case '\r':
9156                     break;
9157                   default:
9158                     initial_position[i][j++] = CharToPiece(*p);
9159                     break;
9160                 }
9161             while (*p == ' ' || *p == '\t' ||
9162                    *p == '\n' || *p == '\r') p++;
9163         
9164             if (strncmp(p, "black", strlen("black"))==0)
9165               blackPlaysFirst = TRUE;
9166             else
9167               blackPlaysFirst = FALSE;
9168             startedFromSetupPosition = TRUE;
9169         
9170             CopyBoard(boards[0], initial_position);
9171             if (blackPlaysFirst) {
9172                 currentMove = forwardMostMove = backwardMostMove = 1;
9173                 CopyBoard(boards[1], initial_position);
9174                 strcpy(moveList[0], "");
9175                 strcpy(parseList[0], "");
9176                 timeRemaining[0][1] = whiteTimeRemaining;
9177                 timeRemaining[1][1] = blackTimeRemaining;
9178                 if (commentList[0] != NULL) {
9179                     commentList[1] = commentList[0];
9180                     commentList[0] = NULL;
9181                 }
9182             } else {
9183                 currentMove = forwardMostMove = backwardMostMove = 0;
9184             }
9185         }
9186         yyboardindex = forwardMostMove;
9187         cm = (ChessMove) yylex();
9188     }
9189
9190     if (first.pr == NoProc) {
9191         StartChessProgram(&first);
9192     }
9193     InitChessProgram(&first, FALSE);
9194     SendToProgram("force\n", &first);
9195     if (startedFromSetupPosition) {
9196         SendBoard(&first, forwardMostMove);
9197     if (appData.debugMode) {
9198         fprintf(debugFP, "Load Game\n");
9199     }
9200         DisplayBothClocks();
9201     }      
9202
9203     /* [HGM] server: flag to write setup moves in broadcast file as one */
9204     loadFlag = appData.suppressLoadMoves;
9205
9206     while (cm == Comment) {
9207         char *p;
9208         if (appData.debugMode) 
9209           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9210         p = yy_text;
9211         if (*p == '{' || *p == '[' || *p == '(') {
9212             p[strlen(p) - 1] = NULLCHAR;
9213             p++;
9214         }
9215         while (*p == '\n') p++;
9216         AppendComment(currentMove, p);
9217         yyboardindex = forwardMostMove;
9218         cm = (ChessMove) yylex();
9219     }
9220
9221     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9222         cm == WhiteWins || cm == BlackWins ||
9223         cm == GameIsDrawn || cm == GameUnfinished) {
9224         DisplayMessage("", _("No moves in game"));
9225         if (cmailMsgLoaded) {
9226             if (appData.debugMode)
9227               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9228             ClearHighlights();
9229             flipView = FALSE;
9230         }
9231         DrawPosition(FALSE, boards[currentMove]);
9232         DisplayBothClocks();
9233         gameMode = EditGame;
9234         ModeHighlight();
9235         gameFileFP = NULL;
9236         cmailOldMove = 0;
9237         return TRUE;
9238     }
9239
9240     // [HGM] PV info: routine tests if comment empty
9241     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9242         DisplayComment(currentMove - 1, commentList[currentMove]);
9243     }
9244     if (!matchMode && appData.timeDelay != 0) 
9245       DrawPosition(FALSE, boards[currentMove]);
9246
9247     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9248       programStats.ok_to_send = 1;
9249     }
9250
9251     /* if the first token after the PGN tags is a move
9252      * and not move number 1, retrieve it from the parser 
9253      */
9254     if (cm != MoveNumberOne)
9255         LoadGameOneMove(cm);
9256
9257     /* load the remaining moves from the file */
9258     while (LoadGameOneMove((ChessMove)0)) {
9259       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9260       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9261     }
9262
9263     /* rewind to the start of the game */
9264     currentMove = backwardMostMove;
9265
9266     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9267
9268     if (oldGameMode == AnalyzeFile ||
9269         oldGameMode == AnalyzeMode) {
9270       AnalyzeFileEvent();
9271     }
9272
9273     if (matchMode || appData.timeDelay == 0) {
9274       ToEndEvent();
9275       gameMode = EditGame;
9276       ModeHighlight();
9277     } else if (appData.timeDelay > 0) {
9278       AutoPlayGameLoop();
9279     }
9280
9281     if (appData.debugMode) 
9282         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9283
9284     loadFlag = 0; /* [HGM] true game starts */
9285     return TRUE;
9286 }
9287
9288 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9289 int
9290 ReloadPosition(offset)
9291      int offset;
9292 {
9293     int positionNumber = lastLoadPositionNumber + offset;
9294     if (lastLoadPositionFP == NULL) {
9295         DisplayError(_("No position has been loaded yet"), 0);
9296         return FALSE;
9297     }
9298     if (positionNumber <= 0) {
9299         DisplayError(_("Can't back up any further"), 0);
9300         return FALSE;
9301     }
9302     return LoadPosition(lastLoadPositionFP, positionNumber,
9303                         lastLoadPositionTitle);
9304 }
9305
9306 /* Load the nth position from the given file */
9307 int
9308 LoadPositionFromFile(filename, n, title)
9309      char *filename;
9310      int n;
9311      char *title;
9312 {
9313     FILE *f;
9314     char buf[MSG_SIZ];
9315
9316     if (strcmp(filename, "-") == 0) {
9317         return LoadPosition(stdin, n, "stdin");
9318     } else {
9319         f = fopen(filename, "rb");
9320         if (f == NULL) {
9321             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9322             DisplayError(buf, errno);
9323             return FALSE;
9324         } else {
9325             return LoadPosition(f, n, title);
9326         }
9327     }
9328 }
9329
9330 /* Load the nth position from the given open file, and close it */
9331 int
9332 LoadPosition(f, positionNumber, title)
9333      FILE *f;
9334      int positionNumber;
9335      char *title;
9336 {
9337     char *p, line[MSG_SIZ];
9338     Board initial_position;
9339     int i, j, fenMode, pn;
9340     
9341     if (gameMode == Training )
9342         SetTrainingModeOff();
9343
9344     if (gameMode != BeginningOfGame) {
9345         Reset(FALSE, TRUE);
9346     }
9347     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9348         fclose(lastLoadPositionFP);
9349     }
9350     if (positionNumber == 0) positionNumber = 1;
9351     lastLoadPositionFP = f;
9352     lastLoadPositionNumber = positionNumber;
9353     strcpy(lastLoadPositionTitle, title);
9354     if (first.pr == NoProc) {
9355       StartChessProgram(&first);
9356       InitChessProgram(&first, FALSE);
9357     }    
9358     pn = positionNumber;
9359     if (positionNumber < 0) {
9360         /* Negative position number means to seek to that byte offset */
9361         if (fseek(f, -positionNumber, 0) == -1) {
9362             DisplayError(_("Can't seek on position file"), 0);
9363             return FALSE;
9364         };
9365         pn = 1;
9366     } else {
9367         if (fseek(f, 0, 0) == -1) {
9368             if (f == lastLoadPositionFP ?
9369                 positionNumber == lastLoadPositionNumber + 1 :
9370                 positionNumber == 1) {
9371                 pn = 1;
9372             } else {
9373                 DisplayError(_("Can't seek on position file"), 0);
9374                 return FALSE;
9375             }
9376         }
9377     }
9378     /* See if this file is FEN or old-style xboard */
9379     if (fgets(line, MSG_SIZ, f) == NULL) {
9380         DisplayError(_("Position not found in file"), 0);
9381         return FALSE;
9382     }
9383 #if 0
9384     switch (line[0]) {
9385       case '#':  case 'x':
9386       default:
9387         fenMode = FALSE;
9388         break;
9389       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9390       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9391       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9392       case '7':  case '8':  case '9':
9393       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9394       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9395       case 'C':  case 'W':             case 'c':  case 'w': 
9396         fenMode = TRUE;
9397         break;
9398     }
9399 #else
9400     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9401     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9402 #endif
9403
9404     if (pn >= 2) {
9405         if (fenMode || line[0] == '#') pn--;
9406         while (pn > 0) {
9407             /* skip positions before number pn */
9408             if (fgets(line, MSG_SIZ, f) == NULL) {
9409                 Reset(TRUE, TRUE);
9410                 DisplayError(_("Position not found in file"), 0);
9411                 return FALSE;
9412             }
9413             if (fenMode || line[0] == '#') pn--;
9414         }
9415     }
9416
9417     if (fenMode) {
9418         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9419             DisplayError(_("Bad FEN position in file"), 0);
9420             return FALSE;
9421         }
9422     } else {
9423         (void) fgets(line, MSG_SIZ, f);
9424         (void) fgets(line, MSG_SIZ, f);
9425     
9426         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9427             (void) fgets(line, MSG_SIZ, f);
9428             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9429                 if (*p == ' ')
9430                   continue;
9431                 initial_position[i][j++] = CharToPiece(*p);
9432             }
9433         }
9434     
9435         blackPlaysFirst = FALSE;
9436         if (!feof(f)) {
9437             (void) fgets(line, MSG_SIZ, f);
9438             if (strncmp(line, "black", strlen("black"))==0)
9439               blackPlaysFirst = TRUE;
9440         }
9441     }
9442     startedFromSetupPosition = TRUE;
9443     
9444     SendToProgram("force\n", &first);
9445     CopyBoard(boards[0], initial_position);
9446     if (blackPlaysFirst) {
9447         currentMove = forwardMostMove = backwardMostMove = 1;
9448         strcpy(moveList[0], "");
9449         strcpy(parseList[0], "");
9450         CopyBoard(boards[1], initial_position);
9451         DisplayMessage("", _("Black to play"));
9452     } else {
9453         currentMove = forwardMostMove = backwardMostMove = 0;
9454         DisplayMessage("", _("White to play"));
9455     }
9456           /* [HGM] copy FEN attributes as well */
9457           {   int i;
9458               initialRulePlies = FENrulePlies;
9459               epStatus[forwardMostMove] = FENepStatus;
9460               for( i=0; i< nrCastlingRights; i++ )
9461                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9462           }
9463     SendBoard(&first, forwardMostMove);
9464     if (appData.debugMode) {
9465 int i, j;
9466   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9467   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9468         fprintf(debugFP, "Load Position\n");
9469     }
9470
9471     if (positionNumber > 1) {
9472         sprintf(line, "%s %d", title, positionNumber);
9473         DisplayTitle(line);
9474     } else {
9475         DisplayTitle(title);
9476     }
9477     gameMode = EditGame;
9478     ModeHighlight();
9479     ResetClocks();
9480     timeRemaining[0][1] = whiteTimeRemaining;
9481     timeRemaining[1][1] = blackTimeRemaining;
9482     DrawPosition(FALSE, boards[currentMove]);
9483    
9484     return TRUE;
9485 }
9486
9487
9488 void
9489 CopyPlayerNameIntoFileName(dest, src)
9490      char **dest, *src;
9491 {
9492     while (*src != NULLCHAR && *src != ',') {
9493         if (*src == ' ') {
9494             *(*dest)++ = '_';
9495             src++;
9496         } else {
9497             *(*dest)++ = *src++;
9498         }
9499     }
9500 }
9501
9502 char *DefaultFileName(ext)
9503      char *ext;
9504 {
9505     static char def[MSG_SIZ];
9506     char *p;
9507
9508     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9509         p = def;
9510         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9511         *p++ = '-';
9512         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9513         *p++ = '.';
9514         strcpy(p, ext);
9515     } else {
9516         def[0] = NULLCHAR;
9517     }
9518     return def;
9519 }
9520
9521 /* Save the current game to the given file */
9522 int
9523 SaveGameToFile(filename, append)
9524      char *filename;
9525      int append;
9526 {
9527     FILE *f;
9528     char buf[MSG_SIZ];
9529
9530     if (strcmp(filename, "-") == 0) {
9531         return SaveGame(stdout, 0, NULL);
9532     } else {
9533         f = fopen(filename, append ? "a" : "w");
9534         if (f == NULL) {
9535             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9536             DisplayError(buf, errno);
9537             return FALSE;
9538         } else {
9539             return SaveGame(f, 0, NULL);
9540         }
9541     }
9542 }
9543
9544 char *
9545 SavePart(str)
9546      char *str;
9547 {
9548     static char buf[MSG_SIZ];
9549     char *p;
9550     
9551     p = strchr(str, ' ');
9552     if (p == NULL) return str;
9553     strncpy(buf, str, p - str);
9554     buf[p - str] = NULLCHAR;
9555     return buf;
9556 }
9557
9558 #define PGN_MAX_LINE 75
9559
9560 #define PGN_SIDE_WHITE  0
9561 #define PGN_SIDE_BLACK  1
9562
9563 /* [AS] */
9564 static int FindFirstMoveOutOfBook( int side )
9565 {
9566     int result = -1;
9567
9568     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9569         int index = backwardMostMove;
9570         int has_book_hit = 0;
9571
9572         if( (index % 2) != side ) {
9573             index++;
9574         }
9575
9576         while( index < forwardMostMove ) {
9577             /* Check to see if engine is in book */
9578             int depth = pvInfoList[index].depth;
9579             int score = pvInfoList[index].score;
9580             int in_book = 0;
9581
9582             if( depth <= 2 ) {
9583                 in_book = 1;
9584             }
9585             else if( score == 0 && depth == 63 ) {
9586                 in_book = 1; /* Zappa */
9587             }
9588             else if( score == 2 && depth == 99 ) {
9589                 in_book = 1; /* Abrok */
9590             }
9591
9592             has_book_hit += in_book;
9593
9594             if( ! in_book ) {
9595                 result = index;
9596
9597                 break;
9598             }
9599
9600             index += 2;
9601         }
9602     }
9603
9604     return result;
9605 }
9606
9607 /* [AS] */
9608 void GetOutOfBookInfo( char * buf )
9609 {
9610     int oob[2];
9611     int i;
9612     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9613
9614     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9615     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9616
9617     *buf = '\0';
9618
9619     if( oob[0] >= 0 || oob[1] >= 0 ) {
9620         for( i=0; i<2; i++ ) {
9621             int idx = oob[i];
9622
9623             if( idx >= 0 ) {
9624                 if( i > 0 && oob[0] >= 0 ) {
9625                     strcat( buf, "   " );
9626                 }
9627
9628                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9629                 sprintf( buf+strlen(buf), "%s%.2f", 
9630                     pvInfoList[idx].score >= 0 ? "+" : "",
9631                     pvInfoList[idx].score / 100.0 );
9632             }
9633         }
9634     }
9635 }
9636
9637 /* Save game in PGN style and close the file */
9638 int
9639 SaveGamePGN(f)
9640      FILE *f;
9641 {
9642     int i, offset, linelen, newblock;
9643     time_t tm;
9644 //    char *movetext;
9645     char numtext[32];
9646     int movelen, numlen, blank;
9647     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9648
9649     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9650     
9651     tm = time((time_t *) NULL);
9652     
9653     PrintPGNTags(f, &gameInfo);
9654     
9655     if (backwardMostMove > 0 || startedFromSetupPosition) {
9656         char *fen = PositionToFEN(backwardMostMove, NULL);
9657         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9658         fprintf(f, "\n{--------------\n");
9659         PrintPosition(f, backwardMostMove);
9660         fprintf(f, "--------------}\n");
9661         free(fen);
9662     }
9663     else {
9664         /* [AS] Out of book annotation */
9665         if( appData.saveOutOfBookInfo ) {
9666             char buf[64];
9667
9668             GetOutOfBookInfo( buf );
9669
9670             if( buf[0] != '\0' ) {
9671                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9672             }
9673         }
9674
9675         fprintf(f, "\n");
9676     }
9677
9678     i = backwardMostMove;
9679     linelen = 0;
9680     newblock = TRUE;
9681
9682     while (i < forwardMostMove) {
9683         /* Print comments preceding this move */
9684         if (commentList[i] != NULL) {
9685             if (linelen > 0) fprintf(f, "\n");
9686             fprintf(f, "{\n%s}\n", commentList[i]);
9687             linelen = 0;
9688             newblock = TRUE;
9689         }
9690
9691         /* Format move number */
9692         if ((i % 2) == 0) {
9693             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9694         } else {
9695             if (newblock) {
9696                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9697             } else {
9698                 numtext[0] = NULLCHAR;
9699             }
9700         }
9701         numlen = strlen(numtext);
9702         newblock = FALSE;
9703
9704         /* Print move number */
9705         blank = linelen > 0 && numlen > 0;
9706         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9707             fprintf(f, "\n");
9708             linelen = 0;
9709             blank = 0;
9710         }
9711         if (blank) {
9712             fprintf(f, " ");
9713             linelen++;
9714         }
9715         fprintf(f, numtext);
9716         linelen += numlen;
9717
9718         /* Get move */
9719         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9720         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9721 #if 0
9722         // SavePart already does this!
9723         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9724                 int p = movelen - 1;
9725                 if(move_buffer[p] == ' ') p--;
9726                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9727                     while(p && move_buffer[--p] != '(');
9728                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9729                 }
9730         }
9731 #endif
9732         /* Print move */
9733         blank = linelen > 0 && movelen > 0;
9734         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9735             fprintf(f, "\n");
9736             linelen = 0;
9737             blank = 0;
9738         }
9739         if (blank) {
9740             fprintf(f, " ");
9741             linelen++;
9742         }
9743         fprintf(f, move_buffer);
9744         linelen += movelen;
9745
9746         /* [AS] Add PV info if present */
9747         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9748             /* [HGM] add time */
9749             char buf[MSG_SIZ]; int seconds = 0;
9750
9751 #if 1
9752             if(i >= backwardMostMove) {
9753                 if(WhiteOnMove(i))
9754                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9755                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9756                 else
9757                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9758                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9759             }
9760             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9761 #else
9762             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9763 #endif
9764
9765             if( seconds <= 0) buf[0] = 0; else
9766             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9767                 seconds = (seconds + 4)/10; // round to full seconds
9768                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9769                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9770             }
9771
9772             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9773                 pvInfoList[i].score >= 0 ? "+" : "",
9774                 pvInfoList[i].score / 100.0,
9775                 pvInfoList[i].depth,
9776                 buf );
9777
9778             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9779
9780             /* Print score/depth */
9781             blank = linelen > 0 && movelen > 0;
9782             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9783                 fprintf(f, "\n");
9784                 linelen = 0;
9785                 blank = 0;
9786             }
9787             if (blank) {
9788                 fprintf(f, " ");
9789                 linelen++;
9790             }
9791             fprintf(f, move_buffer);
9792             linelen += movelen;
9793         }
9794
9795         i++;
9796     }
9797     
9798     /* Start a new line */
9799     if (linelen > 0) fprintf(f, "\n");
9800
9801     /* Print comments after last move */
9802     if (commentList[i] != NULL) {
9803         fprintf(f, "{\n%s}\n", commentList[i]);
9804     }
9805
9806     /* Print result */
9807     if (gameInfo.resultDetails != NULL &&
9808         gameInfo.resultDetails[0] != NULLCHAR) {
9809         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9810                 PGNResult(gameInfo.result));
9811     } else {
9812         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9813     }
9814
9815     fclose(f);
9816     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9817     return TRUE;
9818 }
9819
9820 /* Save game in old style and close the file */
9821 int
9822 SaveGameOldStyle(f)
9823      FILE *f;
9824 {
9825     int i, offset;
9826     time_t tm;
9827     
9828     tm = time((time_t *) NULL);
9829     
9830     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9831     PrintOpponents(f);
9832     
9833     if (backwardMostMove > 0 || startedFromSetupPosition) {
9834         fprintf(f, "\n[--------------\n");
9835         PrintPosition(f, backwardMostMove);
9836         fprintf(f, "--------------]\n");
9837     } else {
9838         fprintf(f, "\n");
9839     }
9840
9841     i = backwardMostMove;
9842     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9843
9844     while (i < forwardMostMove) {
9845         if (commentList[i] != NULL) {
9846             fprintf(f, "[%s]\n", commentList[i]);
9847         }
9848
9849         if ((i % 2) == 1) {
9850             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9851             i++;
9852         } else {
9853             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9854             i++;
9855             if (commentList[i] != NULL) {
9856                 fprintf(f, "\n");
9857                 continue;
9858             }
9859             if (i >= forwardMostMove) {
9860                 fprintf(f, "\n");
9861                 break;
9862             }
9863             fprintf(f, "%s\n", parseList[i]);
9864             i++;
9865         }
9866     }
9867     
9868     if (commentList[i] != NULL) {
9869         fprintf(f, "[%s]\n", commentList[i]);
9870     }
9871
9872     /* This isn't really the old style, but it's close enough */
9873     if (gameInfo.resultDetails != NULL &&
9874         gameInfo.resultDetails[0] != NULLCHAR) {
9875         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9876                 gameInfo.resultDetails);
9877     } else {
9878         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9879     }
9880
9881     fclose(f);
9882     return TRUE;
9883 }
9884
9885 /* Save the current game to open file f and close the file */
9886 int
9887 SaveGame(f, dummy, dummy2)
9888      FILE *f;
9889      int dummy;
9890      char *dummy2;
9891 {
9892     if (gameMode == EditPosition) EditPositionDone();
9893     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9894     if (appData.oldSaveStyle)
9895       return SaveGameOldStyle(f);
9896     else
9897       return SaveGamePGN(f);
9898 }
9899
9900 /* Save the current position to the given file */
9901 int
9902 SavePositionToFile(filename)
9903      char *filename;
9904 {
9905     FILE *f;
9906     char buf[MSG_SIZ];
9907
9908     if (strcmp(filename, "-") == 0) {
9909         return SavePosition(stdout, 0, NULL);
9910     } else {
9911         f = fopen(filename, "a");
9912         if (f == NULL) {
9913             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9914             DisplayError(buf, errno);
9915             return FALSE;
9916         } else {
9917             SavePosition(f, 0, NULL);
9918             return TRUE;
9919         }
9920     }
9921 }
9922
9923 /* Save the current position to the given open file and close the file */
9924 int
9925 SavePosition(f, dummy, dummy2)
9926      FILE *f;
9927      int dummy;
9928      char *dummy2;
9929 {
9930     time_t tm;
9931     char *fen;
9932     
9933     if (appData.oldSaveStyle) {
9934         tm = time((time_t *) NULL);
9935     
9936         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9937         PrintOpponents(f);
9938         fprintf(f, "[--------------\n");
9939         PrintPosition(f, currentMove);
9940         fprintf(f, "--------------]\n");
9941     } else {
9942         fen = PositionToFEN(currentMove, NULL);
9943         fprintf(f, "%s\n", fen);
9944         free(fen);
9945     }
9946     fclose(f);
9947     return TRUE;
9948 }
9949
9950 void
9951 ReloadCmailMsgEvent(unregister)
9952      int unregister;
9953 {
9954 #if !WIN32
9955     static char *inFilename = NULL;
9956     static char *outFilename;
9957     int i;
9958     struct stat inbuf, outbuf;
9959     int status;
9960     
9961     /* Any registered moves are unregistered if unregister is set, */
9962     /* i.e. invoked by the signal handler */
9963     if (unregister) {
9964         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9965             cmailMoveRegistered[i] = FALSE;
9966             if (cmailCommentList[i] != NULL) {
9967                 free(cmailCommentList[i]);
9968                 cmailCommentList[i] = NULL;
9969             }
9970         }
9971         nCmailMovesRegistered = 0;
9972     }
9973
9974     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9975         cmailResult[i] = CMAIL_NOT_RESULT;
9976     }
9977     nCmailResults = 0;
9978
9979     if (inFilename == NULL) {
9980         /* Because the filenames are static they only get malloced once  */
9981         /* and they never get freed                                      */
9982         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9983         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9984
9985         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9986         sprintf(outFilename, "%s.out", appData.cmailGameName);
9987     }
9988     
9989     status = stat(outFilename, &outbuf);
9990     if (status < 0) {
9991         cmailMailedMove = FALSE;
9992     } else {
9993         status = stat(inFilename, &inbuf);
9994         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9995     }
9996     
9997     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9998        counts the games, notes how each one terminated, etc.
9999        
10000        It would be nice to remove this kludge and instead gather all
10001        the information while building the game list.  (And to keep it
10002        in the game list nodes instead of having a bunch of fixed-size
10003        parallel arrays.)  Note this will require getting each game's
10004        termination from the PGN tags, as the game list builder does
10005        not process the game moves.  --mann
10006        */
10007     cmailMsgLoaded = TRUE;
10008     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10009     
10010     /* Load first game in the file or popup game menu */
10011     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10012
10013 #endif /* !WIN32 */
10014     return;
10015 }
10016
10017 int
10018 RegisterMove()
10019 {
10020     FILE *f;
10021     char string[MSG_SIZ];
10022
10023     if (   cmailMailedMove
10024         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10025         return TRUE;            /* Allow free viewing  */
10026     }
10027
10028     /* Unregister move to ensure that we don't leave RegisterMove        */
10029     /* with the move registered when the conditions for registering no   */
10030     /* longer hold                                                       */
10031     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10032         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10033         nCmailMovesRegistered --;
10034
10035         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10036           {
10037               free(cmailCommentList[lastLoadGameNumber - 1]);
10038               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10039           }
10040     }
10041
10042     if (cmailOldMove == -1) {
10043         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10044         return FALSE;
10045     }
10046
10047     if (currentMove > cmailOldMove + 1) {
10048         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10049         return FALSE;
10050     }
10051
10052     if (currentMove < cmailOldMove) {
10053         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10054         return FALSE;
10055     }
10056
10057     if (forwardMostMove > currentMove) {
10058         /* Silently truncate extra moves */
10059         TruncateGame();
10060     }
10061
10062     if (   (currentMove == cmailOldMove + 1)
10063         || (   (currentMove == cmailOldMove)
10064             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10065                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10066         if (gameInfo.result != GameUnfinished) {
10067             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10068         }
10069
10070         if (commentList[currentMove] != NULL) {
10071             cmailCommentList[lastLoadGameNumber - 1]
10072               = StrSave(commentList[currentMove]);
10073         }
10074         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10075
10076         if (appData.debugMode)
10077           fprintf(debugFP, "Saving %s for game %d\n",
10078                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10079
10080         sprintf(string,
10081                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10082         
10083         f = fopen(string, "w");
10084         if (appData.oldSaveStyle) {
10085             SaveGameOldStyle(f); /* also closes the file */
10086             
10087             sprintf(string, "%s.pos.out", appData.cmailGameName);
10088             f = fopen(string, "w");
10089             SavePosition(f, 0, NULL); /* also closes the file */
10090         } else {
10091             fprintf(f, "{--------------\n");
10092             PrintPosition(f, currentMove);
10093             fprintf(f, "--------------}\n\n");
10094             
10095             SaveGame(f, 0, NULL); /* also closes the file*/
10096         }
10097         
10098         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10099         nCmailMovesRegistered ++;
10100     } else if (nCmailGames == 1) {
10101         DisplayError(_("You have not made a move yet"), 0);
10102         return FALSE;
10103     }
10104
10105     return TRUE;
10106 }
10107
10108 void
10109 MailMoveEvent()
10110 {
10111 #if !WIN32
10112     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10113     FILE *commandOutput;
10114     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10115     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10116     int nBuffers;
10117     int i;
10118     int archived;
10119     char *arcDir;
10120
10121     if (! cmailMsgLoaded) {
10122         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10123         return;
10124     }
10125
10126     if (nCmailGames == nCmailResults) {
10127         DisplayError(_("No unfinished games"), 0);
10128         return;
10129     }
10130
10131 #if CMAIL_PROHIBIT_REMAIL
10132     if (cmailMailedMove) {
10133         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);
10134         DisplayError(msg, 0);
10135         return;
10136     }
10137 #endif
10138
10139     if (! (cmailMailedMove || RegisterMove())) return;
10140     
10141     if (   cmailMailedMove
10142         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10143         sprintf(string, partCommandString,
10144                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10145         commandOutput = popen(string, "r");
10146
10147         if (commandOutput == NULL) {
10148             DisplayError(_("Failed to invoke cmail"), 0);
10149         } else {
10150             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10151                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10152             }
10153             if (nBuffers > 1) {
10154                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10155                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10156                 nBytes = MSG_SIZ - 1;
10157             } else {
10158                 (void) memcpy(msg, buffer, nBytes);
10159             }
10160             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10161
10162             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10163                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10164
10165                 archived = TRUE;
10166                 for (i = 0; i < nCmailGames; i ++) {
10167                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10168                         archived = FALSE;
10169                     }
10170                 }
10171                 if (   archived
10172                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10173                         != NULL)) {
10174                     sprintf(buffer, "%s/%s.%s.archive",
10175                             arcDir,
10176                             appData.cmailGameName,
10177                             gameInfo.date);
10178                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10179                     cmailMsgLoaded = FALSE;
10180                 }
10181             }
10182
10183             DisplayInformation(msg);
10184             pclose(commandOutput);
10185         }
10186     } else {
10187         if ((*cmailMsg) != '\0') {
10188             DisplayInformation(cmailMsg);
10189         }
10190     }
10191
10192     return;
10193 #endif /* !WIN32 */
10194 }
10195
10196 char *
10197 CmailMsg()
10198 {
10199 #if WIN32
10200     return NULL;
10201 #else
10202     int  prependComma = 0;
10203     char number[5];
10204     char string[MSG_SIZ];       /* Space for game-list */
10205     int  i;
10206     
10207     if (!cmailMsgLoaded) return "";
10208
10209     if (cmailMailedMove) {
10210         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10211     } else {
10212         /* Create a list of games left */
10213         sprintf(string, "[");
10214         for (i = 0; i < nCmailGames; i ++) {
10215             if (! (   cmailMoveRegistered[i]
10216                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10217                 if (prependComma) {
10218                     sprintf(number, ",%d", i + 1);
10219                 } else {
10220                     sprintf(number, "%d", i + 1);
10221                     prependComma = 1;
10222                 }
10223                 
10224                 strcat(string, number);
10225             }
10226         }
10227         strcat(string, "]");
10228
10229         if (nCmailMovesRegistered + nCmailResults == 0) {
10230             switch (nCmailGames) {
10231               case 1:
10232                 sprintf(cmailMsg,
10233                         _("Still need to make move for game\n"));
10234                 break;
10235                 
10236               case 2:
10237                 sprintf(cmailMsg,
10238                         _("Still need to make moves for both games\n"));
10239                 break;
10240                 
10241               default:
10242                 sprintf(cmailMsg,
10243                         _("Still need to make moves for all %d games\n"),
10244                         nCmailGames);
10245                 break;
10246             }
10247         } else {
10248             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10249               case 1:
10250                 sprintf(cmailMsg,
10251                         _("Still need to make a move for game %s\n"),
10252                         string);
10253                 break;
10254                 
10255               case 0:
10256                 if (nCmailResults == nCmailGames) {
10257                     sprintf(cmailMsg, _("No unfinished games\n"));
10258                 } else {
10259                     sprintf(cmailMsg, _("Ready to send mail\n"));
10260                 }
10261                 break;
10262                 
10263               default:
10264                 sprintf(cmailMsg,
10265                         _("Still need to make moves for games %s\n"),
10266                         string);
10267             }
10268         }
10269     }
10270     return cmailMsg;
10271 #endif /* WIN32 */
10272 }
10273
10274 void
10275 ResetGameEvent()
10276 {
10277     if (gameMode == Training)
10278       SetTrainingModeOff();
10279
10280     Reset(TRUE, TRUE);
10281     cmailMsgLoaded = FALSE;
10282     if (appData.icsActive) {
10283       SendToICS(ics_prefix);
10284       SendToICS("refresh\n");
10285     }
10286 }
10287
10288 void
10289 ExitEvent(status)
10290      int status;
10291 {
10292     exiting++;
10293     if (exiting > 2) {
10294       /* Give up on clean exit */
10295       exit(status);
10296     }
10297     if (exiting > 1) {
10298       /* Keep trying for clean exit */
10299       return;
10300     }
10301
10302     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10303
10304     if (telnetISR != NULL) {
10305       RemoveInputSource(telnetISR);
10306     }
10307     if (icsPR != NoProc) {
10308       DestroyChildProcess(icsPR, TRUE);
10309     }
10310 #if 0
10311     /* Save game if resource set and not already saved by GameEnds() */
10312     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10313                              && forwardMostMove > 0) {
10314       if (*appData.saveGameFile != NULLCHAR) {
10315         SaveGameToFile(appData.saveGameFile, TRUE);
10316       } else if (appData.autoSaveGames) {
10317         AutoSaveGame();
10318       }
10319       if (*appData.savePositionFile != NULLCHAR) {
10320         SavePositionToFile(appData.savePositionFile);
10321       }
10322     }
10323     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10324 #else
10325     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10326     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10327 #endif
10328     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10329     /* make sure this other one finishes before killing it!                  */
10330     if(endingGame) { int count = 0;
10331         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10332         while(endingGame && count++ < 10) DoSleep(1);
10333         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10334     }
10335
10336     /* Kill off chess programs */
10337     if (first.pr != NoProc) {
10338         ExitAnalyzeMode();
10339         
10340         DoSleep( appData.delayBeforeQuit );
10341         SendToProgram("quit\n", &first);
10342         DoSleep( appData.delayAfterQuit );
10343         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10344     }
10345     if (second.pr != NoProc) {
10346         DoSleep( appData.delayBeforeQuit );
10347         SendToProgram("quit\n", &second);
10348         DoSleep( appData.delayAfterQuit );
10349         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10350     }
10351     if (first.isr != NULL) {
10352         RemoveInputSource(first.isr);
10353     }
10354     if (second.isr != NULL) {
10355         RemoveInputSource(second.isr);
10356     }
10357
10358     ShutDownFrontEnd();
10359     exit(status);
10360 }
10361
10362 void
10363 PauseEvent()
10364 {
10365     if (appData.debugMode)
10366         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10367     if (pausing) {
10368         pausing = FALSE;
10369         ModeHighlight();
10370         if (gameMode == MachinePlaysWhite ||
10371             gameMode == MachinePlaysBlack) {
10372             StartClocks();
10373         } else {
10374             DisplayBothClocks();
10375         }
10376         if (gameMode == PlayFromGameFile) {
10377             if (appData.timeDelay >= 0) 
10378                 AutoPlayGameLoop();
10379         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10380             Reset(FALSE, TRUE);
10381             SendToICS(ics_prefix);
10382             SendToICS("refresh\n");
10383         } else if (currentMove < forwardMostMove) {
10384             ForwardInner(forwardMostMove);
10385         }
10386         pauseExamInvalid = FALSE;
10387     } else {
10388         switch (gameMode) {
10389           default:
10390             return;
10391           case IcsExamining:
10392             pauseExamForwardMostMove = forwardMostMove;
10393             pauseExamInvalid = FALSE;
10394             /* fall through */
10395           case IcsObserving:
10396           case IcsPlayingWhite:
10397           case IcsPlayingBlack:
10398             pausing = TRUE;
10399             ModeHighlight();
10400             return;
10401           case PlayFromGameFile:
10402             (void) StopLoadGameTimer();
10403             pausing = TRUE;
10404             ModeHighlight();
10405             break;
10406           case BeginningOfGame:
10407             if (appData.icsActive) return;
10408             /* else fall through */
10409           case MachinePlaysWhite:
10410           case MachinePlaysBlack:
10411           case TwoMachinesPlay:
10412             if (forwardMostMove == 0)
10413               return;           /* don't pause if no one has moved */
10414             if ((gameMode == MachinePlaysWhite &&
10415                  !WhiteOnMove(forwardMostMove)) ||
10416                 (gameMode == MachinePlaysBlack &&
10417                  WhiteOnMove(forwardMostMove))) {
10418                 StopClocks();
10419             }
10420             pausing = TRUE;
10421             ModeHighlight();
10422             break;
10423         }
10424     }
10425 }
10426
10427 void
10428 EditCommentEvent()
10429 {
10430     char title[MSG_SIZ];
10431
10432     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10433         strcpy(title, _("Edit comment"));
10434     } else {
10435         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10436                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10437                 parseList[currentMove - 1]);
10438     }
10439
10440     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10441 }
10442
10443
10444 void
10445 EditTagsEvent()
10446 {
10447     char *tags = PGNTags(&gameInfo);
10448     EditTagsPopUp(tags);
10449     free(tags);
10450 }
10451
10452 void
10453 AnalyzeModeEvent()
10454 {
10455     if (appData.noChessProgram || gameMode == AnalyzeMode)
10456       return;
10457
10458     if (gameMode != AnalyzeFile) {
10459         if (!appData.icsEngineAnalyze) {
10460                EditGameEvent();
10461                if (gameMode != EditGame) return;
10462         }
10463         ResurrectChessProgram();
10464         SendToProgram("analyze\n", &first);
10465         first.analyzing = TRUE;
10466         /*first.maybeThinking = TRUE;*/
10467         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10468         AnalysisPopUp(_("Analysis"),
10469                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10470     }
10471     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10472     pausing = FALSE;
10473     ModeHighlight();
10474     SetGameInfo();
10475
10476     StartAnalysisClock();
10477     GetTimeMark(&lastNodeCountTime);
10478     lastNodeCount = 0;
10479 }
10480
10481 void
10482 AnalyzeFileEvent()
10483 {
10484     if (appData.noChessProgram || gameMode == AnalyzeFile)
10485       return;
10486
10487     if (gameMode != AnalyzeMode) {
10488         EditGameEvent();
10489         if (gameMode != EditGame) return;
10490         ResurrectChessProgram();
10491         SendToProgram("analyze\n", &first);
10492         first.analyzing = TRUE;
10493         /*first.maybeThinking = TRUE;*/
10494         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10495         AnalysisPopUp(_("Analysis"),
10496                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10497     }
10498     gameMode = AnalyzeFile;
10499     pausing = FALSE;
10500     ModeHighlight();
10501     SetGameInfo();
10502
10503     StartAnalysisClock();
10504     GetTimeMark(&lastNodeCountTime);
10505     lastNodeCount = 0;
10506 }
10507
10508 void
10509 MachineWhiteEvent()
10510 {
10511     char buf[MSG_SIZ];
10512     char *bookHit = NULL;
10513
10514     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10515       return;
10516
10517
10518     if (gameMode == PlayFromGameFile || 
10519         gameMode == TwoMachinesPlay  || 
10520         gameMode == Training         || 
10521         gameMode == AnalyzeMode      || 
10522         gameMode == EndOfGame)
10523         EditGameEvent();
10524
10525     if (gameMode == EditPosition) 
10526         EditPositionDone();
10527
10528     if (!WhiteOnMove(currentMove)) {
10529         DisplayError(_("It is not White's turn"), 0);
10530         return;
10531     }
10532   
10533     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10534       ExitAnalyzeMode();
10535
10536     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10537         gameMode == AnalyzeFile)
10538         TruncateGame();
10539
10540     ResurrectChessProgram();    /* in case it isn't running */
10541     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10542         gameMode = MachinePlaysWhite;
10543         ResetClocks();
10544     } else
10545     gameMode = MachinePlaysWhite;
10546     pausing = FALSE;
10547     ModeHighlight();
10548     SetGameInfo();
10549     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10550     DisplayTitle(buf);
10551     if (first.sendName) {
10552       sprintf(buf, "name %s\n", gameInfo.black);
10553       SendToProgram(buf, &first);
10554     }
10555     if (first.sendTime) {
10556       if (first.useColors) {
10557         SendToProgram("black\n", &first); /*gnu kludge*/
10558       }
10559       SendTimeRemaining(&first, TRUE);
10560     }
10561     if (first.useColors) {
10562       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10563     }
10564     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10565     SetMachineThinkingEnables();
10566     first.maybeThinking = TRUE;
10567     StartClocks();
10568     firstMove = FALSE;
10569
10570     if (appData.autoFlipView && !flipView) {
10571       flipView = !flipView;
10572       DrawPosition(FALSE, NULL);
10573       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10574     }
10575
10576     if(bookHit) { // [HGM] book: simulate book reply
10577         static char bookMove[MSG_SIZ]; // a bit generous?
10578
10579         programStats.nodes = programStats.depth = programStats.time = 
10580         programStats.score = programStats.got_only_move = 0;
10581         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10582
10583         strcpy(bookMove, "move ");
10584         strcat(bookMove, bookHit);
10585         HandleMachineMove(bookMove, &first);
10586     }
10587 }
10588
10589 void
10590 MachineBlackEvent()
10591 {
10592     char buf[MSG_SIZ];
10593    char *bookHit = NULL;
10594
10595     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10596         return;
10597
10598
10599     if (gameMode == PlayFromGameFile || 
10600         gameMode == TwoMachinesPlay  || 
10601         gameMode == Training         || 
10602         gameMode == AnalyzeMode      || 
10603         gameMode == EndOfGame)
10604         EditGameEvent();
10605
10606     if (gameMode == EditPosition) 
10607         EditPositionDone();
10608
10609     if (WhiteOnMove(currentMove)) {
10610         DisplayError(_("It is not Black's turn"), 0);
10611         return;
10612     }
10613     
10614     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10615       ExitAnalyzeMode();
10616
10617     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10618         gameMode == AnalyzeFile)
10619         TruncateGame();
10620
10621     ResurrectChessProgram();    /* in case it isn't running */
10622     gameMode = MachinePlaysBlack;
10623     pausing = FALSE;
10624     ModeHighlight();
10625     SetGameInfo();
10626     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10627     DisplayTitle(buf);
10628     if (first.sendName) {
10629       sprintf(buf, "name %s\n", gameInfo.white);
10630       SendToProgram(buf, &first);
10631     }
10632     if (first.sendTime) {
10633       if (first.useColors) {
10634         SendToProgram("white\n", &first); /*gnu kludge*/
10635       }
10636       SendTimeRemaining(&first, FALSE);
10637     }
10638     if (first.useColors) {
10639       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10640     }
10641     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10642     SetMachineThinkingEnables();
10643     first.maybeThinking = TRUE;
10644     StartClocks();
10645
10646     if (appData.autoFlipView && flipView) {
10647       flipView = !flipView;
10648       DrawPosition(FALSE, NULL);
10649       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10650     }
10651     if(bookHit) { // [HGM] book: simulate book reply
10652         static char bookMove[MSG_SIZ]; // a bit generous?
10653
10654         programStats.nodes = programStats.depth = programStats.time = 
10655         programStats.score = programStats.got_only_move = 0;
10656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10657
10658         strcpy(bookMove, "move ");
10659         strcat(bookMove, bookHit);
10660         HandleMachineMove(bookMove, &first);
10661     }
10662 }
10663
10664
10665 void
10666 DisplayTwoMachinesTitle()
10667 {
10668     char buf[MSG_SIZ];
10669     if (appData.matchGames > 0) {
10670         if (first.twoMachinesColor[0] == 'w') {
10671             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10672                     gameInfo.white, gameInfo.black,
10673                     first.matchWins, second.matchWins,
10674                     matchGame - 1 - (first.matchWins + second.matchWins));
10675         } else {
10676             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10677                     gameInfo.white, gameInfo.black,
10678                     second.matchWins, first.matchWins,
10679                     matchGame - 1 - (first.matchWins + second.matchWins));
10680         }
10681     } else {
10682         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10683     }
10684     DisplayTitle(buf);
10685 }
10686
10687 void
10688 TwoMachinesEvent P((void))
10689 {
10690     int i;
10691     char buf[MSG_SIZ];
10692     ChessProgramState *onmove;
10693     char *bookHit = NULL;
10694     
10695     if (appData.noChessProgram) return;
10696
10697     switch (gameMode) {
10698       case TwoMachinesPlay:
10699         return;
10700       case MachinePlaysWhite:
10701       case MachinePlaysBlack:
10702         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10703             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10704             return;
10705         }
10706         /* fall through */
10707       case BeginningOfGame:
10708       case PlayFromGameFile:
10709       case EndOfGame:
10710         EditGameEvent();
10711         if (gameMode != EditGame) return;
10712         break;
10713       case EditPosition:
10714         EditPositionDone();
10715         break;
10716       case AnalyzeMode:
10717       case AnalyzeFile:
10718         ExitAnalyzeMode();
10719         break;
10720       case EditGame:
10721       default:
10722         break;
10723     }
10724
10725     forwardMostMove = currentMove;
10726     ResurrectChessProgram();    /* in case first program isn't running */
10727
10728     if (second.pr == NULL) {
10729         StartChessProgram(&second);
10730         if (second.protocolVersion == 1) {
10731           TwoMachinesEventIfReady();
10732         } else {
10733           /* kludge: allow timeout for initial "feature" command */
10734           FreezeUI();
10735           DisplayMessage("", _("Starting second chess program"));
10736           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10737         }
10738         return;
10739     }
10740     DisplayMessage("", "");
10741     InitChessProgram(&second, FALSE);
10742     SendToProgram("force\n", &second);
10743     if (startedFromSetupPosition) {
10744         SendBoard(&second, backwardMostMove);
10745     if (appData.debugMode) {
10746         fprintf(debugFP, "Two Machines\n");
10747     }
10748     }
10749     for (i = backwardMostMove; i < forwardMostMove; i++) {
10750         SendMoveToProgram(i, &second);
10751     }
10752
10753     gameMode = TwoMachinesPlay;
10754     pausing = FALSE;
10755     ModeHighlight();
10756     SetGameInfo();
10757     DisplayTwoMachinesTitle();
10758     firstMove = TRUE;
10759     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10760         onmove = &first;
10761     } else {
10762         onmove = &second;
10763     }
10764
10765     SendToProgram(first.computerString, &first);
10766     if (first.sendName) {
10767       sprintf(buf, "name %s\n", second.tidy);
10768       SendToProgram(buf, &first);
10769     }
10770     SendToProgram(second.computerString, &second);
10771     if (second.sendName) {
10772       sprintf(buf, "name %s\n", first.tidy);
10773       SendToProgram(buf, &second);
10774     }
10775
10776     ResetClocks();
10777     if (!first.sendTime || !second.sendTime) {
10778         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10779         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10780     }
10781     if (onmove->sendTime) {
10782       if (onmove->useColors) {
10783         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10784       }
10785       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10786     }
10787     if (onmove->useColors) {
10788       SendToProgram(onmove->twoMachinesColor, onmove);
10789     }
10790     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10791 //    SendToProgram("go\n", onmove);
10792     onmove->maybeThinking = TRUE;
10793     SetMachineThinkingEnables();
10794
10795     StartClocks();
10796
10797     if(bookHit) { // [HGM] book: simulate book reply
10798         static char bookMove[MSG_SIZ]; // a bit generous?
10799
10800         programStats.nodes = programStats.depth = programStats.time = 
10801         programStats.score = programStats.got_only_move = 0;
10802         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10803
10804         strcpy(bookMove, "move ");
10805         strcat(bookMove, bookHit);
10806         HandleMachineMove(bookMove, &first);
10807     }
10808 }
10809
10810 void
10811 TrainingEvent()
10812 {
10813     if (gameMode == Training) {
10814       SetTrainingModeOff();
10815       gameMode = PlayFromGameFile;
10816       DisplayMessage("", _("Training mode off"));
10817     } else {
10818       gameMode = Training;
10819       animateTraining = appData.animate;
10820
10821       /* make sure we are not already at the end of the game */
10822       if (currentMove < forwardMostMove) {
10823         SetTrainingModeOn();
10824         DisplayMessage("", _("Training mode on"));
10825       } else {
10826         gameMode = PlayFromGameFile;
10827         DisplayError(_("Already at end of game"), 0);
10828       }
10829     }
10830     ModeHighlight();
10831 }
10832
10833 void
10834 IcsClientEvent()
10835 {
10836     if (!appData.icsActive) return;
10837     switch (gameMode) {
10838       case IcsPlayingWhite:
10839       case IcsPlayingBlack:
10840       case IcsObserving:
10841       case IcsIdle:
10842       case BeginningOfGame:
10843       case IcsExamining:
10844         return;
10845
10846       case EditGame:
10847         break;
10848
10849       case EditPosition:
10850         EditPositionDone();
10851         break;
10852
10853       case AnalyzeMode:
10854       case AnalyzeFile:
10855         ExitAnalyzeMode();
10856         break;
10857         
10858       default:
10859         EditGameEvent();
10860         break;
10861     }
10862
10863     gameMode = IcsIdle;
10864     ModeHighlight();
10865     return;
10866 }
10867
10868
10869 void
10870 EditGameEvent()
10871 {
10872     int i;
10873
10874     switch (gameMode) {
10875       case Training:
10876         SetTrainingModeOff();
10877         break;
10878       case MachinePlaysWhite:
10879       case MachinePlaysBlack:
10880       case BeginningOfGame:
10881         SendToProgram("force\n", &first);
10882         SetUserThinkingEnables();
10883         break;
10884       case PlayFromGameFile:
10885         (void) StopLoadGameTimer();
10886         if (gameFileFP != NULL) {
10887             gameFileFP = NULL;
10888         }
10889         break;
10890       case EditPosition:
10891         EditPositionDone();
10892         break;
10893       case AnalyzeMode:
10894       case AnalyzeFile:
10895         ExitAnalyzeMode();
10896         SendToProgram("force\n", &first);
10897         break;
10898       case TwoMachinesPlay:
10899         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10900         ResurrectChessProgram();
10901         SetUserThinkingEnables();
10902         break;
10903       case EndOfGame:
10904         ResurrectChessProgram();
10905         break;
10906       case IcsPlayingBlack:
10907       case IcsPlayingWhite:
10908         DisplayError(_("Warning: You are still playing a game"), 0);
10909         break;
10910       case IcsObserving:
10911         DisplayError(_("Warning: You are still observing a game"), 0);
10912         break;
10913       case IcsExamining:
10914         DisplayError(_("Warning: You are still examining a game"), 0);
10915         break;
10916       case IcsIdle:
10917         break;
10918       case EditGame:
10919       default:
10920         return;
10921     }
10922     
10923     pausing = FALSE;
10924     StopClocks();
10925     first.offeredDraw = second.offeredDraw = 0;
10926
10927     if (gameMode == PlayFromGameFile) {
10928         whiteTimeRemaining = timeRemaining[0][currentMove];
10929         blackTimeRemaining = timeRemaining[1][currentMove];
10930         DisplayTitle("");
10931     }
10932
10933     if (gameMode == MachinePlaysWhite ||
10934         gameMode == MachinePlaysBlack ||
10935         gameMode == TwoMachinesPlay ||
10936         gameMode == EndOfGame) {
10937         i = forwardMostMove;
10938         while (i > currentMove) {
10939             SendToProgram("undo\n", &first);
10940             i--;
10941         }
10942         whiteTimeRemaining = timeRemaining[0][currentMove];
10943         blackTimeRemaining = timeRemaining[1][currentMove];
10944         DisplayBothClocks();
10945         if (whiteFlag || blackFlag) {
10946             whiteFlag = blackFlag = 0;
10947         }
10948         DisplayTitle("");
10949     }           
10950     
10951     gameMode = EditGame;
10952     ModeHighlight();
10953     SetGameInfo();
10954 }
10955
10956
10957 void
10958 EditPositionEvent()
10959 {
10960     if (gameMode == EditPosition) {
10961         EditGameEvent();
10962         return;
10963     }
10964     
10965     EditGameEvent();
10966     if (gameMode != EditGame) return;
10967     
10968     gameMode = EditPosition;
10969     ModeHighlight();
10970     SetGameInfo();
10971     if (currentMove > 0)
10972       CopyBoard(boards[0], boards[currentMove]);
10973     
10974     blackPlaysFirst = !WhiteOnMove(currentMove);
10975     ResetClocks();
10976     currentMove = forwardMostMove = backwardMostMove = 0;
10977     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10978     DisplayMove(-1);
10979 }
10980
10981 void
10982 ExitAnalyzeMode()
10983 {
10984     /* [DM] icsEngineAnalyze - possible call from other functions */
10985     if (appData.icsEngineAnalyze) {
10986         appData.icsEngineAnalyze = FALSE;
10987
10988         DisplayMessage("",_("Close ICS engine analyze..."));
10989     }
10990     if (first.analysisSupport && first.analyzing) {
10991       SendToProgram("exit\n", &first);
10992       first.analyzing = FALSE;
10993     }
10994     AnalysisPopDown();
10995     thinkOutput[0] = NULLCHAR;
10996 }
10997
10998 void
10999 EditPositionDone()
11000 {
11001     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11002
11003     startedFromSetupPosition = TRUE;
11004     InitChessProgram(&first, FALSE);
11005     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11006     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11007         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11008         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11009     } else castlingRights[0][2] = -1;
11010     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11011         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11012         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11013     } else castlingRights[0][5] = -1;
11014     SendToProgram("force\n", &first);
11015     if (blackPlaysFirst) {
11016         strcpy(moveList[0], "");
11017         strcpy(parseList[0], "");
11018         currentMove = forwardMostMove = backwardMostMove = 1;
11019         CopyBoard(boards[1], boards[0]);
11020         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11021         { int i;
11022           epStatus[1] = epStatus[0];
11023           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11024         }
11025     } else {
11026         currentMove = forwardMostMove = backwardMostMove = 0;
11027     }
11028     SendBoard(&first, forwardMostMove);
11029     if (appData.debugMode) {
11030         fprintf(debugFP, "EditPosDone\n");
11031     }
11032     DisplayTitle("");
11033     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11034     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11035     gameMode = EditGame;
11036     ModeHighlight();
11037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11038     ClearHighlights(); /* [AS] */
11039 }
11040
11041 /* Pause for `ms' milliseconds */
11042 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11043 void
11044 TimeDelay(ms)
11045      long ms;
11046 {
11047     TimeMark m1, m2;
11048
11049     GetTimeMark(&m1);
11050     do {
11051         GetTimeMark(&m2);
11052     } while (SubtractTimeMarks(&m2, &m1) < ms);
11053 }
11054
11055 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11056 void
11057 SendMultiLineToICS(buf)
11058      char *buf;
11059 {
11060     char temp[MSG_SIZ+1], *p;
11061     int len;
11062
11063     len = strlen(buf);
11064     if (len > MSG_SIZ)
11065       len = MSG_SIZ;
11066   
11067     strncpy(temp, buf, len);
11068     temp[len] = 0;
11069
11070     p = temp;
11071     while (*p) {
11072         if (*p == '\n' || *p == '\r')
11073           *p = ' ';
11074         ++p;
11075     }
11076
11077     strcat(temp, "\n");
11078     SendToICS(temp);
11079     SendToPlayer(temp, strlen(temp));
11080 }
11081
11082 void
11083 SetWhiteToPlayEvent()
11084 {
11085     if (gameMode == EditPosition) {
11086         blackPlaysFirst = FALSE;
11087         DisplayBothClocks();    /* works because currentMove is 0 */
11088     } else if (gameMode == IcsExamining) {
11089         SendToICS(ics_prefix);
11090         SendToICS("tomove white\n");
11091     }
11092 }
11093
11094 void
11095 SetBlackToPlayEvent()
11096 {
11097     if (gameMode == EditPosition) {
11098         blackPlaysFirst = TRUE;
11099         currentMove = 1;        /* kludge */
11100         DisplayBothClocks();
11101         currentMove = 0;
11102     } else if (gameMode == IcsExamining) {
11103         SendToICS(ics_prefix);
11104         SendToICS("tomove black\n");
11105     }
11106 }
11107
11108 void
11109 EditPositionMenuEvent(selection, x, y)
11110      ChessSquare selection;
11111      int x, y;
11112 {
11113     char buf[MSG_SIZ];
11114     ChessSquare piece = boards[0][y][x];
11115
11116     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11117
11118     switch (selection) {
11119       case ClearBoard:
11120         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11121             SendToICS(ics_prefix);
11122             SendToICS("bsetup clear\n");
11123         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11124             SendToICS(ics_prefix);
11125             SendToICS("clearboard\n");
11126         } else {
11127             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11128                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11129                 for (y = 0; y < BOARD_HEIGHT; y++) {
11130                     if (gameMode == IcsExamining) {
11131                         if (boards[currentMove][y][x] != EmptySquare) {
11132                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11133                                     AAA + x, ONE + y);
11134                             SendToICS(buf);
11135                         }
11136                     } else {
11137                         boards[0][y][x] = p;
11138                     }
11139                 }
11140             }
11141         }
11142         if (gameMode == EditPosition) {
11143             DrawPosition(FALSE, boards[0]);
11144         }
11145         break;
11146
11147       case WhitePlay:
11148         SetWhiteToPlayEvent();
11149         break;
11150
11151       case BlackPlay:
11152         SetBlackToPlayEvent();
11153         break;
11154
11155       case EmptySquare:
11156         if (gameMode == IcsExamining) {
11157             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11158             SendToICS(buf);
11159         } else {
11160             boards[0][y][x] = EmptySquare;
11161             DrawPosition(FALSE, boards[0]);
11162         }
11163         break;
11164
11165       case PromotePiece:
11166         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11167            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11168             selection = (ChessSquare) (PROMOTED piece);
11169         } else if(piece == EmptySquare) selection = WhiteSilver;
11170         else selection = (ChessSquare)((int)piece - 1);
11171         goto defaultlabel;
11172
11173       case DemotePiece:
11174         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11175            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11176             selection = (ChessSquare) (DEMOTED piece);
11177         } else if(piece == EmptySquare) selection = BlackSilver;
11178         else selection = (ChessSquare)((int)piece + 1);       
11179         goto defaultlabel;
11180
11181       case WhiteQueen:
11182       case BlackQueen:
11183         if(gameInfo.variant == VariantShatranj ||
11184            gameInfo.variant == VariantXiangqi  ||
11185            gameInfo.variant == VariantCourier    )
11186             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11187         goto defaultlabel;
11188
11189       case WhiteKing:
11190       case BlackKing:
11191         if(gameInfo.variant == VariantXiangqi)
11192             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11193         if(gameInfo.variant == VariantKnightmate)
11194             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11195       default:
11196         defaultlabel:
11197         if (gameMode == IcsExamining) {
11198             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11199                     PieceToChar(selection), AAA + x, ONE + y);
11200             SendToICS(buf);
11201         } else {
11202             boards[0][y][x] = selection;
11203             DrawPosition(FALSE, boards[0]);
11204         }
11205         break;
11206     }
11207 }
11208
11209
11210 void
11211 DropMenuEvent(selection, x, y)
11212      ChessSquare selection;
11213      int x, y;
11214 {
11215     ChessMove moveType;
11216
11217     switch (gameMode) {
11218       case IcsPlayingWhite:
11219       case MachinePlaysBlack:
11220         if (!WhiteOnMove(currentMove)) {
11221             DisplayMoveError(_("It is Black's turn"));
11222             return;
11223         }
11224         moveType = WhiteDrop;
11225         break;
11226       case IcsPlayingBlack:
11227       case MachinePlaysWhite:
11228         if (WhiteOnMove(currentMove)) {
11229             DisplayMoveError(_("It is White's turn"));
11230             return;
11231         }
11232         moveType = BlackDrop;
11233         break;
11234       case EditGame:
11235         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11236         break;
11237       default:
11238         return;
11239     }
11240
11241     if (moveType == BlackDrop && selection < BlackPawn) {
11242       selection = (ChessSquare) ((int) selection
11243                                  + (int) BlackPawn - (int) WhitePawn);
11244     }
11245     if (boards[currentMove][y][x] != EmptySquare) {
11246         DisplayMoveError(_("That square is occupied"));
11247         return;
11248     }
11249
11250     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11251 }
11252
11253 void
11254 AcceptEvent()
11255 {
11256     /* Accept a pending offer of any kind from opponent */
11257     
11258     if (appData.icsActive) {
11259         SendToICS(ics_prefix);
11260         SendToICS("accept\n");
11261     } else if (cmailMsgLoaded) {
11262         if (currentMove == cmailOldMove &&
11263             commentList[cmailOldMove] != NULL &&
11264             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11265                    "Black offers a draw" : "White offers a draw")) {
11266             TruncateGame();
11267             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11268             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11269         } else {
11270             DisplayError(_("There is no pending offer on this move"), 0);
11271             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11272         }
11273     } else {
11274         /* Not used for offers from chess program */
11275     }
11276 }
11277
11278 void
11279 DeclineEvent()
11280 {
11281     /* Decline a pending offer of any kind from opponent */
11282     
11283     if (appData.icsActive) {
11284         SendToICS(ics_prefix);
11285         SendToICS("decline\n");
11286     } else if (cmailMsgLoaded) {
11287         if (currentMove == cmailOldMove &&
11288             commentList[cmailOldMove] != NULL &&
11289             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11290                    "Black offers a draw" : "White offers a draw")) {
11291 #ifdef NOTDEF
11292             AppendComment(cmailOldMove, "Draw declined");
11293             DisplayComment(cmailOldMove - 1, "Draw declined");
11294 #endif /*NOTDEF*/
11295         } else {
11296             DisplayError(_("There is no pending offer on this move"), 0);
11297         }
11298     } else {
11299         /* Not used for offers from chess program */
11300     }
11301 }
11302
11303 void
11304 RematchEvent()
11305 {
11306     /* Issue ICS rematch command */
11307     if (appData.icsActive) {
11308         SendToICS(ics_prefix);
11309         SendToICS("rematch\n");
11310     }
11311 }
11312
11313 void
11314 CallFlagEvent()
11315 {
11316     /* Call your opponent's flag (claim a win on time) */
11317     if (appData.icsActive) {
11318         SendToICS(ics_prefix);
11319         SendToICS("flag\n");
11320     } else {
11321         switch (gameMode) {
11322           default:
11323             return;
11324           case MachinePlaysWhite:
11325             if (whiteFlag) {
11326                 if (blackFlag)
11327                   GameEnds(GameIsDrawn, "Both players ran out of time",
11328                            GE_PLAYER);
11329                 else
11330                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11331             } else {
11332                 DisplayError(_("Your opponent is not out of time"), 0);
11333             }
11334             break;
11335           case MachinePlaysBlack:
11336             if (blackFlag) {
11337                 if (whiteFlag)
11338                   GameEnds(GameIsDrawn, "Both players ran out of time",
11339                            GE_PLAYER);
11340                 else
11341                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11342             } else {
11343                 DisplayError(_("Your opponent is not out of time"), 0);
11344             }
11345             break;
11346         }
11347     }
11348 }
11349
11350 void
11351 DrawEvent()
11352 {
11353     /* Offer draw or accept pending draw offer from opponent */
11354     
11355     if (appData.icsActive) {
11356         /* Note: tournament rules require draw offers to be
11357            made after you make your move but before you punch
11358            your clock.  Currently ICS doesn't let you do that;
11359            instead, you immediately punch your clock after making
11360            a move, but you can offer a draw at any time. */
11361         
11362         SendToICS(ics_prefix);
11363         SendToICS("draw\n");
11364     } else if (cmailMsgLoaded) {
11365         if (currentMove == cmailOldMove &&
11366             commentList[cmailOldMove] != NULL &&
11367             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11368                    "Black offers a draw" : "White offers a draw")) {
11369             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11370             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11371         } else if (currentMove == cmailOldMove + 1) {
11372             char *offer = WhiteOnMove(cmailOldMove) ?
11373               "White offers a draw" : "Black offers a draw";
11374             AppendComment(currentMove, offer);
11375             DisplayComment(currentMove - 1, offer);
11376             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11377         } else {
11378             DisplayError(_("You must make your move before offering a draw"), 0);
11379             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11380         }
11381     } else if (first.offeredDraw) {
11382         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11383     } else {
11384         if (first.sendDrawOffers) {
11385             SendToProgram("draw\n", &first);
11386             userOfferedDraw = TRUE;
11387         }
11388     }
11389 }
11390
11391 void
11392 AdjournEvent()
11393 {
11394     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11395     
11396     if (appData.icsActive) {
11397         SendToICS(ics_prefix);
11398         SendToICS("adjourn\n");
11399     } else {
11400         /* Currently GNU Chess doesn't offer or accept Adjourns */
11401     }
11402 }
11403
11404
11405 void
11406 AbortEvent()
11407 {
11408     /* Offer Abort or accept pending Abort offer from opponent */
11409     
11410     if (appData.icsActive) {
11411         SendToICS(ics_prefix);
11412         SendToICS("abort\n");
11413     } else {
11414         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11415     }
11416 }
11417
11418 void
11419 ResignEvent()
11420 {
11421     /* Resign.  You can do this even if it's not your turn. */
11422     
11423     if (appData.icsActive) {
11424         SendToICS(ics_prefix);
11425         SendToICS("resign\n");
11426     } else {
11427         switch (gameMode) {
11428           case MachinePlaysWhite:
11429             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11430             break;
11431           case MachinePlaysBlack:
11432             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11433             break;
11434           case EditGame:
11435             if (cmailMsgLoaded) {
11436                 TruncateGame();
11437                 if (WhiteOnMove(cmailOldMove)) {
11438                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11439                 } else {
11440                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11441                 }
11442                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11443             }
11444             break;
11445           default:
11446             break;
11447         }
11448     }
11449 }
11450
11451
11452 void
11453 StopObservingEvent()
11454 {
11455     /* Stop observing current games */
11456     SendToICS(ics_prefix);
11457     SendToICS("unobserve\n");
11458 }
11459
11460 void
11461 StopExaminingEvent()
11462 {
11463     /* Stop observing current game */
11464     SendToICS(ics_prefix);
11465     SendToICS("unexamine\n");
11466 }
11467
11468 void
11469 ForwardInner(target)
11470      int target;
11471 {
11472     int limit;
11473
11474     if (appData.debugMode)
11475         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11476                 target, currentMove, forwardMostMove);
11477
11478     if (gameMode == EditPosition)
11479       return;
11480
11481     if (gameMode == PlayFromGameFile && !pausing)
11482       PauseEvent();
11483     
11484     if (gameMode == IcsExamining && pausing)
11485       limit = pauseExamForwardMostMove;
11486     else
11487       limit = forwardMostMove;
11488     
11489     if (target > limit) target = limit;
11490
11491     if (target > 0 && moveList[target - 1][0]) {
11492         int fromX, fromY, toX, toY;
11493         toX = moveList[target - 1][2] - AAA;
11494         toY = moveList[target - 1][3] - ONE;
11495         if (moveList[target - 1][1] == '@') {
11496             if (appData.highlightLastMove) {
11497                 SetHighlights(-1, -1, toX, toY);
11498             }
11499         } else {
11500             fromX = moveList[target - 1][0] - AAA;
11501             fromY = moveList[target - 1][1] - ONE;
11502             if (target == currentMove + 1) {
11503                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11504             }
11505             if (appData.highlightLastMove) {
11506                 SetHighlights(fromX, fromY, toX, toY);
11507             }
11508         }
11509     }
11510     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11511         gameMode == Training || gameMode == PlayFromGameFile || 
11512         gameMode == AnalyzeFile) {
11513         while (currentMove < target) {
11514             SendMoveToProgram(currentMove++, &first);
11515         }
11516     } else {
11517         currentMove = target;
11518     }
11519     
11520     if (gameMode == EditGame || gameMode == EndOfGame) {
11521         whiteTimeRemaining = timeRemaining[0][currentMove];
11522         blackTimeRemaining = timeRemaining[1][currentMove];
11523     }
11524     DisplayBothClocks();
11525     DisplayMove(currentMove - 1);
11526     DrawPosition(FALSE, boards[currentMove]);
11527     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11528     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11529         DisplayComment(currentMove - 1, commentList[currentMove]);
11530     }
11531 }
11532
11533
11534 void
11535 ForwardEvent()
11536 {
11537     if (gameMode == IcsExamining && !pausing) {
11538         SendToICS(ics_prefix);
11539         SendToICS("forward\n");
11540     } else {
11541         ForwardInner(currentMove + 1);
11542     }
11543 }
11544
11545 void
11546 ToEndEvent()
11547 {
11548     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11549         /* to optimze, we temporarily turn off analysis mode while we feed
11550          * the remaining moves to the engine. Otherwise we get analysis output
11551          * after each move.
11552          */ 
11553         if (first.analysisSupport) {
11554           SendToProgram("exit\nforce\n", &first);
11555           first.analyzing = FALSE;
11556         }
11557     }
11558         
11559     if (gameMode == IcsExamining && !pausing) {
11560         SendToICS(ics_prefix);
11561         SendToICS("forward 999999\n");
11562     } else {
11563         ForwardInner(forwardMostMove);
11564     }
11565
11566     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11567         /* we have fed all the moves, so reactivate analysis mode */
11568         SendToProgram("analyze\n", &first);
11569         first.analyzing = TRUE;
11570         /*first.maybeThinking = TRUE;*/
11571         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11572     }
11573 }
11574
11575 void
11576 BackwardInner(target)
11577      int target;
11578 {
11579     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11580
11581     if (appData.debugMode)
11582         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11583                 target, currentMove, forwardMostMove);
11584
11585     if (gameMode == EditPosition) return;
11586     if (currentMove <= backwardMostMove) {
11587         ClearHighlights();
11588         DrawPosition(full_redraw, boards[currentMove]);
11589         return;
11590     }
11591     if (gameMode == PlayFromGameFile && !pausing)
11592       PauseEvent();
11593     
11594     if (moveList[target][0]) {
11595         int fromX, fromY, toX, toY;
11596         toX = moveList[target][2] - AAA;
11597         toY = moveList[target][3] - ONE;
11598         if (moveList[target][1] == '@') {
11599             if (appData.highlightLastMove) {
11600                 SetHighlights(-1, -1, toX, toY);
11601             }
11602         } else {
11603             fromX = moveList[target][0] - AAA;
11604             fromY = moveList[target][1] - ONE;
11605             if (target == currentMove - 1) {
11606                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11607             }
11608             if (appData.highlightLastMove) {
11609                 SetHighlights(fromX, fromY, toX, toY);
11610             }
11611         }
11612     }
11613     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11614         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11615         while (currentMove > target) {
11616             SendToProgram("undo\n", &first);
11617             currentMove--;
11618         }
11619     } else {
11620         currentMove = target;
11621     }
11622     
11623     if (gameMode == EditGame || gameMode == EndOfGame) {
11624         whiteTimeRemaining = timeRemaining[0][currentMove];
11625         blackTimeRemaining = timeRemaining[1][currentMove];
11626     }
11627     DisplayBothClocks();
11628     DisplayMove(currentMove - 1);
11629     DrawPosition(full_redraw, boards[currentMove]);
11630     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11631     // [HGM] PV info: routine tests if comment empty
11632     DisplayComment(currentMove - 1, commentList[currentMove]);
11633 }
11634
11635 void
11636 BackwardEvent()
11637 {
11638     if (gameMode == IcsExamining && !pausing) {
11639         SendToICS(ics_prefix);
11640         SendToICS("backward\n");
11641     } else {
11642         BackwardInner(currentMove - 1);
11643     }
11644 }
11645
11646 void
11647 ToStartEvent()
11648 {
11649     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11650         /* to optimze, we temporarily turn off analysis mode while we undo
11651          * all the moves. Otherwise we get analysis output after each undo.
11652          */ 
11653         if (first.analysisSupport) {
11654           SendToProgram("exit\nforce\n", &first);
11655           first.analyzing = FALSE;
11656         }
11657     }
11658
11659     if (gameMode == IcsExamining && !pausing) {
11660         SendToICS(ics_prefix);
11661         SendToICS("backward 999999\n");
11662     } else {
11663         BackwardInner(backwardMostMove);
11664     }
11665
11666     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11667         /* we have fed all the moves, so reactivate analysis mode */
11668         SendToProgram("analyze\n", &first);
11669         first.analyzing = TRUE;
11670         /*first.maybeThinking = TRUE;*/
11671         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11672     }
11673 }
11674
11675 void
11676 ToNrEvent(int to)
11677 {
11678   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11679   if (to >= forwardMostMove) to = forwardMostMove;
11680   if (to <= backwardMostMove) to = backwardMostMove;
11681   if (to < currentMove) {
11682     BackwardInner(to);
11683   } else {
11684     ForwardInner(to);
11685   }
11686 }
11687
11688 void
11689 RevertEvent()
11690 {
11691     if (gameMode != IcsExamining) {
11692         DisplayError(_("You are not examining a game"), 0);
11693         return;
11694     }
11695     if (pausing) {
11696         DisplayError(_("You can't revert while pausing"), 0);
11697         return;
11698     }
11699     SendToICS(ics_prefix);
11700     SendToICS("revert\n");
11701 }
11702
11703 void
11704 RetractMoveEvent()
11705 {
11706     switch (gameMode) {
11707       case MachinePlaysWhite:
11708       case MachinePlaysBlack:
11709         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11710             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11711             return;
11712         }
11713         if (forwardMostMove < 2) return;
11714         currentMove = forwardMostMove = forwardMostMove - 2;
11715         whiteTimeRemaining = timeRemaining[0][currentMove];
11716         blackTimeRemaining = timeRemaining[1][currentMove];
11717         DisplayBothClocks();
11718         DisplayMove(currentMove - 1);
11719         ClearHighlights();/*!! could figure this out*/
11720         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11721         SendToProgram("remove\n", &first);
11722         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11723         break;
11724
11725       case BeginningOfGame:
11726       default:
11727         break;
11728
11729       case IcsPlayingWhite:
11730       case IcsPlayingBlack:
11731         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11732             SendToICS(ics_prefix);
11733             SendToICS("takeback 2\n");
11734         } else {
11735             SendToICS(ics_prefix);
11736             SendToICS("takeback 1\n");
11737         }
11738         break;
11739     }
11740 }
11741
11742 void
11743 MoveNowEvent()
11744 {
11745     ChessProgramState *cps;
11746
11747     switch (gameMode) {
11748       case MachinePlaysWhite:
11749         if (!WhiteOnMove(forwardMostMove)) {
11750             DisplayError(_("It is your turn"), 0);
11751             return;
11752         }
11753         cps = &first;
11754         break;
11755       case MachinePlaysBlack:
11756         if (WhiteOnMove(forwardMostMove)) {
11757             DisplayError(_("It is your turn"), 0);
11758             return;
11759         }
11760         cps = &first;
11761         break;
11762       case TwoMachinesPlay:
11763         if (WhiteOnMove(forwardMostMove) ==
11764             (first.twoMachinesColor[0] == 'w')) {
11765             cps = &first;
11766         } else {
11767             cps = &second;
11768         }
11769         break;
11770       case BeginningOfGame:
11771       default:
11772         return;
11773     }
11774     SendToProgram("?\n", cps);
11775 }
11776
11777 void
11778 TruncateGameEvent()
11779 {
11780     EditGameEvent();
11781     if (gameMode != EditGame) return;
11782     TruncateGame();
11783 }
11784
11785 void
11786 TruncateGame()
11787 {
11788     if (forwardMostMove > currentMove) {
11789         if (gameInfo.resultDetails != NULL) {
11790             free(gameInfo.resultDetails);
11791             gameInfo.resultDetails = NULL;
11792             gameInfo.result = GameUnfinished;
11793         }
11794         forwardMostMove = currentMove;
11795         HistorySet(parseList, backwardMostMove, forwardMostMove,
11796                    currentMove-1);
11797     }
11798 }
11799
11800 void
11801 HintEvent()
11802 {
11803     if (appData.noChessProgram) return;
11804     switch (gameMode) {
11805       case MachinePlaysWhite:
11806         if (WhiteOnMove(forwardMostMove)) {
11807             DisplayError(_("Wait until your turn"), 0);
11808             return;
11809         }
11810         break;
11811       case BeginningOfGame:
11812       case MachinePlaysBlack:
11813         if (!WhiteOnMove(forwardMostMove)) {
11814             DisplayError(_("Wait until your turn"), 0);
11815             return;
11816         }
11817         break;
11818       default:
11819         DisplayError(_("No hint available"), 0);
11820         return;
11821     }
11822     SendToProgram("hint\n", &first);
11823     hintRequested = TRUE;
11824 }
11825
11826 void
11827 BookEvent()
11828 {
11829     if (appData.noChessProgram) return;
11830     switch (gameMode) {
11831       case MachinePlaysWhite:
11832         if (WhiteOnMove(forwardMostMove)) {
11833             DisplayError(_("Wait until your turn"), 0);
11834             return;
11835         }
11836         break;
11837       case BeginningOfGame:
11838       case MachinePlaysBlack:
11839         if (!WhiteOnMove(forwardMostMove)) {
11840             DisplayError(_("Wait until your turn"), 0);
11841             return;
11842         }
11843         break;
11844       case EditPosition:
11845         EditPositionDone();
11846         break;
11847       case TwoMachinesPlay:
11848         return;
11849       default:
11850         break;
11851     }
11852     SendToProgram("bk\n", &first);
11853     bookOutput[0] = NULLCHAR;
11854     bookRequested = TRUE;
11855 }
11856
11857 void
11858 AboutGameEvent()
11859 {
11860     char *tags = PGNTags(&gameInfo);
11861     TagsPopUp(tags, CmailMsg());
11862     free(tags);
11863 }
11864
11865 /* end button procedures */
11866
11867 void
11868 PrintPosition(fp, move)
11869      FILE *fp;
11870      int move;
11871 {
11872     int i, j;
11873     
11874     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11875         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11876             char c = PieceToChar(boards[move][i][j]);
11877             fputc(c == 'x' ? '.' : c, fp);
11878             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11879         }
11880     }
11881     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11882       fprintf(fp, "white to play\n");
11883     else
11884       fprintf(fp, "black to play\n");
11885 }
11886
11887 void
11888 PrintOpponents(fp)
11889      FILE *fp;
11890 {
11891     if (gameInfo.white != NULL) {
11892         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11893     } else {
11894         fprintf(fp, "\n");
11895     }
11896 }
11897
11898 /* Find last component of program's own name, using some heuristics */
11899 void
11900 TidyProgramName(prog, host, buf)
11901      char *prog, *host, buf[MSG_SIZ];
11902 {
11903     char *p, *q;
11904     int local = (strcmp(host, "localhost") == 0);
11905     while (!local && (p = strchr(prog, ';')) != NULL) {
11906         p++;
11907         while (*p == ' ') p++;
11908         prog = p;
11909     }
11910     if (*prog == '"' || *prog == '\'') {
11911         q = strchr(prog + 1, *prog);
11912     } else {
11913         q = strchr(prog, ' ');
11914     }
11915     if (q == NULL) q = prog + strlen(prog);
11916     p = q;
11917     while (p >= prog && *p != '/' && *p != '\\') p--;
11918     p++;
11919     if(p == prog && *p == '"') p++;
11920     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11921     memcpy(buf, p, q - p);
11922     buf[q - p] = NULLCHAR;
11923     if (!local) {
11924         strcat(buf, "@");
11925         strcat(buf, host);
11926     }
11927 }
11928
11929 char *
11930 TimeControlTagValue()
11931 {
11932     char buf[MSG_SIZ];
11933     if (!appData.clockMode) {
11934         strcpy(buf, "-");
11935     } else if (movesPerSession > 0) {
11936         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11937     } else if (timeIncrement == 0) {
11938         sprintf(buf, "%ld", timeControl/1000);
11939     } else {
11940         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11941     }
11942     return StrSave(buf);
11943 }
11944
11945 void
11946 SetGameInfo()
11947 {
11948     /* This routine is used only for certain modes */
11949     VariantClass v = gameInfo.variant;
11950     ClearGameInfo(&gameInfo);
11951     gameInfo.variant = v;
11952
11953     switch (gameMode) {
11954       case MachinePlaysWhite:
11955         gameInfo.event = StrSave( appData.pgnEventHeader );
11956         gameInfo.site = StrSave(HostName());
11957         gameInfo.date = PGNDate();
11958         gameInfo.round = StrSave("-");
11959         gameInfo.white = StrSave(first.tidy);
11960         gameInfo.black = StrSave(UserName());
11961         gameInfo.timeControl = TimeControlTagValue();
11962         break;
11963
11964       case MachinePlaysBlack:
11965         gameInfo.event = StrSave( appData.pgnEventHeader );
11966         gameInfo.site = StrSave(HostName());
11967         gameInfo.date = PGNDate();
11968         gameInfo.round = StrSave("-");
11969         gameInfo.white = StrSave(UserName());
11970         gameInfo.black = StrSave(first.tidy);
11971         gameInfo.timeControl = TimeControlTagValue();
11972         break;
11973
11974       case TwoMachinesPlay:
11975         gameInfo.event = StrSave( appData.pgnEventHeader );
11976         gameInfo.site = StrSave(HostName());
11977         gameInfo.date = PGNDate();
11978         if (matchGame > 0) {
11979             char buf[MSG_SIZ];
11980             sprintf(buf, "%d", matchGame);
11981             gameInfo.round = StrSave(buf);
11982         } else {
11983             gameInfo.round = StrSave("-");
11984         }
11985         if (first.twoMachinesColor[0] == 'w') {
11986             gameInfo.white = StrSave(first.tidy);
11987             gameInfo.black = StrSave(second.tidy);
11988         } else {
11989             gameInfo.white = StrSave(second.tidy);
11990             gameInfo.black = StrSave(first.tidy);
11991         }
11992         gameInfo.timeControl = TimeControlTagValue();
11993         break;
11994
11995       case EditGame:
11996         gameInfo.event = StrSave("Edited game");
11997         gameInfo.site = StrSave(HostName());
11998         gameInfo.date = PGNDate();
11999         gameInfo.round = StrSave("-");
12000         gameInfo.white = StrSave("-");
12001         gameInfo.black = StrSave("-");
12002         break;
12003
12004       case EditPosition:
12005         gameInfo.event = StrSave("Edited position");
12006         gameInfo.site = StrSave(HostName());
12007         gameInfo.date = PGNDate();
12008         gameInfo.round = StrSave("-");
12009         gameInfo.white = StrSave("-");
12010         gameInfo.black = StrSave("-");
12011         break;
12012
12013       case IcsPlayingWhite:
12014       case IcsPlayingBlack:
12015       case IcsObserving:
12016       case IcsExamining:
12017         break;
12018
12019       case PlayFromGameFile:
12020         gameInfo.event = StrSave("Game from non-PGN file");
12021         gameInfo.site = StrSave(HostName());
12022         gameInfo.date = PGNDate();
12023         gameInfo.round = StrSave("-");
12024         gameInfo.white = StrSave("?");
12025         gameInfo.black = StrSave("?");
12026         break;
12027
12028       default:
12029         break;
12030     }
12031 }
12032
12033 void
12034 ReplaceComment(index, text)
12035      int index;
12036      char *text;
12037 {
12038     int len;
12039
12040     while (*text == '\n') text++;
12041     len = strlen(text);
12042     while (len > 0 && text[len - 1] == '\n') len--;
12043
12044     if (commentList[index] != NULL)
12045       free(commentList[index]);
12046
12047     if (len == 0) {
12048         commentList[index] = NULL;
12049         return;
12050     }
12051     commentList[index] = (char *) malloc(len + 2);
12052     strncpy(commentList[index], text, len);
12053     commentList[index][len] = '\n';
12054     commentList[index][len + 1] = NULLCHAR;
12055 }
12056
12057 void
12058 CrushCRs(text)
12059      char *text;
12060 {
12061   char *p = text;
12062   char *q = text;
12063   char ch;
12064
12065   do {
12066     ch = *p++;
12067     if (ch == '\r') continue;
12068     *q++ = ch;
12069   } while (ch != '\0');
12070 }
12071
12072 void
12073 AppendComment(index, text)
12074      int index;
12075      char *text;
12076 {
12077     int oldlen, len;
12078     char *old;
12079
12080     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12081
12082     CrushCRs(text);
12083     while (*text == '\n') text++;
12084     len = strlen(text);
12085     while (len > 0 && text[len - 1] == '\n') len--;
12086
12087     if (len == 0) return;
12088
12089     if (commentList[index] != NULL) {
12090         old = commentList[index];
12091         oldlen = strlen(old);
12092         commentList[index] = (char *) malloc(oldlen + len + 2);
12093         strcpy(commentList[index], old);
12094         free(old);
12095         strncpy(&commentList[index][oldlen], text, len);
12096         commentList[index][oldlen + len] = '\n';
12097         commentList[index][oldlen + len + 1] = NULLCHAR;
12098     } else {
12099         commentList[index] = (char *) malloc(len + 2);
12100         strncpy(commentList[index], text, len);
12101         commentList[index][len] = '\n';
12102         commentList[index][len + 1] = NULLCHAR;
12103     }
12104 }
12105
12106 static char * FindStr( char * text, char * sub_text )
12107 {
12108     char * result = strstr( text, sub_text );
12109
12110     if( result != NULL ) {
12111         result += strlen( sub_text );
12112     }
12113
12114     return result;
12115 }
12116
12117 /* [AS] Try to extract PV info from PGN comment */
12118 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12119 char *GetInfoFromComment( int index, char * text )
12120 {
12121     char * sep = text;
12122
12123     if( text != NULL && index > 0 ) {
12124         int score = 0;
12125         int depth = 0;
12126         int time = -1, sec = 0, deci;
12127         char * s_eval = FindStr( text, "[%eval " );
12128         char * s_emt = FindStr( text, "[%emt " );
12129
12130         if( s_eval != NULL || s_emt != NULL ) {
12131             /* New style */
12132             char delim;
12133
12134             if( s_eval != NULL ) {
12135                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12136                     return text;
12137                 }
12138
12139                 if( delim != ']' ) {
12140                     return text;
12141                 }
12142             }
12143
12144             if( s_emt != NULL ) {
12145             }
12146         }
12147         else {
12148             /* We expect something like: [+|-]nnn.nn/dd */
12149             int score_lo = 0;
12150
12151             sep = strchr( text, '/' );
12152             if( sep == NULL || sep < (text+4) ) {
12153                 return text;
12154             }
12155
12156             time = -1; sec = -1; deci = -1;
12157             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12158                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12159                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12160                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12161                 return text;
12162             }
12163
12164             if( score_lo < 0 || score_lo >= 100 ) {
12165                 return text;
12166             }
12167
12168             if(sec >= 0) time = 600*time + 10*sec; else
12169             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12170
12171             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12172
12173             /* [HGM] PV time: now locate end of PV info */
12174             while( *++sep >= '0' && *sep <= '9'); // strip depth
12175             if(time >= 0)
12176             while( *++sep >= '0' && *sep <= '9'); // strip time
12177             if(sec >= 0)
12178             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12179             if(deci >= 0)
12180             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12181             while(*sep == ' ') sep++;
12182         }
12183
12184         if( depth <= 0 ) {
12185             return text;
12186         }
12187
12188         if( time < 0 ) {
12189             time = -1;
12190         }
12191
12192         pvInfoList[index-1].depth = depth;
12193         pvInfoList[index-1].score = score;
12194         pvInfoList[index-1].time  = 10*time; // centi-sec
12195     }
12196     return sep;
12197 }
12198
12199 void
12200 SendToProgram(message, cps)
12201      char *message;
12202      ChessProgramState *cps;
12203 {
12204     int count, outCount, error;
12205     char buf[MSG_SIZ];
12206
12207     if (cps->pr == NULL) return;
12208     Attention(cps);
12209     
12210     if (appData.debugMode) {
12211         TimeMark now;
12212         GetTimeMark(&now);
12213         fprintf(debugFP, "%ld >%-6s: %s", 
12214                 SubtractTimeMarks(&now, &programStartTime),
12215                 cps->which, message);
12216     }
12217     
12218     count = strlen(message);
12219     outCount = OutputToProcess(cps->pr, message, count, &error);
12220     if (outCount < count && !exiting 
12221                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12222         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12223         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12224             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12225                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12226                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12227             } else {
12228                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12229             }
12230             gameInfo.resultDetails = buf;
12231         }
12232         DisplayFatalError(buf, error, 1);
12233     }
12234 }
12235
12236 void
12237 ReceiveFromProgram(isr, closure, message, count, error)
12238      InputSourceRef isr;
12239      VOIDSTAR closure;
12240      char *message;
12241      int count;
12242      int error;
12243 {
12244     char *end_str;
12245     char buf[MSG_SIZ];
12246     ChessProgramState *cps = (ChessProgramState *)closure;
12247
12248     if (isr != cps->isr) return; /* Killed intentionally */
12249     if (count <= 0) {
12250         if (count == 0) {
12251             sprintf(buf,
12252                     _("Error: %s chess program (%s) exited unexpectedly"),
12253                     cps->which, cps->program);
12254         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12255                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12256                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12257                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12258                 } else {
12259                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12260                 }
12261                 gameInfo.resultDetails = buf;
12262             }
12263             RemoveInputSource(cps->isr);
12264             DisplayFatalError(buf, 0, 1);
12265         } else {
12266             sprintf(buf,
12267                     _("Error reading from %s chess program (%s)"),
12268                     cps->which, cps->program);
12269             RemoveInputSource(cps->isr);
12270
12271             /* [AS] Program is misbehaving badly... kill it */
12272             if( count == -2 ) {
12273                 DestroyChildProcess( cps->pr, 9 );
12274                 cps->pr = NoProc;
12275             }
12276
12277             DisplayFatalError(buf, error, 1);
12278         }
12279         return;
12280     }
12281     
12282     if ((end_str = strchr(message, '\r')) != NULL)
12283       *end_str = NULLCHAR;
12284     if ((end_str = strchr(message, '\n')) != NULL)
12285       *end_str = NULLCHAR;
12286     
12287     if (appData.debugMode) {
12288         TimeMark now; int print = 1;
12289         char *quote = ""; char c; int i;
12290
12291         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12292                 char start = message[0];
12293                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12294                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12295                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12296                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12297                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12298                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12299                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12300                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12301                         { quote = "# "; print = (appData.engineComments == 2); }
12302                 message[0] = start; // restore original message
12303         }
12304         if(print) {
12305                 GetTimeMark(&now);
12306                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12307                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12308                         quote,
12309                         message);
12310         }
12311     }
12312
12313     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12314     if (appData.icsEngineAnalyze) {
12315         if (strstr(message, "whisper") != NULL ||
12316              strstr(message, "kibitz") != NULL || 
12317             strstr(message, "tellics") != NULL) return;
12318     }
12319
12320     HandleMachineMove(message, cps);
12321 }
12322
12323
12324 void
12325 SendTimeControl(cps, mps, tc, inc, sd, st)
12326      ChessProgramState *cps;
12327      int mps, inc, sd, st;
12328      long tc;
12329 {
12330     char buf[MSG_SIZ];
12331     int seconds;
12332
12333     if( timeControl_2 > 0 ) {
12334         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12335             tc = timeControl_2;
12336         }
12337     }
12338     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12339     inc /= cps->timeOdds;
12340     st  /= cps->timeOdds;
12341
12342     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12343
12344     if (st > 0) {
12345       /* Set exact time per move, normally using st command */
12346       if (cps->stKludge) {
12347         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12348         seconds = st % 60;
12349         if (seconds == 0) {
12350           sprintf(buf, "level 1 %d\n", st/60);
12351         } else {
12352           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12353         }
12354       } else {
12355         sprintf(buf, "st %d\n", st);
12356       }
12357     } else {
12358       /* Set conventional or incremental time control, using level command */
12359       if (seconds == 0) {
12360         /* Note old gnuchess bug -- minutes:seconds used to not work.
12361            Fixed in later versions, but still avoid :seconds
12362            when seconds is 0. */
12363         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12364       } else {
12365         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12366                 seconds, inc/1000);
12367       }
12368     }
12369     SendToProgram(buf, cps);
12370
12371     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12372     /* Orthogonally, limit search to given depth */
12373     if (sd > 0) {
12374       if (cps->sdKludge) {
12375         sprintf(buf, "depth\n%d\n", sd);
12376       } else {
12377         sprintf(buf, "sd %d\n", sd);
12378       }
12379       SendToProgram(buf, cps);
12380     }
12381
12382     if(cps->nps > 0) { /* [HGM] nps */
12383         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12384         else {
12385                 sprintf(buf, "nps %d\n", cps->nps);
12386               SendToProgram(buf, cps);
12387         }
12388     }
12389 }
12390
12391 ChessProgramState *WhitePlayer()
12392 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12393 {
12394     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12395        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12396         return &second;
12397     return &first;
12398 }
12399
12400 void
12401 SendTimeRemaining(cps, machineWhite)
12402      ChessProgramState *cps;
12403      int /*boolean*/ machineWhite;
12404 {
12405     char message[MSG_SIZ];
12406     long time, otime;
12407
12408     /* Note: this routine must be called when the clocks are stopped
12409        or when they have *just* been set or switched; otherwise
12410        it will be off by the time since the current tick started.
12411     */
12412     if (machineWhite) {
12413         time = whiteTimeRemaining / 10;
12414         otime = blackTimeRemaining / 10;
12415     } else {
12416         time = blackTimeRemaining / 10;
12417         otime = whiteTimeRemaining / 10;
12418     }
12419     /* [HGM] translate opponent's time by time-odds factor */
12420     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12421     if (appData.debugMode) {
12422         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12423     }
12424
12425     if (time <= 0) time = 1;
12426     if (otime <= 0) otime = 1;
12427     
12428     sprintf(message, "time %ld\n", time);
12429     SendToProgram(message, cps);
12430
12431     sprintf(message, "otim %ld\n", otime);
12432     SendToProgram(message, cps);
12433 }
12434
12435 int
12436 BoolFeature(p, name, loc, cps)
12437      char **p;
12438      char *name;
12439      int *loc;
12440      ChessProgramState *cps;
12441 {
12442   char buf[MSG_SIZ];
12443   int len = strlen(name);
12444   int val;
12445   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12446     (*p) += len + 1;
12447     sscanf(*p, "%d", &val);
12448     *loc = (val != 0);
12449     while (**p && **p != ' ') (*p)++;
12450     sprintf(buf, "accepted %s\n", name);
12451     SendToProgram(buf, cps);
12452     return TRUE;
12453   }
12454   return FALSE;
12455 }
12456
12457 int
12458 IntFeature(p, name, loc, cps)
12459      char **p;
12460      char *name;
12461      int *loc;
12462      ChessProgramState *cps;
12463 {
12464   char buf[MSG_SIZ];
12465   int len = strlen(name);
12466   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12467     (*p) += len + 1;
12468     sscanf(*p, "%d", loc);
12469     while (**p && **p != ' ') (*p)++;
12470     sprintf(buf, "accepted %s\n", name);
12471     SendToProgram(buf, cps);
12472     return TRUE;
12473   }
12474   return FALSE;
12475 }
12476
12477 int
12478 StringFeature(p, name, loc, cps)
12479      char **p;
12480      char *name;
12481      char loc[];
12482      ChessProgramState *cps;
12483 {
12484   char buf[MSG_SIZ];
12485   int len = strlen(name);
12486   if (strncmp((*p), name, len) == 0
12487       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12488     (*p) += len + 2;
12489     sscanf(*p, "%[^\"]", loc);
12490     while (**p && **p != '\"') (*p)++;
12491     if (**p == '\"') (*p)++;
12492     sprintf(buf, "accepted %s\n", name);
12493     SendToProgram(buf, cps);
12494     return TRUE;
12495   }
12496   return FALSE;
12497 }
12498
12499 int 
12500 ParseOption(Option *opt, ChessProgramState *cps)
12501 // [HGM] options: process the string that defines an engine option, and determine
12502 // name, type, default value, and allowed value range
12503 {
12504         char *p, *q, buf[MSG_SIZ];
12505         int n, min = (-1)<<31, max = 1<<31, def;
12506
12507         if(p = strstr(opt->name, " -spin ")) {
12508             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12509             if(max < min) max = min; // enforce consistency
12510             if(def < min) def = min;
12511             if(def > max) def = max;
12512             opt->value = def;
12513             opt->min = min;
12514             opt->max = max;
12515             opt->type = Spin;
12516         } else if((p = strstr(opt->name, " -slider "))) {
12517             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12518             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12519             if(max < min) max = min; // enforce consistency
12520             if(def < min) def = min;
12521             if(def > max) def = max;
12522             opt->value = def;
12523             opt->min = min;
12524             opt->max = max;
12525             opt->type = Spin; // Slider;
12526         } else if((p = strstr(opt->name, " -string "))) {
12527             opt->textValue = p+9;
12528             opt->type = TextBox;
12529         } else if((p = strstr(opt->name, " -file "))) {
12530             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12531             opt->textValue = p+7;
12532             opt->type = TextBox; // FileName;
12533         } else if((p = strstr(opt->name, " -path "))) {
12534             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12535             opt->textValue = p+7;
12536             opt->type = TextBox; // PathName;
12537         } else if(p = strstr(opt->name, " -check ")) {
12538             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12539             opt->value = (def != 0);
12540             opt->type = CheckBox;
12541         } else if(p = strstr(opt->name, " -combo ")) {
12542             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12543             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12544             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12545             opt->value = n = 0;
12546             while(q = StrStr(q, " /// ")) {
12547                 n++; *q = 0;    // count choices, and null-terminate each of them
12548                 q += 5;
12549                 if(*q == '*') { // remember default, which is marked with * prefix
12550                     q++;
12551                     opt->value = n;
12552                 }
12553                 cps->comboList[cps->comboCnt++] = q;
12554             }
12555             cps->comboList[cps->comboCnt++] = NULL;
12556             opt->max = n + 1;
12557             opt->type = ComboBox;
12558         } else if(p = strstr(opt->name, " -button")) {
12559             opt->type = Button;
12560         } else if(p = strstr(opt->name, " -save")) {
12561             opt->type = SaveButton;
12562         } else return FALSE;
12563         *p = 0; // terminate option name
12564         // now look if the command-line options define a setting for this engine option.
12565         if(cps->optionSettings && cps->optionSettings[0])
12566             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12567         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12568                 sprintf(buf, "option %s", p);
12569                 if(p = strstr(buf, ",")) *p = 0;
12570                 strcat(buf, "\n");
12571                 SendToProgram(buf, cps);
12572         }
12573         return TRUE;
12574 }
12575
12576 void
12577 FeatureDone(cps, val)
12578      ChessProgramState* cps;
12579      int val;
12580 {
12581   DelayedEventCallback cb = GetDelayedEvent();
12582   if ((cb == InitBackEnd3 && cps == &first) ||
12583       (cb == TwoMachinesEventIfReady && cps == &second)) {
12584     CancelDelayedEvent();
12585     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12586   }
12587   cps->initDone = val;
12588 }
12589
12590 /* Parse feature command from engine */
12591 void
12592 ParseFeatures(args, cps)
12593      char* args;
12594      ChessProgramState *cps;  
12595 {
12596   char *p = args;
12597   char *q;
12598   int val;
12599   char buf[MSG_SIZ];
12600
12601   for (;;) {
12602     while (*p == ' ') p++;
12603     if (*p == NULLCHAR) return;
12604
12605     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12606     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12607     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12608     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12609     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12610     if (BoolFeature(&p, "reuse", &val, cps)) {
12611       /* Engine can disable reuse, but can't enable it if user said no */
12612       if (!val) cps->reuse = FALSE;
12613       continue;
12614     }
12615     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12616     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12617       if (gameMode == TwoMachinesPlay) {
12618         DisplayTwoMachinesTitle();
12619       } else {
12620         DisplayTitle("");
12621       }
12622       continue;
12623     }
12624     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12625     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12626     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12627     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12628     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12629     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12630     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12631     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12632     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12633     if (IntFeature(&p, "done", &val, cps)) {
12634       FeatureDone(cps, val);
12635       continue;
12636     }
12637     /* Added by Tord: */
12638     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12639     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12640     /* End of additions by Tord */
12641
12642     /* [HGM] added features: */
12643     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12644     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12645     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12646     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12647     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12648     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12649     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12650         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12651             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12652             SendToProgram(buf, cps);
12653             continue;
12654         }
12655         if(cps->nrOptions >= MAX_OPTIONS) {
12656             cps->nrOptions--;
12657             sprintf(buf, "%s engine has too many options\n", cps->which);
12658             DisplayError(buf, 0);
12659         }
12660         continue;
12661     }
12662     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12663     /* End of additions by HGM */
12664
12665     /* unknown feature: complain and skip */
12666     q = p;
12667     while (*q && *q != '=') q++;
12668     sprintf(buf, "rejected %.*s\n", q-p, p);
12669     SendToProgram(buf, cps);
12670     p = q;
12671     if (*p == '=') {
12672       p++;
12673       if (*p == '\"') {
12674         p++;
12675         while (*p && *p != '\"') p++;
12676         if (*p == '\"') p++;
12677       } else {
12678         while (*p && *p != ' ') p++;
12679       }
12680     }
12681   }
12682
12683 }
12684
12685 void
12686 PeriodicUpdatesEvent(newState)
12687      int newState;
12688 {
12689     if (newState == appData.periodicUpdates)
12690       return;
12691
12692     appData.periodicUpdates=newState;
12693
12694     /* Display type changes, so update it now */
12695     DisplayAnalysis();
12696
12697     /* Get the ball rolling again... */
12698     if (newState) {
12699         AnalysisPeriodicEvent(1);
12700         StartAnalysisClock();
12701     }
12702 }
12703
12704 void
12705 PonderNextMoveEvent(newState)
12706      int newState;
12707 {
12708     if (newState == appData.ponderNextMove) return;
12709     if (gameMode == EditPosition) EditPositionDone();
12710     if (newState) {
12711         SendToProgram("hard\n", &first);
12712         if (gameMode == TwoMachinesPlay) {
12713             SendToProgram("hard\n", &second);
12714         }
12715     } else {
12716         SendToProgram("easy\n", &first);
12717         thinkOutput[0] = NULLCHAR;
12718         if (gameMode == TwoMachinesPlay) {
12719             SendToProgram("easy\n", &second);
12720         }
12721     }
12722     appData.ponderNextMove = newState;
12723 }
12724
12725 void
12726 NewSettingEvent(option, command, value)
12727      char *command;
12728      int option, value;
12729 {
12730     char buf[MSG_SIZ];
12731
12732     if (gameMode == EditPosition) EditPositionDone();
12733     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12734     SendToProgram(buf, &first);
12735     if (gameMode == TwoMachinesPlay) {
12736         SendToProgram(buf, &second);
12737     }
12738 }
12739
12740 void
12741 ShowThinkingEvent()
12742 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12743 {
12744     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12745     int newState = appData.showThinking
12746         // [HGM] thinking: other features now need thinking output as well
12747         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12748     
12749     if (oldState == newState) return;
12750     oldState = newState;
12751     if (gameMode == EditPosition) EditPositionDone();
12752     if (oldState) {
12753         SendToProgram("post\n", &first);
12754         if (gameMode == TwoMachinesPlay) {
12755             SendToProgram("post\n", &second);
12756         }
12757     } else {
12758         SendToProgram("nopost\n", &first);
12759         thinkOutput[0] = NULLCHAR;
12760         if (gameMode == TwoMachinesPlay) {
12761             SendToProgram("nopost\n", &second);
12762         }
12763     }
12764 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12765 }
12766
12767 void
12768 AskQuestionEvent(title, question, replyPrefix, which)
12769      char *title; char *question; char *replyPrefix; char *which;
12770 {
12771   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12772   if (pr == NoProc) return;
12773   AskQuestion(title, question, replyPrefix, pr);
12774 }
12775
12776 void
12777 DisplayMove(moveNumber)
12778      int moveNumber;
12779 {
12780     char message[MSG_SIZ];
12781     char res[MSG_SIZ];
12782     char cpThinkOutput[MSG_SIZ];
12783
12784     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12785     
12786     if (moveNumber == forwardMostMove - 1 || 
12787         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12788
12789         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12790
12791         if (strchr(cpThinkOutput, '\n')) {
12792             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12793         }
12794     } else {
12795         *cpThinkOutput = NULLCHAR;
12796     }
12797
12798     /* [AS] Hide thinking from human user */
12799     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12800         *cpThinkOutput = NULLCHAR;
12801         if( thinkOutput[0] != NULLCHAR ) {
12802             int i;
12803
12804             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12805                 cpThinkOutput[i] = '.';
12806             }
12807             cpThinkOutput[i] = NULLCHAR;
12808             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12809         }
12810     }
12811
12812     if (moveNumber == forwardMostMove - 1 &&
12813         gameInfo.resultDetails != NULL) {
12814         if (gameInfo.resultDetails[0] == NULLCHAR) {
12815             sprintf(res, " %s", PGNResult(gameInfo.result));
12816         } else {
12817             sprintf(res, " {%s} %s",
12818                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12819         }
12820     } else {
12821         res[0] = NULLCHAR;
12822     }
12823
12824     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12825         DisplayMessage(res, cpThinkOutput);
12826     } else {
12827         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12828                 WhiteOnMove(moveNumber) ? " " : ".. ",
12829                 parseList[moveNumber], res);
12830         DisplayMessage(message, cpThinkOutput);
12831     }
12832 }
12833
12834 void
12835 DisplayAnalysisText(text)
12836      char *text;
12837 {
12838     char buf[MSG_SIZ];
12839
12840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12841                || appData.icsEngineAnalyze) {
12842         sprintf(buf, "Analysis (%s)", first.tidy);
12843         AnalysisPopUp(buf, text);
12844     }
12845 }
12846
12847 static int
12848 only_one_move(str)
12849      char *str;
12850 {
12851     while (*str && isspace(*str)) ++str;
12852     while (*str && !isspace(*str)) ++str;
12853     if (!*str) return 1;
12854     while (*str && isspace(*str)) ++str;
12855     if (!*str) return 1;
12856     return 0;
12857 }
12858
12859 void
12860 DisplayAnalysis()
12861 {
12862     char buf[MSG_SIZ];
12863     char lst[MSG_SIZ / 2];
12864     double nps;
12865     static char *xtra[] = { "", " (--)", " (++)" };
12866     int h, m, s, cs;
12867   
12868     if (programStats.time == 0) {
12869         programStats.time = 1;
12870     }
12871   
12872     if (programStats.got_only_move) {
12873         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12874     } else {
12875         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12876
12877         nps = (u64ToDouble(programStats.nodes) /
12878              ((double)programStats.time /100.0));
12879
12880         cs = programStats.time % 100;
12881         s = programStats.time / 100;
12882         h = (s / (60*60));
12883         s = s - h*60*60;
12884         m = (s/60);
12885         s = s - m*60;
12886
12887         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12888           if (programStats.move_name[0] != NULLCHAR) {
12889             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12890                     programStats.depth,
12891                     programStats.nr_moves-programStats.moves_left,
12892                     programStats.nr_moves, programStats.move_name,
12893                     ((float)programStats.score)/100.0, lst,
12894                     only_one_move(lst)?
12895                     xtra[programStats.got_fail] : "",
12896                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12897           } else {
12898             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12899                     programStats.depth,
12900                     programStats.nr_moves-programStats.moves_left,
12901                     programStats.nr_moves, ((float)programStats.score)/100.0,
12902                     lst,
12903                     only_one_move(lst)?
12904                     xtra[programStats.got_fail] : "",
12905                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12906           }
12907         } else {
12908             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12909                     programStats.depth,
12910                     ((float)programStats.score)/100.0,
12911                     lst,
12912                     only_one_move(lst)?
12913                     xtra[programStats.got_fail] : "",
12914                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12915         }
12916     }
12917     DisplayAnalysisText(buf);
12918 }
12919
12920 void
12921 DisplayComment(moveNumber, text)
12922      int moveNumber;
12923      char *text;
12924 {
12925     char title[MSG_SIZ];
12926     char buf[8000]; // comment can be long!
12927     int score, depth;
12928
12929     if( appData.autoDisplayComment ) {
12930         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12931             strcpy(title, "Comment");
12932         } else {
12933             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12934                     WhiteOnMove(moveNumber) ? " " : ".. ",
12935                     parseList[moveNumber]);
12936         }
12937         // [HGM] PV info: display PV info together with (or as) comment
12938         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12939             if(text == NULL) text = "";                                           
12940             score = pvInfoList[moveNumber].score;
12941             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12942                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12943             text = buf;
12944         }
12945     } else title[0] = 0;
12946
12947     if (text != NULL)
12948         CommentPopUp(title, text);
12949 }
12950
12951 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12952  * might be busy thinking or pondering.  It can be omitted if your
12953  * gnuchess is configured to stop thinking immediately on any user
12954  * input.  However, that gnuchess feature depends on the FIONREAD
12955  * ioctl, which does not work properly on some flavors of Unix.
12956  */
12957 void
12958 Attention(cps)
12959      ChessProgramState *cps;
12960 {
12961 #if ATTENTION
12962     if (!cps->useSigint) return;
12963     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12964     switch (gameMode) {
12965       case MachinePlaysWhite:
12966       case MachinePlaysBlack:
12967       case TwoMachinesPlay:
12968       case IcsPlayingWhite:
12969       case IcsPlayingBlack:
12970       case AnalyzeMode:
12971       case AnalyzeFile:
12972         /* Skip if we know it isn't thinking */
12973         if (!cps->maybeThinking) return;
12974         if (appData.debugMode)
12975           fprintf(debugFP, "Interrupting %s\n", cps->which);
12976         InterruptChildProcess(cps->pr);
12977         cps->maybeThinking = FALSE;
12978         break;
12979       default:
12980         break;
12981     }
12982 #endif /*ATTENTION*/
12983 }
12984
12985 int
12986 CheckFlags()
12987 {
12988     if (whiteTimeRemaining <= 0) {
12989         if (!whiteFlag) {
12990             whiteFlag = TRUE;
12991             if (appData.icsActive) {
12992                 if (appData.autoCallFlag &&
12993                     gameMode == IcsPlayingBlack && !blackFlag) {
12994                   SendToICS(ics_prefix);
12995                   SendToICS("flag\n");
12996                 }
12997             } else {
12998                 if (blackFlag) {
12999                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13000                 } else {
13001                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13002                     if (appData.autoCallFlag) {
13003                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13004                         return TRUE;
13005                     }
13006                 }
13007             }
13008         }
13009     }
13010     if (blackTimeRemaining <= 0) {
13011         if (!blackFlag) {
13012             blackFlag = TRUE;
13013             if (appData.icsActive) {
13014                 if (appData.autoCallFlag &&
13015                     gameMode == IcsPlayingWhite && !whiteFlag) {
13016                   SendToICS(ics_prefix);
13017                   SendToICS("flag\n");
13018                 }
13019             } else {
13020                 if (whiteFlag) {
13021                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13022                 } else {
13023                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13024                     if (appData.autoCallFlag) {
13025                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13026                         return TRUE;
13027                     }
13028                 }
13029             }
13030         }
13031     }
13032     return FALSE;
13033 }
13034
13035 void
13036 CheckTimeControl()
13037 {
13038     if (!appData.clockMode || appData.icsActive ||
13039         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13040
13041     /*
13042      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13043      */
13044     if ( !WhiteOnMove(forwardMostMove) )
13045         /* White made time control */
13046         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13047         /* [HGM] time odds: correct new time quota for time odds! */
13048                                             / WhitePlayer()->timeOdds;
13049       else
13050         /* Black made time control */
13051         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13052                                             / WhitePlayer()->other->timeOdds;
13053 }
13054
13055 void
13056 DisplayBothClocks()
13057 {
13058     int wom = gameMode == EditPosition ?
13059       !blackPlaysFirst : WhiteOnMove(currentMove);
13060     DisplayWhiteClock(whiteTimeRemaining, wom);
13061     DisplayBlackClock(blackTimeRemaining, !wom);
13062 }
13063
13064
13065 /* Timekeeping seems to be a portability nightmare.  I think everyone
13066    has ftime(), but I'm really not sure, so I'm including some ifdefs
13067    to use other calls if you don't.  Clocks will be less accurate if
13068    you have neither ftime nor gettimeofday.
13069 */
13070
13071 /* VS 2008 requires the #include outside of the function */
13072 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13073 #include <sys/timeb.h>
13074 #endif
13075
13076 /* Get the current time as a TimeMark */
13077 void
13078 GetTimeMark(tm)
13079      TimeMark *tm;
13080 {
13081 #if HAVE_GETTIMEOFDAY
13082
13083     struct timeval timeVal;
13084     struct timezone timeZone;
13085
13086     gettimeofday(&timeVal, &timeZone);
13087     tm->sec = (long) timeVal.tv_sec; 
13088     tm->ms = (int) (timeVal.tv_usec / 1000L);
13089
13090 #else /*!HAVE_GETTIMEOFDAY*/
13091 #if HAVE_FTIME
13092
13093 // include <sys/timeb.h> / moved to just above start of function
13094     struct timeb timeB;
13095
13096     ftime(&timeB);
13097     tm->sec = (long) timeB.time;
13098     tm->ms = (int) timeB.millitm;
13099
13100 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13101     tm->sec = (long) time(NULL);
13102     tm->ms = 0;
13103 #endif
13104 #endif
13105 }
13106
13107 /* Return the difference in milliseconds between two
13108    time marks.  We assume the difference will fit in a long!
13109 */
13110 long
13111 SubtractTimeMarks(tm2, tm1)
13112      TimeMark *tm2, *tm1;
13113 {
13114     return 1000L*(tm2->sec - tm1->sec) +
13115            (long) (tm2->ms - tm1->ms);
13116 }
13117
13118
13119 /*
13120  * Code to manage the game clocks.
13121  *
13122  * In tournament play, black starts the clock and then white makes a move.
13123  * We give the human user a slight advantage if he is playing white---the
13124  * clocks don't run until he makes his first move, so it takes zero time.
13125  * Also, we don't account for network lag, so we could get out of sync
13126  * with GNU Chess's clock -- but then, referees are always right.  
13127  */
13128
13129 static TimeMark tickStartTM;
13130 static long intendedTickLength;
13131
13132 long
13133 NextTickLength(timeRemaining)
13134      long timeRemaining;
13135 {
13136     long nominalTickLength, nextTickLength;
13137
13138     if (timeRemaining > 0L && timeRemaining <= 10000L)
13139       nominalTickLength = 100L;
13140     else
13141       nominalTickLength = 1000L;
13142     nextTickLength = timeRemaining % nominalTickLength;
13143     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13144
13145     return nextTickLength;
13146 }
13147
13148 /* Adjust clock one minute up or down */
13149 void
13150 AdjustClock(Boolean which, int dir)
13151 {
13152     if(which) blackTimeRemaining += 60000*dir;
13153     else      whiteTimeRemaining += 60000*dir;
13154     DisplayBothClocks();
13155 }
13156
13157 /* Stop clocks and reset to a fresh time control */
13158 void
13159 ResetClocks() 
13160 {
13161     (void) StopClockTimer();
13162     if (appData.icsActive) {
13163         whiteTimeRemaining = blackTimeRemaining = 0;
13164     } else { /* [HGM] correct new time quote for time odds */
13165         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13166         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13167     }
13168     if (whiteFlag || blackFlag) {
13169         DisplayTitle("");
13170         whiteFlag = blackFlag = FALSE;
13171     }
13172     DisplayBothClocks();
13173 }
13174
13175 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13176
13177 /* Decrement running clock by amount of time that has passed */
13178 void
13179 DecrementClocks()
13180 {
13181     long timeRemaining;
13182     long lastTickLength, fudge;
13183     TimeMark now;
13184
13185     if (!appData.clockMode) return;
13186     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13187         
13188     GetTimeMark(&now);
13189
13190     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13191
13192     /* Fudge if we woke up a little too soon */
13193     fudge = intendedTickLength - lastTickLength;
13194     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13195
13196     if (WhiteOnMove(forwardMostMove)) {
13197         if(whiteNPS >= 0) lastTickLength = 0;
13198         timeRemaining = whiteTimeRemaining -= lastTickLength;
13199         DisplayWhiteClock(whiteTimeRemaining - fudge,
13200                           WhiteOnMove(currentMove));
13201     } else {
13202         if(blackNPS >= 0) lastTickLength = 0;
13203         timeRemaining = blackTimeRemaining -= lastTickLength;
13204         DisplayBlackClock(blackTimeRemaining - fudge,
13205                           !WhiteOnMove(currentMove));
13206     }
13207
13208     if (CheckFlags()) return;
13209         
13210     tickStartTM = now;
13211     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13212     StartClockTimer(intendedTickLength);
13213
13214     /* if the time remaining has fallen below the alarm threshold, sound the
13215      * alarm. if the alarm has sounded and (due to a takeback or time control
13216      * with increment) the time remaining has increased to a level above the
13217      * threshold, reset the alarm so it can sound again. 
13218      */
13219     
13220     if (appData.icsActive && appData.icsAlarm) {
13221
13222         /* make sure we are dealing with the user's clock */
13223         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13224                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13225            )) return;
13226
13227         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13228             alarmSounded = FALSE;
13229         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13230             PlayAlarmSound();
13231             alarmSounded = TRUE;
13232         }
13233     }
13234 }
13235
13236
13237 /* A player has just moved, so stop the previously running
13238    clock and (if in clock mode) start the other one.
13239    We redisplay both clocks in case we're in ICS mode, because
13240    ICS gives us an update to both clocks after every move.
13241    Note that this routine is called *after* forwardMostMove
13242    is updated, so the last fractional tick must be subtracted
13243    from the color that is *not* on move now.
13244 */
13245 void
13246 SwitchClocks()
13247 {
13248     long lastTickLength;
13249     TimeMark now;
13250     int flagged = FALSE;
13251
13252     GetTimeMark(&now);
13253
13254     if (StopClockTimer() && appData.clockMode) {
13255         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13256         if (WhiteOnMove(forwardMostMove)) {
13257             if(blackNPS >= 0) lastTickLength = 0;
13258             blackTimeRemaining -= lastTickLength;
13259            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13260 //         if(pvInfoList[forwardMostMove-1].time == -1)
13261                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13262                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13263         } else {
13264            if(whiteNPS >= 0) lastTickLength = 0;
13265            whiteTimeRemaining -= lastTickLength;
13266            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13267 //         if(pvInfoList[forwardMostMove-1].time == -1)
13268                  pvInfoList[forwardMostMove-1].time = 
13269                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13270         }
13271         flagged = CheckFlags();
13272     }
13273     CheckTimeControl();
13274
13275     if (flagged || !appData.clockMode) return;
13276
13277     switch (gameMode) {
13278       case MachinePlaysBlack:
13279       case MachinePlaysWhite:
13280       case BeginningOfGame:
13281         if (pausing) return;
13282         break;
13283
13284       case EditGame:
13285       case PlayFromGameFile:
13286       case IcsExamining:
13287         return;
13288
13289       default:
13290         break;
13291     }
13292
13293     tickStartTM = now;
13294     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13295       whiteTimeRemaining : blackTimeRemaining);
13296     StartClockTimer(intendedTickLength);
13297 }
13298         
13299
13300 /* Stop both clocks */
13301 void
13302 StopClocks()
13303 {       
13304     long lastTickLength;
13305     TimeMark now;
13306
13307     if (!StopClockTimer()) return;
13308     if (!appData.clockMode) return;
13309
13310     GetTimeMark(&now);
13311
13312     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13313     if (WhiteOnMove(forwardMostMove)) {
13314         if(whiteNPS >= 0) lastTickLength = 0;
13315         whiteTimeRemaining -= lastTickLength;
13316         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13317     } else {
13318         if(blackNPS >= 0) lastTickLength = 0;
13319         blackTimeRemaining -= lastTickLength;
13320         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13321     }
13322     CheckFlags();
13323 }
13324         
13325 /* Start clock of player on move.  Time may have been reset, so
13326    if clock is already running, stop and restart it. */
13327 void
13328 StartClocks()
13329 {
13330     (void) StopClockTimer(); /* in case it was running already */
13331     DisplayBothClocks();
13332     if (CheckFlags()) return;
13333
13334     if (!appData.clockMode) return;
13335     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13336
13337     GetTimeMark(&tickStartTM);
13338     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13339       whiteTimeRemaining : blackTimeRemaining);
13340
13341    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13342     whiteNPS = blackNPS = -1; 
13343     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13344        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13345         whiteNPS = first.nps;
13346     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13347        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13348         blackNPS = first.nps;
13349     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13350         whiteNPS = second.nps;
13351     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13352         blackNPS = second.nps;
13353     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13354
13355     StartClockTimer(intendedTickLength);
13356 }
13357
13358 char *
13359 TimeString(ms)
13360      long ms;
13361 {
13362     long second, minute, hour, day;
13363     char *sign = "";
13364     static char buf[32];
13365     
13366     if (ms > 0 && ms <= 9900) {
13367       /* convert milliseconds to tenths, rounding up */
13368       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13369
13370       sprintf(buf, " %03.1f ", tenths/10.0);
13371       return buf;
13372     }
13373
13374     /* convert milliseconds to seconds, rounding up */
13375     /* use floating point to avoid strangeness of integer division
13376        with negative dividends on many machines */
13377     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13378
13379     if (second < 0) {
13380         sign = "-";
13381         second = -second;
13382     }
13383     
13384     day = second / (60 * 60 * 24);
13385     second = second % (60 * 60 * 24);
13386     hour = second / (60 * 60);
13387     second = second % (60 * 60);
13388     minute = second / 60;
13389     second = second % 60;
13390     
13391     if (day > 0)
13392       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13393               sign, day, hour, minute, second);
13394     else if (hour > 0)
13395       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13396     else
13397       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13398     
13399     return buf;
13400 }
13401
13402
13403 /*
13404  * This is necessary because some C libraries aren't ANSI C compliant yet.
13405  */
13406 char *
13407 StrStr(string, match)
13408      char *string, *match;
13409 {
13410     int i, length;
13411     
13412     length = strlen(match);
13413     
13414     for (i = strlen(string) - length; i >= 0; i--, string++)
13415       if (!strncmp(match, string, length))
13416         return string;
13417     
13418     return NULL;
13419 }
13420
13421 char *
13422 StrCaseStr(string, match)
13423      char *string, *match;
13424 {
13425     int i, j, length;
13426     
13427     length = strlen(match);
13428     
13429     for (i = strlen(string) - length; i >= 0; i--, string++) {
13430         for (j = 0; j < length; j++) {
13431             if (ToLower(match[j]) != ToLower(string[j]))
13432               break;
13433         }
13434         if (j == length) return string;
13435     }
13436
13437     return NULL;
13438 }
13439
13440 #ifndef _amigados
13441 int
13442 StrCaseCmp(s1, s2)
13443      char *s1, *s2;
13444 {
13445     char c1, c2;
13446     
13447     for (;;) {
13448         c1 = ToLower(*s1++);
13449         c2 = ToLower(*s2++);
13450         if (c1 > c2) return 1;
13451         if (c1 < c2) return -1;
13452         if (c1 == NULLCHAR) return 0;
13453     }
13454 }
13455
13456
13457 int
13458 ToLower(c)
13459      int c;
13460 {
13461     return isupper(c) ? tolower(c) : c;
13462 }
13463
13464
13465 int
13466 ToUpper(c)
13467      int c;
13468 {
13469     return islower(c) ? toupper(c) : c;
13470 }
13471 #endif /* !_amigados    */
13472
13473 char *
13474 StrSave(s)
13475      char *s;
13476 {
13477     char *ret;
13478
13479     if ((ret = (char *) malloc(strlen(s) + 1))) {
13480         strcpy(ret, s);
13481     }
13482     return ret;
13483 }
13484
13485 char *
13486 StrSavePtr(s, savePtr)
13487      char *s, **savePtr;
13488 {
13489     if (*savePtr) {
13490         free(*savePtr);
13491     }
13492     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13493         strcpy(*savePtr, s);
13494     }
13495     return(*savePtr);
13496 }
13497
13498 char *
13499 PGNDate()
13500 {
13501     time_t clock;
13502     struct tm *tm;
13503     char buf[MSG_SIZ];
13504
13505     clock = time((time_t *)NULL);
13506     tm = localtime(&clock);
13507     sprintf(buf, "%04d.%02d.%02d",
13508             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13509     return StrSave(buf);
13510 }
13511
13512
13513 char *
13514 PositionToFEN(move, overrideCastling)
13515      int move;
13516      char *overrideCastling;
13517 {
13518     int i, j, fromX, fromY, toX, toY;
13519     int whiteToPlay;
13520     char buf[128];
13521     char *p, *q;
13522     int emptycount;
13523     ChessSquare piece;
13524
13525     whiteToPlay = (gameMode == EditPosition) ?
13526       !blackPlaysFirst : (move % 2 == 0);
13527     p = buf;
13528
13529     /* Piece placement data */
13530     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13531         emptycount = 0;
13532         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13533             if (boards[move][i][j] == EmptySquare) {
13534                 emptycount++;
13535             } else { ChessSquare piece = boards[move][i][j];
13536                 if (emptycount > 0) {
13537                     if(emptycount<10) /* [HGM] can be >= 10 */
13538                         *p++ = '0' + emptycount;
13539                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13540                     emptycount = 0;
13541                 }
13542                 if(PieceToChar(piece) == '+') {
13543                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13544                     *p++ = '+';
13545                     piece = (ChessSquare)(DEMOTED piece);
13546                 } 
13547                 *p++ = PieceToChar(piece);
13548                 if(p[-1] == '~') {
13549                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13550                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13551                     *p++ = '~';
13552                 }
13553             }
13554         }
13555         if (emptycount > 0) {
13556             if(emptycount<10) /* [HGM] can be >= 10 */
13557                 *p++ = '0' + emptycount;
13558             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13559             emptycount = 0;
13560         }
13561         *p++ = '/';
13562     }
13563     *(p - 1) = ' ';
13564
13565     /* [HGM] print Crazyhouse or Shogi holdings */
13566     if( gameInfo.holdingsWidth ) {
13567         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13568         q = p;
13569         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13570             piece = boards[move][i][BOARD_WIDTH-1];
13571             if( piece != EmptySquare )
13572               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13573                   *p++ = PieceToChar(piece);
13574         }
13575         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13576             piece = boards[move][BOARD_HEIGHT-i-1][0];
13577             if( piece != EmptySquare )
13578               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13579                   *p++ = PieceToChar(piece);
13580         }
13581
13582         if( q == p ) *p++ = '-';
13583         *p++ = ']';
13584         *p++ = ' ';
13585     }
13586
13587     /* Active color */
13588     *p++ = whiteToPlay ? 'w' : 'b';
13589     *p++ = ' ';
13590
13591   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13592     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13593   } else {
13594   if(nrCastlingRights) {
13595      q = p;
13596      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13597        /* [HGM] write directly from rights */
13598            if(castlingRights[move][2] >= 0 &&
13599               castlingRights[move][0] >= 0   )
13600                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13601            if(castlingRights[move][2] >= 0 &&
13602               castlingRights[move][1] >= 0   )
13603                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13604            if(castlingRights[move][5] >= 0 &&
13605               castlingRights[move][3] >= 0   )
13606                 *p++ = castlingRights[move][3] + AAA;
13607            if(castlingRights[move][5] >= 0 &&
13608               castlingRights[move][4] >= 0   )
13609                 *p++ = castlingRights[move][4] + AAA;
13610      } else {
13611
13612         /* [HGM] write true castling rights */
13613         if( nrCastlingRights == 6 ) {
13614             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13615                castlingRights[move][2] >= 0  ) *p++ = 'K';
13616             if(castlingRights[move][1] == BOARD_LEFT &&
13617                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13618             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13619                castlingRights[move][5] >= 0  ) *p++ = 'k';
13620             if(castlingRights[move][4] == BOARD_LEFT &&
13621                castlingRights[move][5] >= 0  ) *p++ = 'q';
13622         }
13623      }
13624      if (q == p) *p++ = '-'; /* No castling rights */
13625      *p++ = ' ';
13626   }
13627
13628   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13629      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13630     /* En passant target square */
13631     if (move > backwardMostMove) {
13632         fromX = moveList[move - 1][0] - AAA;
13633         fromY = moveList[move - 1][1] - ONE;
13634         toX = moveList[move - 1][2] - AAA;
13635         toY = moveList[move - 1][3] - ONE;
13636         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13637             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13638             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13639             fromX == toX) {
13640             /* 2-square pawn move just happened */
13641             *p++ = toX + AAA;
13642             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13643         } else {
13644             *p++ = '-';
13645         }
13646     } else {
13647         *p++ = '-';
13648     }
13649     *p++ = ' ';
13650   }
13651   }
13652
13653     /* [HGM] find reversible plies */
13654     {   int i = 0, j=move;
13655
13656         if (appData.debugMode) { int k;
13657             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13658             for(k=backwardMostMove; k<=forwardMostMove; k++)
13659                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13660
13661         }
13662
13663         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13664         if( j == backwardMostMove ) i += initialRulePlies;
13665         sprintf(p, "%d ", i);
13666         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13667     }
13668     /* Fullmove number */
13669     sprintf(p, "%d", (move / 2) + 1);
13670     
13671     return StrSave(buf);
13672 }
13673
13674 Boolean
13675 ParseFEN(board, blackPlaysFirst, fen)
13676     Board board;
13677      int *blackPlaysFirst;
13678      char *fen;
13679 {
13680     int i, j;
13681     char *p;
13682     int emptycount;
13683     ChessSquare piece;
13684
13685     p = fen;
13686
13687     /* [HGM] by default clear Crazyhouse holdings, if present */
13688     if(gameInfo.holdingsWidth) {
13689        for(i=0; i<BOARD_HEIGHT; i++) {
13690            board[i][0]             = EmptySquare; /* black holdings */
13691            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13692            board[i][1]             = (ChessSquare) 0; /* black counts */
13693            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13694        }
13695     }
13696
13697     /* Piece placement data */
13698     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13699         j = 0;
13700         for (;;) {
13701             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13702                 if (*p == '/') p++;
13703                 emptycount = gameInfo.boardWidth - j;
13704                 while (emptycount--)
13705                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13706                 break;
13707 #if(BOARD_SIZE >= 10)
13708             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13709                 p++; emptycount=10;
13710                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13711                 while (emptycount--)
13712                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13713 #endif
13714             } else if (isdigit(*p)) {
13715                 emptycount = *p++ - '0';
13716                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13717                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13718                 while (emptycount--)
13719                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13720             } else if (*p == '+' || isalpha(*p)) {
13721                 if (j >= gameInfo.boardWidth) return FALSE;
13722                 if(*p=='+') {
13723                     piece = CharToPiece(*++p);
13724                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13725                     piece = (ChessSquare) (PROMOTED piece ); p++;
13726                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13727                 } else piece = CharToPiece(*p++);
13728
13729                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13730                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13731                     piece = (ChessSquare) (PROMOTED piece);
13732                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13733                     p++;
13734                 }
13735                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13736             } else {
13737                 return FALSE;
13738             }
13739         }
13740     }
13741     while (*p == '/' || *p == ' ') p++;
13742
13743     /* [HGM] look for Crazyhouse holdings here */
13744     while(*p==' ') p++;
13745     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13746         if(*p == '[') p++;
13747         if(*p == '-' ) *p++; /* empty holdings */ else {
13748             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13749             /* if we would allow FEN reading to set board size, we would   */
13750             /* have to add holdings and shift the board read so far here   */
13751             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13752                 *p++;
13753                 if((int) piece >= (int) BlackPawn ) {
13754                     i = (int)piece - (int)BlackPawn;
13755                     i = PieceToNumber((ChessSquare)i);
13756                     if( i >= gameInfo.holdingsSize ) return FALSE;
13757                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13758                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13759                 } else {
13760                     i = (int)piece - (int)WhitePawn;
13761                     i = PieceToNumber((ChessSquare)i);
13762                     if( i >= gameInfo.holdingsSize ) return FALSE;
13763                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13764                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13765                 }
13766             }
13767         }
13768         if(*p == ']') *p++;
13769     }
13770
13771     while(*p == ' ') p++;
13772
13773     /* Active color */
13774     switch (*p++) {
13775       case 'w':
13776         *blackPlaysFirst = FALSE;
13777         break;
13778       case 'b': 
13779         *blackPlaysFirst = TRUE;
13780         break;
13781       default:
13782         return FALSE;
13783     }
13784
13785     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13786     /* return the extra info in global variiables             */
13787
13788     /* set defaults in case FEN is incomplete */
13789     FENepStatus = EP_UNKNOWN;
13790     for(i=0; i<nrCastlingRights; i++ ) {
13791         FENcastlingRights[i] =
13792             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13793     }   /* assume possible unless obviously impossible */
13794     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13795     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13796     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13797     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13798     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13799     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13800     FENrulePlies = 0;
13801
13802     while(*p==' ') p++;
13803     if(nrCastlingRights) {
13804       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13805           /* castling indicator present, so default becomes no castlings */
13806           for(i=0; i<nrCastlingRights; i++ ) {
13807                  FENcastlingRights[i] = -1;
13808           }
13809       }
13810       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13811              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13812              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13813              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13814         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13815
13816         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13817             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13818             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13819         }
13820         switch(c) {
13821           case'K':
13822               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13823               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13824               FENcastlingRights[2] = whiteKingFile;
13825               break;
13826           case'Q':
13827               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13828               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13829               FENcastlingRights[2] = whiteKingFile;
13830               break;
13831           case'k':
13832               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13833               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13834               FENcastlingRights[5] = blackKingFile;
13835               break;
13836           case'q':
13837               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13838               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13839               FENcastlingRights[5] = blackKingFile;
13840           case '-':
13841               break;
13842           default: /* FRC castlings */
13843               if(c >= 'a') { /* black rights */
13844                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13845                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13846                   if(i == BOARD_RGHT) break;
13847                   FENcastlingRights[5] = i;
13848                   c -= AAA;
13849                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13850                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13851                   if(c > i)
13852                       FENcastlingRights[3] = c;
13853                   else
13854                       FENcastlingRights[4] = c;
13855               } else { /* white rights */
13856                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13857                     if(board[0][i] == WhiteKing) break;
13858                   if(i == BOARD_RGHT) break;
13859                   FENcastlingRights[2] = i;
13860                   c -= AAA - 'a' + 'A';
13861                   if(board[0][c] >= WhiteKing) break;
13862                   if(c > i)
13863                       FENcastlingRights[0] = c;
13864                   else
13865                       FENcastlingRights[1] = c;
13866               }
13867         }
13868       }
13869     if (appData.debugMode) {
13870         fprintf(debugFP, "FEN castling rights:");
13871         for(i=0; i<nrCastlingRights; i++)
13872         fprintf(debugFP, " %d", FENcastlingRights[i]);
13873         fprintf(debugFP, "\n");
13874     }
13875
13876       while(*p==' ') p++;
13877     }
13878
13879     /* read e.p. field in games that know e.p. capture */
13880     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13881        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13882       if(*p=='-') {
13883         p++; FENepStatus = EP_NONE;
13884       } else {
13885          char c = *p++ - AAA;
13886
13887          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13888          if(*p >= '0' && *p <='9') *p++;
13889          FENepStatus = c;
13890       }
13891     }
13892
13893
13894     if(sscanf(p, "%d", &i) == 1) {
13895         FENrulePlies = i; /* 50-move ply counter */
13896         /* (The move number is still ignored)    */
13897     }
13898
13899     return TRUE;
13900 }
13901       
13902 void
13903 EditPositionPasteFEN(char *fen)
13904 {
13905   if (fen != NULL) {
13906     Board initial_position;
13907
13908     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13909       DisplayError(_("Bad FEN position in clipboard"), 0);
13910       return ;
13911     } else {
13912       int savedBlackPlaysFirst = blackPlaysFirst;
13913       EditPositionEvent();
13914       blackPlaysFirst = savedBlackPlaysFirst;
13915       CopyBoard(boards[0], initial_position);
13916           /* [HGM] copy FEN attributes as well */
13917           {   int i;
13918               initialRulePlies = FENrulePlies;
13919               epStatus[0] = FENepStatus;
13920               for( i=0; i<nrCastlingRights; i++ )
13921                   castlingRights[0][i] = FENcastlingRights[i];
13922           }
13923       EditPositionDone();
13924       DisplayBothClocks();
13925       DrawPosition(FALSE, boards[currentMove]);
13926     }
13927   }
13928 }