fixed bug for double saved games
[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 InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
234
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
240 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245 int lastSavedGame; /* [HGM] save: ID of game */
246
247 /* States for ics_getting_history */
248 #define H_FALSE 0
249 #define H_REQUESTED 1
250 #define H_GOT_REQ_HEADER 2
251 #define H_GOT_UNREQ_HEADER 3
252 #define H_GETTING_MOVES 4
253 #define H_GOT_UNWANTED_HEADER 5
254
255 /* whosays values for GameEnds */
256 #define GE_ICS 0
257 #define GE_ENGINE 1
258 #define GE_PLAYER 2
259 #define GE_FILE 3
260 #define GE_XBOARD 4
261 #define GE_ENGINE1 5
262 #define GE_ENGINE2 6
263
264 /* Maximum number of games in a cmail message */
265 #define CMAIL_MAX_GAMES 20
266
267 /* Different types of move when calling RegisterMove */
268 #define CMAIL_MOVE   0
269 #define CMAIL_RESIGN 1
270 #define CMAIL_DRAW   2
271 #define CMAIL_ACCEPT 3
272
273 /* Different types of result to remember for each game */
274 #define CMAIL_NOT_RESULT 0
275 #define CMAIL_OLD_RESULT 1
276 #define CMAIL_NEW_RESULT 2
277
278 /* Telnet protocol constants */
279 #define TN_WILL 0373
280 #define TN_WONT 0374
281 #define TN_DO   0375
282 #define TN_DONT 0376
283 #define TN_IAC  0377
284 #define TN_ECHO 0001
285 #define TN_SGA  0003
286 #define TN_PORT 23
287
288 /* [AS] */
289 static char * safeStrCpy( char * dst, const char * src, size_t count )
290 {
291     assert( dst != NULL );
292     assert( src != NULL );
293     assert( count > 0 );
294
295     strncpy( dst, src, count );
296     dst[ count-1 ] = '\0';
297     return dst;
298 }
299
300 #if 0
301 //[HGM] for future use? Conditioned out for now to suppress warning.
302 static char * safeStrCat( char * dst, const char * src, size_t count )
303 {
304     size_t  dst_len;
305
306     assert( dst != NULL );
307     assert( src != NULL );
308     assert( count > 0 );
309
310     dst_len = strlen(dst);
311
312     assert( count > dst_len ); /* Buffer size must be greater than current length */
313
314     safeStrCpy( dst + dst_len, src, count - dst_len );
315
316     return dst;
317 }
318 #endif
319
320 /* Some compiler can't cast u64 to double
321  * This function do the job for us:
322
323  * We use the highest bit for cast, this only
324  * works if the highest bit is not
325  * in use (This should not happen)
326  *
327  * We used this for all compiler
328  */
329 double
330 u64ToDouble(u64 value)
331 {
332   double r;
333   u64 tmp = value & u64Const(0x7fffffffffffffff);
334   r = (double)(s64)tmp;
335   if (value & u64Const(0x8000000000000000))
336        r +=  9.2233720368547758080e18; /* 2^63 */
337  return r;
338 }
339
340 /* Fake up flags for now, as we aren't keeping track of castling
341    availability yet. [HGM] Change of logic: the flag now only
342    indicates the type of castlings allowed by the rule of the game.
343    The actual rights themselves are maintained in the array
344    castlingRights, as part of the game history, and are not probed
345    by this function.
346  */
347 int
348 PosFlags(index)
349 {
350   int flags = F_ALL_CASTLE_OK;
351   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
352   switch (gameInfo.variant) {
353   case VariantSuicide:
354     flags &= ~F_ALL_CASTLE_OK;
355   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
356     flags |= F_IGNORE_CHECK;
357   case VariantLosers:
358     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
359     break;
360   case VariantAtomic:
361     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
362     break;
363   case VariantKriegspiel:
364     flags |= F_KRIEGSPIEL_CAPTURE;
365     break;
366   case VariantCapaRandom: 
367   case VariantFischeRandom:
368     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
369   case VariantNoCastle:
370   case VariantShatranj:
371   case VariantCourier:
372     flags &= ~F_ALL_CASTLE_OK;
373     break;
374   default:
375     break;
376   }
377   return flags;
378 }
379
380 FILE *gameFileFP, *debugFP;
381
382 /* 
383     [AS] Note: sometimes, the sscanf() function is used to parse the input
384     into a fixed-size buffer. Because of this, we must be prepared to
385     receive strings as long as the size of the input buffer, which is currently
386     set to 4K for Windows and 8K for the rest.
387     So, we must either allocate sufficiently large buffers here, or
388     reduce the size of the input buffer in the input reading part.
389 */
390
391 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
392 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
393 char thinkOutput1[MSG_SIZ*10];
394
395 ChessProgramState first, second;
396
397 /* premove variables */
398 int premoveToX = 0;
399 int premoveToY = 0;
400 int premoveFromX = 0;
401 int premoveFromY = 0;
402 int premovePromoChar = 0;
403 int gotPremove = 0;
404 Boolean alarmSounded;
405 /* end premove variables */
406
407 char *ics_prefix = "$";
408 int ics_type = ICS_GENERIC;
409
410 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
411 int pauseExamForwardMostMove = 0;
412 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
413 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
414 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
415 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
416 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
417 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
418 int whiteFlag = FALSE, blackFlag = FALSE;
419 int userOfferedDraw = FALSE;
420 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
421 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
422 int cmailMoveType[CMAIL_MAX_GAMES];
423 long ics_clock_paused = 0;
424 ProcRef icsPR = NoProc, cmailPR = NoProc;
425 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
426 GameMode gameMode = BeginningOfGame;
427 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
428 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
429 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
430 int hiddenThinkOutputState = 0; /* [AS] */
431 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
432 int adjudicateLossPlies = 6;
433 char white_holding[64], black_holding[64];
434 TimeMark lastNodeCountTime;
435 long lastNodeCount=0;
436 int have_sent_ICS_logon = 0;
437 int movesPerSession;
438 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
439 long timeControl_2; /* [AS] Allow separate time controls */
440 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
441 long timeRemaining[2][MAX_MOVES];
442 int matchGame = 0;
443 TimeMark programStartTime;
444 char ics_handle[MSG_SIZ];
445 int have_set_title = 0;
446
447 /* animateTraining preserves the state of appData.animate
448  * when Training mode is activated. This allows the
449  * response to be animated when appData.animate == TRUE and
450  * appData.animateDragging == TRUE.
451  */
452 Boolean animateTraining;
453
454 GameInfo gameInfo;
455
456 AppData appData;
457
458 Board boards[MAX_MOVES];
459 /* [HGM] Following 7 needed for accurate legality tests: */
460 char  epStatus[MAX_MOVES];
461 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
462 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
463 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
464 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
465 int   initialRulePlies, FENrulePlies;
466 char  FENepStatus;
467 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
468 int loadFlag = 0; 
469 int shuffleOpenings;
470
471 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475         BlackKing, BlackBishop, BlackKnight, BlackRook }
476 };
477
478 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
479     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482         BlackKing, BlackKing, BlackKnight, BlackRook }
483 };
484
485 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
486     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488     { BlackRook, BlackMan, BlackBishop, BlackQueen,
489         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
490 };
491
492 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
493     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
494         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
496         BlackKing, BlackBishop, BlackKnight, BlackRook }
497 };
498
499 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 };
505
506
507 #if (BOARD_SIZE>=10)
508 ChessSquare ShogiArray[2][BOARD_SIZE] = {
509     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
510         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
511     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
512         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
513 };
514
515 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
516     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
517         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
519         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
520 };
521
522 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
524         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
526         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
527 };
528
529 ChessSquare GreatArray[2][BOARD_SIZE] = {
530     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
531         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
532     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
533         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
534 };
535
536 ChessSquare JanusArray[2][BOARD_SIZE] = {
537     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
538         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
539     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
540         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
541 };
542
543 #ifdef GOTHIC
544 ChessSquare GothicArray[2][BOARD_SIZE] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
546         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
548         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
549 };
550 #else // !GOTHIC
551 #define GothicArray CapablancaArray
552 #endif // !GOTHIC
553
554 #ifdef FALCON
555 ChessSquare FalconArray[2][BOARD_SIZE] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
557         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
559         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
560 };
561 #else // !FALCON
562 #define FalconArray CapablancaArray
563 #endif // !FALCON
564
565 #else // !(BOARD_SIZE>=10)
566 #define XiangqiPosition FIDEArray
567 #define CapablancaArray FIDEArray
568 #define GothicArray FIDEArray
569 #define GreatArray FIDEArray
570 #endif // !(BOARD_SIZE>=10)
571
572 #if (BOARD_SIZE>=12)
573 ChessSquare CourierArray[2][BOARD_SIZE] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
575         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
577         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 };
579 #else // !(BOARD_SIZE>=12)
580 #define CourierArray CapablancaArray
581 #endif // !(BOARD_SIZE>=12)
582
583
584 Board initialPosition;
585
586
587 /* Convert str to a rating. Checks for special cases of "----",
588
589    "++++", etc. Also strips ()'s */
590 int
591 string_to_rating(str)
592   char *str;
593 {
594   while(*str && !isdigit(*str)) ++str;
595   if (!*str)
596     return 0;   /* One of the special "no rating" cases */
597   else
598     return atoi(str);
599 }
600
601 void
602 ClearProgramStats()
603 {
604     /* Init programStats */
605     programStats.movelist[0] = 0;
606     programStats.depth = 0;
607     programStats.nr_moves = 0;
608     programStats.moves_left = 0;
609     programStats.nodes = 0;
610     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
611     programStats.score = 0;
612     programStats.got_only_move = 0;
613     programStats.got_fail = 0;
614     programStats.line_is_book = 0;
615 }
616
617 void
618 InitBackEnd1()
619 {
620     int matched, min, sec;
621
622     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623
624     GetTimeMark(&programStartTime);
625     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
626
627     ClearProgramStats();
628     programStats.ok_to_send = 1;
629     programStats.seen_stat = 0;
630
631     /*
632      * Initialize game list
633      */
634     ListNew(&gameList);
635
636
637     /*
638      * Internet chess server status
639      */
640     if (appData.icsActive) {
641         appData.matchMode = FALSE;
642         appData.matchGames = 0;
643 #if ZIPPY       
644         appData.noChessProgram = !appData.zippyPlay;
645 #else
646         appData.zippyPlay = FALSE;
647         appData.zippyTalk = FALSE;
648         appData.noChessProgram = TRUE;
649 #endif
650         if (*appData.icsHelper != NULLCHAR) {
651             appData.useTelnet = TRUE;
652             appData.telnetProgram = appData.icsHelper;
653         }
654     } else {
655         appData.zippyTalk = appData.zippyPlay = FALSE;
656     }
657
658     /* [AS] Initialize pv info list [HGM] and game state */
659     {
660         int i, j;
661
662         for( i=0; i<MAX_MOVES; i++ ) {
663             pvInfoList[i].depth = -1;
664             epStatus[i]=EP_NONE;
665             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
666         }
667     }
668
669     /*
670      * Parse timeControl resource
671      */
672     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
673                           appData.movesPerSession)) {
674         char buf[MSG_SIZ];
675         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
676         DisplayFatalError(buf, 0, 2);
677     }
678
679     /*
680      * Parse searchTime resource
681      */
682     if (*appData.searchTime != NULLCHAR) {
683         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684         if (matched == 1) {
685             searchTime = min * 60;
686         } else if (matched == 2) {
687             searchTime = min * 60 + sec;
688         } else {
689             char buf[MSG_SIZ];
690             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
691             DisplayFatalError(buf, 0, 2);
692         }
693     }
694
695     /* [AS] Adjudication threshold */
696     adjudicateLossThreshold = appData.adjudicateLossThreshold;
697     
698     first.which = "first";
699     second.which = "second";
700     first.maybeThinking = second.maybeThinking = FALSE;
701     first.pr = second.pr = NoProc;
702     first.isr = second.isr = NULL;
703     first.sendTime = second.sendTime = 2;
704     first.sendDrawOffers = 1;
705     if (appData.firstPlaysBlack) {
706         first.twoMachinesColor = "black\n";
707         second.twoMachinesColor = "white\n";
708     } else {
709         first.twoMachinesColor = "white\n";
710         second.twoMachinesColor = "black\n";
711     }
712     first.program = appData.firstChessProgram;
713     second.program = appData.secondChessProgram;
714     first.host = appData.firstHost;
715     second.host = appData.secondHost;
716     first.dir = appData.firstDirectory;
717     second.dir = appData.secondDirectory;
718     first.other = &second;
719     second.other = &first;
720     first.initString = appData.initString;
721     second.initString = appData.secondInitString;
722     first.computerString = appData.firstComputerString;
723     second.computerString = appData.secondComputerString;
724     first.useSigint = second.useSigint = TRUE;
725     first.useSigterm = second.useSigterm = TRUE;
726     first.reuse = appData.reuseFirst;
727     second.reuse = appData.reuseSecond;
728     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
729     second.nps = appData.secondNPS;
730     first.useSetboard = second.useSetboard = FALSE;
731     first.useSAN = second.useSAN = FALSE;
732     first.usePing = second.usePing = FALSE;
733     first.lastPing = second.lastPing = 0;
734     first.lastPong = second.lastPong = 0;
735     first.usePlayother = second.usePlayother = FALSE;
736     first.useColors = second.useColors = TRUE;
737     first.useUsermove = second.useUsermove = FALSE;
738     first.sendICS = second.sendICS = FALSE;
739     first.sendName = second.sendName = appData.icsActive;
740     first.sdKludge = second.sdKludge = FALSE;
741     first.stKludge = second.stKludge = FALSE;
742     TidyProgramName(first.program, first.host, first.tidy);
743     TidyProgramName(second.program, second.host, second.tidy);
744     first.matchWins = second.matchWins = 0;
745     strcpy(first.variants, appData.variant);
746     strcpy(second.variants, appData.variant);
747     first.analysisSupport = second.analysisSupport = 2; /* detect */
748     first.analyzing = second.analyzing = FALSE;
749     first.initDone = second.initDone = FALSE;
750
751     /* New features added by Tord: */
752     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
753     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
754     /* End of new features added by Tord. */
755     first.fenOverride  = appData.fenOverride1;
756     second.fenOverride = appData.fenOverride2;
757
758     /* [HGM] time odds: set factor for each machine */
759     first.timeOdds  = appData.firstTimeOdds;
760     second.timeOdds = appData.secondTimeOdds;
761     { int norm = 1;
762         if(appData.timeOddsMode) {
763             norm = first.timeOdds;
764             if(norm > second.timeOdds) norm = second.timeOdds;
765         }
766         first.timeOdds /= norm;
767         second.timeOdds /= norm;
768     }
769
770     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
771     first.accumulateTC = appData.firstAccumulateTC;
772     second.accumulateTC = appData.secondAccumulateTC;
773     first.maxNrOfSessions = second.maxNrOfSessions = 1;
774
775     /* [HGM] debug */
776     first.debug = second.debug = FALSE;
777     first.supportsNPS = second.supportsNPS = UNKNOWN;
778
779     /* [HGM] options */
780     first.optionSettings  = appData.firstOptions;
781     second.optionSettings = appData.secondOptions;
782
783     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
784     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
785     first.isUCI = appData.firstIsUCI; /* [AS] */
786     second.isUCI = appData.secondIsUCI; /* [AS] */
787     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
788     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789
790     if (appData.firstProtocolVersion > PROTOVER ||
791         appData.firstProtocolVersion < 1) {
792       char buf[MSG_SIZ];
793       sprintf(buf, _("protocol version %d not supported"),
794               appData.firstProtocolVersion);
795       DisplayFatalError(buf, 0, 2);
796     } else {
797       first.protocolVersion = appData.firstProtocolVersion;
798     }
799
800     if (appData.secondProtocolVersion > PROTOVER ||
801         appData.secondProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.secondProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       second.protocolVersion = appData.secondProtocolVersion;
808     }
809
810     if (appData.icsActive) {
811         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
812     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
813         appData.clockMode = FALSE;
814         first.sendTime = second.sendTime = 0;
815     }
816     
817 #if ZIPPY
818     /* Override some settings from environment variables, for backward
819        compatibility.  Unfortunately it's not feasible to have the env
820        vars just set defaults, at least in xboard.  Ugh.
821     */
822     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
823       ZippyInit();
824     }
825 #endif
826     
827     if (appData.noChessProgram) {
828         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829         sprintf(programVersion, "%s", PACKAGE_STRING);
830     } else {
831 #if 0
832         char *p, *q;
833         q = first.program;
834         while (*q != ' ' && *q != NULLCHAR) q++;
835         p = q;
836         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
837         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
838         sprintf(programVersion, "%s + ", PACKAGE_STRING);
839         strncat(programVersion, p, q - p);
840 #else
841         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
844 #endif
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantBerolina:   /* might work if TestLegality is off */
903       case VariantCapaRandom: /* should work */
904       case VariantJanus:      /* should work */
905       case VariantSuper:      /* experimental */
906       case VariantGreat:      /* experimental, requires legality testing to be off */
907         break;
908       }
909     }
910
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
912     InitEngineUCI( installDir, &second );
913 }
914
915 int NextIntegerFromString( char ** str, long * value )
916 {
917     int result = -1;
918     char * s = *str;
919
920     while( *s == ' ' || *s == '\t' ) {
921         s++;
922     }
923
924     *value = 0;
925
926     if( *s >= '0' && *s <= '9' ) {
927         while( *s >= '0' && *s <= '9' ) {
928             *value = *value * 10 + (*s - '0');
929             s++;
930         }
931
932         result = 0;
933     }
934
935     *str = s;
936
937     return result;
938 }
939
940 int NextTimeControlFromString( char ** str, long * value )
941 {
942     long temp;
943     int result = NextIntegerFromString( str, &temp );
944
945     if( result == 0 ) {
946         *value = temp * 60; /* Minutes */
947         if( **str == ':' ) {
948             (*str)++;
949             result = NextIntegerFromString( str, &temp );
950             *value += temp; /* Seconds */
951         }
952     }
953
954     return result;
955 }
956
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
959     int result = -1; long temp, temp2;
960
961     if(**str != '+') return -1; // old params remain in force!
962     (*str)++;
963     if( NextTimeControlFromString( str, &temp ) ) return -1;
964
965     if(**str != '/') {
966         /* time only: incremental or sudden-death time control */
967         if(**str == '+') { /* increment follows; read it */
968             (*str)++;
969             if(result = NextIntegerFromString( str, &temp2)) return -1;
970             *inc = temp2 * 1000;
971         } else *inc = 0;
972         *moves = 0; *tc = temp * 1000; 
973         return 0;
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
975
976     (*str)++; /* classical time control */
977     result = NextTimeControlFromString( str, &temp2);
978     if(result == 0) {
979         *moves = temp/60;
980         *tc    = temp2 * 1000;
981         *inc   = 0;
982     }
983     return result;
984 }
985
986 int GetTimeQuota(int movenr)
987 {   /* [HGM] get time to add from the multi-session time-control string */
988     int moves=1; /* kludge to force reading of first session */
989     long time, increment;
990     char *s = fullTimeControlString;
991
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993     do {
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996         if(movenr == -1) return time;    /* last move before new session     */
997         if(!moves) return increment;     /* current session is incremental   */
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */
999     } while(movenr >= -1);               /* try again for next session       */
1000
1001     return 0; // no new time quota on this move
1002 }
1003
1004 int
1005 ParseTimeControl(tc, ti, mps)
1006      char *tc;
1007      int ti;
1008      int mps;
1009 {
1010 #if 0
1011     int matched, min, sec;
1012
1013     matched = sscanf(tc, "%d:%d", &min, &sec);
1014     if (matched == 1) {
1015         timeControl = min * 60 * 1000;
1016     } else if (matched == 2) {
1017         timeControl = (min * 60 + sec) * 1000;
1018     } else {
1019         return FALSE;
1020     }
1021 #else
1022     long tc1;
1023     long tc2;
1024     char buf[MSG_SIZ];
1025
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1027     if(ti > 0) {
1028         if(mps)
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030         else sprintf(buf, "+%s+%d", tc, ti);
1031     } else {
1032         if(mps)
1033              sprintf(buf, "+%d/%s", mps, tc);
1034         else sprintf(buf, "+%s", tc);
1035     }
1036     fullTimeControlString = StrSave(buf);
1037
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1039         return FALSE;
1040     }
1041
1042     if( *tc == '/' ) {
1043         /* Parse second time control */
1044         tc++;
1045
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1047             return FALSE;
1048         }
1049
1050         if( tc2 == 0 ) {
1051             return FALSE;
1052         }
1053
1054         timeControl_2 = tc2 * 1000;
1055     }
1056     else {
1057         timeControl_2 = 0;
1058     }
1059
1060     if( tc1 == 0 ) {
1061         return FALSE;
1062     }
1063
1064     timeControl = tc1 * 1000;
1065 #endif
1066
1067     if (ti >= 0) {
1068         timeIncrement = ti * 1000;  /* convert to ms */
1069         movesPerSession = 0;
1070     } else {
1071         timeIncrement = 0;
1072         movesPerSession = mps;
1073     }
1074     return TRUE;
1075 }
1076
1077 void
1078 InitBackEnd2()
1079 {
1080     if (appData.debugMode) {
1081         fprintf(debugFP, "%s\n", programVersion);
1082     }
1083
1084     if (appData.matchGames > 0) {
1085         appData.matchMode = TRUE;
1086     } else if (appData.matchMode) {
1087         appData.matchGames = 1;
1088     }
1089     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090         appData.matchGames = appData.sameColorGames;
1091     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1094     }
1095     Reset(TRUE, FALSE);
1096     if (appData.noChessProgram || first.protocolVersion == 1) {
1097       InitBackEnd3();
1098     } else {
1099       /* kludge: allow timeout for initial "feature" commands */
1100       FreezeUI();
1101       DisplayMessage("", _("Starting chess program"));
1102       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1103     }
1104 }
1105
1106 void
1107 InitBackEnd3 P((void))
1108 {
1109     GameMode initialMode;
1110     char buf[MSG_SIZ];
1111     int err;
1112
1113     InitChessProgram(&first, startedFromSetupPosition);
1114
1115
1116     if (appData.icsActive) {
1117 #ifdef WIN32
1118         /* [DM] Make a console window if needed [HGM] merged ifs */
1119         ConsoleCreate(); 
1120 #endif
1121         err = establish();
1122         if (err != 0) {
1123             if (*appData.icsCommPort != NULLCHAR) {
1124                 sprintf(buf, _("Could not open comm port %s"),  
1125                         appData.icsCommPort);
1126             } else {
1127                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1128                         appData.icsHost, appData.icsPort);
1129             }
1130             DisplayFatalError(buf, err, 1);
1131             return;
1132         }
1133         SetICSMode();
1134         telnetISR =
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136         fromUserISR =
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138     } else if (appData.noChessProgram) {
1139         SetNCPMode();
1140     } else {
1141         SetGNUMode();
1142     }
1143
1144     if (*appData.cmailGameName != NULLCHAR) {
1145         SetCmailMode();
1146         OpenLoopback(&cmailPR);
1147         cmailISR =
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1149     }
1150     
1151     ThawUI();
1152     DisplayMessage("", "");
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {
1154       initialMode = BeginningOfGame;
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156       initialMode = TwoMachinesPlay;
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158       initialMode = AnalyzeFile; 
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160       initialMode = AnalyzeMode;
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162       initialMode = MachinePlaysWhite;
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164       initialMode = MachinePlaysBlack;
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166       initialMode = EditGame;
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168       initialMode = EditPosition;
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170       initialMode = Training;
1171     } else {
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173       DisplayFatalError(buf, 0, 2);
1174       return;
1175     }
1176
1177     if (appData.matchMode) {
1178         /* Set up machine vs. machine match */
1179         if (appData.noChessProgram) {
1180             DisplayFatalError(_("Can't have a match with no chess programs"),
1181                               0, 2);
1182             return;
1183         }
1184         matchMode = TRUE;
1185         matchGame = 1;
1186         if (*appData.loadGameFile != NULLCHAR) {
1187             int index = appData.loadGameIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadGameFromFile(appData.loadGameFile,
1190                                   index,
1191                                   appData.loadGameFile, FALSE)) {
1192                 DisplayFatalError(_("Bad game file"), 0, 1);
1193                 return;
1194             }
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             int index = appData.loadPositionIndex; // [HGM] autoinc
1197             if(index<0) lastIndex = index = 1;
1198             if (!LoadPositionFromFile(appData.loadPositionFile,
1199                                       index,
1200                                       appData.loadPositionFile)) {
1201                 DisplayFatalError(_("Bad position file"), 0, 1);
1202                 return;
1203             }
1204         }
1205         TwoMachinesEvent();
1206     } else if (*appData.cmailGameName != NULLCHAR) {
1207         /* Set up cmail mode */
1208         ReloadCmailMsgEvent(TRUE);
1209     } else {
1210         /* Set up other modes */
1211         if (initialMode == AnalyzeFile) {
1212           if (*appData.loadGameFile == NULLCHAR) {
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1214             return;
1215           }
1216         }
1217         if (*appData.loadGameFile != NULLCHAR) {
1218             (void) LoadGameFromFile(appData.loadGameFile,
1219                                     appData.loadGameIndex,
1220                                     appData.loadGameFile, TRUE);
1221         } else if (*appData.loadPositionFile != NULLCHAR) {
1222             (void) LoadPositionFromFile(appData.loadPositionFile,
1223                                         appData.loadPositionIndex,
1224                                         appData.loadPositionFile);
1225             /* [HGM] try to make self-starting even after FEN load */
1226             /* to allow automatic setup of fairy variants with wtm */
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228                 gameMode = BeginningOfGame;
1229                 setboardSpoiledMachineBlack = 1;
1230             }
1231             /* [HGM] loadPos: make that every new game uses the setup */
1232             /* from file as long as we do not switch variant          */
1233             if(!blackPlaysFirst) { int i;
1234                 startedFromPositionFile = TRUE;
1235                 CopyBoard(filePosition, boards[0]);
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1237             }
1238         }
1239         if (initialMode == AnalyzeMode) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1242             return;
1243           }
1244           if (appData.icsActive) {
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1246             return;
1247           }
1248           AnalyzeModeEvent();
1249         } else if (initialMode == AnalyzeFile) {
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251           ShowThinkingEvent();
1252           AnalyzeFileEvent();
1253           AnalysisPeriodicEvent(1);
1254         } else if (initialMode == MachinePlaysWhite) {
1255           if (appData.noChessProgram) {
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1257                               0, 2);
1258             return;
1259           }
1260           if (appData.icsActive) {
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1262                               0, 2);
1263             return;
1264           }
1265           MachineWhiteEvent();
1266         } else if (initialMode == MachinePlaysBlack) {
1267           if (appData.noChessProgram) {
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1269                               0, 2);
1270             return;
1271           }
1272           if (appData.icsActive) {
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1274                               0, 2);
1275             return;
1276           }
1277           MachineBlackEvent();
1278         } else if (initialMode == TwoMachinesPlay) {
1279           if (appData.noChessProgram) {
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1281                               0, 2);
1282             return;
1283           }
1284           if (appData.icsActive) {
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1286                               0, 2);
1287             return;
1288           }
1289           TwoMachinesEvent();
1290         } else if (initialMode == EditGame) {
1291           EditGameEvent();
1292         } else if (initialMode == EditPosition) {
1293           EditPositionEvent();
1294         } else if (initialMode == Training) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1297             return;
1298           }
1299           TrainingEvent();
1300         }
1301     }
1302 }
1303
1304 /*
1305  * Establish will establish a contact to a remote host.port.
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307  *  used to talk to the host.
1308  * Returns 0 if okay, error code if not.
1309  */
1310 int
1311 establish()
1312 {
1313     char buf[MSG_SIZ];
1314
1315     if (*appData.icsCommPort != NULLCHAR) {
1316         /* Talk to the host through a serial comm port */
1317         return OpenCommPort(appData.icsCommPort, &icsPR);
1318
1319     } else if (*appData.gateway != NULLCHAR) {
1320         if (*appData.remoteShell == NULLCHAR) {
1321             /* Use the rcmd protocol to run telnet program on a gateway host */
1322             snprintf(buf, sizeof(buf), "%s %s %s",
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1325
1326         } else {
1327             /* Use the rsh program to run telnet program on a gateway host */
1328             if (*appData.remoteUser == NULLCHAR) {
1329                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330                         appData.gateway, appData.telnetProgram,
1331                         appData.icsHost, appData.icsPort);
1332             } else {
1333                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334                         appData.remoteShell, appData.gateway, 
1335                         appData.remoteUser, appData.telnetProgram,
1336                         appData.icsHost, appData.icsPort);
1337             }
1338             return StartChildProcess(buf, "", &icsPR);
1339
1340         }
1341     } else if (appData.useTelnet) {
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1343
1344     } else {
1345         /* TCP socket interface differs somewhat between
1346            Unix and NT; handle details in the front end.
1347            */
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1349     }
1350 }
1351
1352 void
1353 show_bytes(fp, buf, count)
1354      FILE *fp;
1355      char *buf;
1356      int count;
1357 {
1358     while (count--) {
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360             fprintf(fp, "\\%03o", *buf & 0xff);
1361         } else {
1362             putc(*buf, fp);
1363         }
1364         buf++;
1365     }
1366     fflush(fp);
1367 }
1368
1369 /* Returns an errno value */
1370 int
1371 OutputMaybeTelnet(pr, message, count, outError)
1372      ProcRef pr;
1373      char *message;
1374      int count;
1375      int *outError;
1376 {
1377     char buf[8192], *p, *q, *buflim;
1378     int left, newcount, outcount;
1379
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381         *appData.gateway != NULLCHAR) {
1382         if (appData.debugMode) {
1383             fprintf(debugFP, ">ICS: ");
1384             show_bytes(debugFP, message, count);
1385             fprintf(debugFP, "\n");
1386         }
1387         return OutputToProcess(pr, message, count, outError);
1388     }
1389
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1391     p = message;
1392     q = buf;
1393     left = count;
1394     newcount = 0;
1395     while (left) {
1396         if (q >= buflim) {
1397             if (appData.debugMode) {
1398                 fprintf(debugFP, ">ICS: ");
1399                 show_bytes(debugFP, buf, newcount);
1400                 fprintf(debugFP, "\n");
1401             }
1402             outcount = OutputToProcess(pr, buf, newcount, outError);
1403             if (outcount < newcount) return -1; /* to be sure */
1404             q = buf;
1405             newcount = 0;
1406         }
1407         if (*p == '\n') {
1408             *q++ = '\r';
1409             newcount++;
1410         } else if (((unsigned char) *p) == TN_IAC) {
1411             *q++ = (char) TN_IAC;
1412             newcount ++;
1413         }
1414         *q++ = *p++;
1415         newcount++;
1416         left--;
1417     }
1418     if (appData.debugMode) {
1419         fprintf(debugFP, ">ICS: ");
1420         show_bytes(debugFP, buf, newcount);
1421         fprintf(debugFP, "\n");
1422     }
1423     outcount = OutputToProcess(pr, buf, newcount, outError);
1424     if (outcount < newcount) return -1; /* to be sure */
1425     return count;
1426 }
1427
1428 void
1429 read_from_player(isr, closure, message, count, error)
1430      InputSourceRef isr;
1431      VOIDSTAR closure;
1432      char *message;
1433      int count;
1434      int error;
1435 {
1436     int outError, outCount;
1437     static int gotEof = 0;
1438
1439     /* Pass data read from player on to ICS */
1440     if (count > 0) {
1441         gotEof = 0;
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443         if (outCount < count) {
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445         }
1446     } else if (count < 0) {
1447         RemoveInputSource(isr);
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449     } else if (gotEof++ > 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1452     }
1453 }
1454
1455 void
1456 SendToICS(s)
1457      char *s;
1458 {
1459     int count, outCount, outError;
1460
1461     if (icsPR == NULL) return;
1462
1463     count = strlen(s);
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465     if (outCount < count) {
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1467     }
1468 }
1469
1470 /* This is used for sending logon scripts to the ICS. Sending
1471    without a delay causes problems when using timestamp on ICC
1472    (at least on my machine). */
1473 void
1474 SendToICSDelayed(s,msdelay)
1475      char *s;
1476      long msdelay;
1477 {
1478     int count, outCount, outError;
1479
1480     if (icsPR == NULL) return;
1481
1482     count = strlen(s);
1483     if (appData.debugMode) {
1484         fprintf(debugFP, ">ICS: ");
1485         show_bytes(debugFP, s, count);
1486         fprintf(debugFP, "\n");
1487     }
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489                                       msdelay);
1490     if (outCount < count) {
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492     }
1493 }
1494
1495
1496 /* Remove all highlighting escape sequences in s
1497    Also deletes any suffix starting with '(' 
1498    */
1499 char *
1500 StripHighlightAndTitle(s)
1501      char *s;
1502 {
1503     static char retbuf[MSG_SIZ];
1504     char *p = retbuf;
1505
1506     while (*s != NULLCHAR) {
1507         while (*s == '\033') {
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;
1509             if (*s != NULLCHAR) s++;
1510         }
1511         while (*s != NULLCHAR && *s != '\033') {
1512             if (*s == '(' || *s == '[') {
1513                 *p = NULLCHAR;
1514                 return retbuf;
1515             }
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 /* Remove all highlighting escape sequences in s */
1524 char *
1525 StripHighlight(s)
1526      char *s;
1527 {
1528     static char retbuf[MSG_SIZ];
1529     char *p = retbuf;
1530
1531     while (*s != NULLCHAR) {
1532         while (*s == '\033') {
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;
1534             if (*s != NULLCHAR) s++;
1535         }
1536         while (*s != NULLCHAR && *s != '\033') {
1537             *p++ = *s++;
1538         }
1539     }
1540     *p = NULLCHAR;
1541     return retbuf;
1542 }
1543
1544 char *variantNames[] = VARIANT_NAMES;
1545 char *
1546 VariantName(v)
1547      VariantClass v;
1548 {
1549     return variantNames[v];
1550 }
1551
1552
1553 /* Identify a variant from the strings the chess servers use or the
1554    PGN Variant tag names we use. */
1555 VariantClass
1556 StringToVariant(e)
1557      char *e;
1558 {
1559     char *p;
1560     int wnum = -1;
1561     VariantClass v = VariantNormal;
1562     int i, found = FALSE;
1563     char buf[MSG_SIZ];
1564
1565     if (!e) return v;
1566
1567     /* [HGM] skip over optional board-size prefixes */
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570         while( *e++ != '_');
1571     }
1572
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574       if (StrCaseStr(e, variantNames[i])) {
1575         v = (VariantClass) i;
1576         found = TRUE;
1577         break;
1578       }
1579     }
1580
1581     if (!found) {
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583           || StrCaseStr(e, "wild/fr") 
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585         v = VariantFischeRandom;
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587                  (i = 1, p = StrCaseStr(e, "w"))) {
1588         p += i;
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1590         if (isdigit(*p)) {
1591           wnum = atoi(p);
1592         } else {
1593           wnum = -1;
1594         }
1595         switch (wnum) {
1596         case 0: /* FICS only, actually */
1597         case 1:
1598           /* Castling legal even if K starts on d-file */
1599           v = VariantWildCastle;
1600           break;
1601         case 2:
1602         case 3:
1603         case 4:
1604           /* Castling illegal even if K & R happen to start in
1605              normal positions. */
1606           v = VariantNoCastle;
1607           break;
1608         case 5:
1609         case 7:
1610         case 8:
1611         case 10:
1612         case 11:
1613         case 12:
1614         case 13:
1615         case 14:
1616         case 15:
1617         case 18:
1618         case 19:
1619           /* Castling legal iff K & R start in normal positions */
1620           v = VariantNormal;
1621           break;
1622         case 6:
1623         case 20:
1624         case 21:
1625           /* Special wilds for position setup; unclear what to do here */
1626           v = VariantLoadable;
1627           break;
1628         case 9:
1629           /* Bizarre ICC game */
1630           v = VariantTwoKings;
1631           break;
1632         case 16:
1633           v = VariantKriegspiel;
1634           break;
1635         case 17:
1636           v = VariantLosers;
1637           break;
1638         case 22:
1639           v = VariantFischeRandom;
1640           break;
1641         case 23:
1642           v = VariantCrazyhouse;
1643           break;
1644         case 24:
1645           v = VariantBughouse;
1646           break;
1647         case 25:
1648           v = Variant3Check;
1649           break;
1650         case 26:
1651           /* Not quite the same as FICS suicide! */
1652           v = VariantGiveaway;
1653           break;
1654         case 27:
1655           v = VariantAtomic;
1656           break;
1657         case 28:
1658           v = VariantShatranj;
1659           break;
1660
1661         /* Temporary names for future ICC types.  The name *will* change in 
1662            the next xboard/WinBoard release after ICC defines it. */
1663         case 29:
1664           v = Variant29;
1665           break;
1666         case 30:
1667           v = Variant30;
1668           break;
1669         case 31:
1670           v = Variant31;
1671           break;
1672         case 32:
1673           v = Variant32;
1674           break;
1675         case 33:
1676           v = Variant33;
1677           break;
1678         case 34:
1679           v = Variant34;
1680           break;
1681         case 35:
1682           v = Variant35;
1683           break;
1684         case 36:
1685           v = Variant36;
1686           break;
1687         case 37:
1688           v = VariantShogi;
1689           break;
1690         case 38:
1691           v = VariantXiangqi;
1692           break;
1693         case 39:
1694           v = VariantCourier;
1695           break;
1696         case 40:
1697           v = VariantGothic;
1698           break;
1699         case 41:
1700           v = VariantCapablanca;
1701           break;
1702         case 42:
1703           v = VariantKnightmate;
1704           break;
1705         case 43:
1706           v = VariantFairy;
1707           break;
1708         case 44:
1709           v = VariantCylinder;
1710           break;
1711         case 45:
1712           v = VariantFalcon;
1713           break;
1714         case 46:
1715           v = VariantCapaRandom;
1716           break;
1717         case 47:
1718           v = VariantBerolina;
1719           break;
1720         case 48:
1721           v = VariantJanus;
1722           break;
1723         case 49:
1724           v = VariantSuper;
1725           break;
1726         case 50:
1727           v = VariantGreat;
1728           break;
1729         case -1:
1730           /* Found "wild" or "w" in the string but no number;
1731              must assume it's normal chess. */
1732           v = VariantNormal;
1733           break;
1734         default:
1735           sprintf(buf, _("Unknown wild type %d"), wnum);
1736           DisplayError(buf, 0);
1737           v = VariantUnknown;
1738           break;
1739         }
1740       }
1741     }
1742     if (appData.debugMode) {
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744               e, wnum, VariantName(v));
1745     }
1746     return v;
1747 }
1748
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1751
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753    advance *index beyond it, and set leftover_start to the new value of
1754    *index; else return FALSE.  If pattern contains the character '*', it
1755    matches any sequence of characters not containing '\r', '\n', or the
1756    character following the '*' (if any), and the matched sequence(s) are
1757    copied into star_match.
1758    */
1759 int
1760 looking_at(buf, index, pattern)
1761      char *buf;
1762      int *index;
1763      char *pattern;
1764 {
1765     char *bufp = &buf[*index], *patternp = pattern;
1766     int star_count = 0;
1767     char *matchp = star_match[0];
1768     
1769     for (;;) {
1770         if (*patternp == NULLCHAR) {
1771             *index = leftover_start = bufp - buf;
1772             *matchp = NULLCHAR;
1773             return TRUE;
1774         }
1775         if (*bufp == NULLCHAR) return FALSE;
1776         if (*patternp == '*') {
1777             if (*bufp == *(patternp + 1)) {
1778                 *matchp = NULLCHAR;
1779                 matchp = star_match[++star_count];
1780                 patternp += 2;
1781                 bufp++;
1782                 continue;
1783             } else if (*bufp == '\n' || *bufp == '\r') {
1784                 patternp++;
1785                 if (*patternp == NULLCHAR)
1786                   continue;
1787                 else
1788                   return FALSE;
1789             } else {
1790                 *matchp++ = *bufp++;
1791                 continue;
1792             }
1793         }
1794         if (*patternp != *bufp) return FALSE;
1795         patternp++;
1796         bufp++;
1797     }
1798 }
1799
1800 void
1801 SendToPlayer(data, length)
1802      char *data;
1803      int length;
1804 {
1805     int error, outCount;
1806     outCount = OutputToProcess(NoProc, data, length, &error);
1807     if (outCount < length) {
1808         DisplayFatalError(_("Error writing to display"), error, 1);
1809     }
1810 }
1811
1812 void
1813 PackHolding(packed, holding)
1814      char packed[];
1815      char *holding;
1816 {
1817     char *p = holding;
1818     char *q = packed;
1819     int runlength = 0;
1820     int curr = 9999;
1821     do {
1822         if (*p == curr) {
1823             runlength++;
1824         } else {
1825             switch (runlength) {
1826               case 0:
1827                 break;
1828               case 1:
1829                 *q++ = curr;
1830                 break;
1831               case 2:
1832                 *q++ = curr;
1833                 *q++ = curr;
1834                 break;
1835               default:
1836                 sprintf(q, "%d", runlength);
1837                 while (*q) q++;
1838                 *q++ = curr;
1839                 break;
1840             }
1841             runlength = 1;
1842             curr = *p;
1843         }
1844     } while (*p++);
1845     *q = NULLCHAR;
1846 }
1847
1848 /* Telnet protocol requests from the front end */
1849 void
1850 TelnetRequest(ddww, option)
1851      unsigned char ddww, option;
1852 {
1853     unsigned char msg[3];
1854     int outCount, outError;
1855
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857
1858     if (appData.debugMode) {
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860         switch (ddww) {
1861           case TN_DO:
1862             ddwwStr = "DO";
1863             break;
1864           case TN_DONT:
1865             ddwwStr = "DONT";
1866             break;
1867           case TN_WILL:
1868             ddwwStr = "WILL";
1869             break;
1870           case TN_WONT:
1871             ddwwStr = "WONT";
1872             break;
1873           default:
1874             ddwwStr = buf1;
1875             sprintf(buf1, "%d", ddww);
1876             break;
1877         }
1878         switch (option) {
1879           case TN_ECHO:
1880             optionStr = "ECHO";
1881             break;
1882           default:
1883             optionStr = buf2;
1884             sprintf(buf2, "%d", option);
1885             break;
1886         }
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888     }
1889     msg[0] = TN_IAC;
1890     msg[1] = ddww;
1891     msg[2] = option;
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893     if (outCount < 3) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 void
1899 DoEcho()
1900 {
1901     if (!appData.icsActive) return;
1902     TelnetRequest(TN_DO, TN_ECHO);
1903 }
1904
1905 void
1906 DontEcho()
1907 {
1908     if (!appData.icsActive) return;
1909     TelnetRequest(TN_DONT, TN_ECHO);
1910 }
1911
1912 void
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 {
1915     /* put the holdings sent to us by the server on the board holdings area */
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917     char p;
1918     ChessSquare piece;
1919
1920     if(gameInfo.holdingsWidth < 2)  return;
1921
1922     if( (int)lowestPiece >= BlackPawn ) {
1923         holdingsColumn = 0;
1924         countsColumn = 1;
1925         holdingsStartRow = BOARD_HEIGHT-1;
1926         direction = -1;
1927     } else {
1928         holdingsColumn = BOARD_WIDTH-1;
1929         countsColumn = BOARD_WIDTH-2;
1930         holdingsStartRow = 0;
1931         direction = 1;
1932     }
1933
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935         board[i][holdingsColumn] = EmptySquare;
1936         board[i][countsColumn]   = (ChessSquare) 0;
1937     }
1938     while( (p=*holdings++) != NULLCHAR ) {
1939         piece = CharToPiece( ToUpper(p) );
1940         if(piece == EmptySquare) continue;
1941         /*j = (int) piece - (int) WhitePawn;*/
1942         j = PieceToNumber(piece);
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944         if(j < 0) continue;               /* should not happen */
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947         board[holdingsStartRow+j*direction][countsColumn]++;
1948     }
1949
1950 }
1951
1952
1953 void
1954 VariantSwitch(Board board, VariantClass newVariant)
1955 {
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959
1960    startedFromPositionFile = FALSE;
1961    if(gameInfo.variant == newVariant) return;
1962
1963    /* [HGM] This routine is called each time an assignment is made to
1964     * gameInfo.variant during a game, to make sure the board sizes
1965     * are set to match the new variant. If that means adding or deleting
1966     * holdings, we shift the playing board accordingly
1967     * This kludge is needed because in ICS observe mode, we get boards
1968     * of an ongoing game without knowing the variant, and learn about the
1969     * latter only later. This can be because of the move list we requested,
1970     * in which case the game history is refilled from the beginning anyway,
1971     * but also when receiving holdings of a crazyhouse game. In the latter
1972     * case we want to add those holdings to the already received position.
1973     */
1974
1975
1976   if (appData.debugMode) {
1977     fprintf(debugFP, "Switch board from %s to %s\n",
1978                VariantName(gameInfo.variant), VariantName(newVariant));
1979     setbuf(debugFP, NULL);
1980   }
1981     shuffleOpenings = 0;       /* [HGM] shuffle */
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983     switch(newVariant) {
1984             case VariantShogi:
1985               newWidth = 9;  newHeight = 9;
1986               gameInfo.holdingsSize = 7;
1987             case VariantBughouse:
1988             case VariantCrazyhouse:
1989               newHoldingsWidth = 2; break;
1990             default:
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;
1992     }
1993
1994     if(newWidth  != gameInfo.boardWidth  ||
1995        newHeight != gameInfo.boardHeight ||
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {
1997
1998         /* shift position to new playing area, if needed */
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000            for(i=0; i<BOARD_HEIGHT; i++) 
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003                                                      board[i][j];
2004            for(i=0; i<newHeight; i++) {
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007            }
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009            for(i=0; i<BOARD_HEIGHT; i++)
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012                                                  board[i][j];
2013         }
2014
2015         gameInfo.boardWidth  = newWidth;
2016         gameInfo.boardHeight = newHeight;
2017         gameInfo.holdingsWidth = newHoldingsWidth;
2018         gameInfo.variant = newVariant;
2019         InitDrawingSizes(-2, 0);
2020
2021         /* [HGM] The following should definitely be solved in a better way */
2022 #if 0
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025         saveEP = epStatus[0];
2026 #endif
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2028 #if 0
2029         epStatus[0] = saveEP;
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2032 #endif
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034
2035     forwardMostMove = oldForwardMostMove;
2036     backwardMostMove = oldBackwardMostMove;
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2038 }
2039
2040 static int loggedOn = FALSE;
2041
2042 /*-- Game start info cache: --*/
2043 int gs_gamenum;
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2050
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2053
2054 void
2055 read_from_ics(isr, closure, data, count, error)
2056      InputSourceRef isr;
2057      VOIDSTAR closure;
2058      char *data;
2059      int count;
2060      int error;
2061 {
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2071     
2072     static int started = STARTED_NONE;
2073     static char parse[20000];
2074     static int parse_pos = 0;
2075     static char buf[BUF_SIZE + 1];
2076     static int firstTime = TRUE, intfSet = FALSE;
2077     static ColorClass prevColor = ColorNormal;
2078     static int savingComment = FALSE;
2079     char str[500];
2080     int i, oldi;
2081     int buf_len;
2082     int next_out;
2083     int tkind;
2084     int backup;    /* [DM] For zippy color lines */
2085     char *p;
2086
2087     if (appData.debugMode) {
2088       if (!error) {
2089         fprintf(debugFP, "<ICS: ");
2090         show_bytes(debugFP, data, count);
2091         fprintf(debugFP, "\n");
2092       }
2093     }
2094
2095     if (appData.debugMode) { int f = forwardMostMove;
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2098     }
2099     if (count > 0) {
2100         /* If last read ended with a partial line that we couldn't parse,
2101            prepend it to the new read and try again. */
2102         if (leftover_len > 0) {
2103             for (i=0; i<leftover_len; i++)
2104               buf[i] = buf[leftover_start + i];
2105         }
2106
2107         /* Copy in new characters, removing nulls and \r's */
2108         buf_len = leftover_len;
2109         for (i = 0; i < count; i++) {
2110             if (data[i] != NULLCHAR && data[i] != '\r')
2111               buf[buf_len++] = data[i];
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') 
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2115                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2116         }
2117
2118         buf[buf_len] = NULLCHAR;
2119         next_out = leftover_len;
2120         leftover_start = 0;
2121         
2122         i = 0;
2123         while (i < buf_len) {
2124             /* Deal with part of the TELNET option negotiation
2125                protocol.  We refuse to do anything beyond the
2126                defaults, except that we allow the WILL ECHO option,
2127                which ICS uses to turn off password echoing when we are
2128                directly connected to it.  We reject this option
2129                if localLineEditing mode is on (always on in xboard)
2130                and we are talking to port 23, which might be a real
2131                telnet server that will try to keep WILL ECHO on permanently.
2132              */
2133             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2134                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2135                 unsigned char option;
2136                 oldi = i;
2137                 switch ((unsigned char) buf[++i]) {
2138                   case TN_WILL:
2139                     if (appData.debugMode)
2140                       fprintf(debugFP, "\n<WILL ");
2141                     switch (option = (unsigned char) buf[++i]) {
2142                       case TN_ECHO:
2143                         if (appData.debugMode)
2144                           fprintf(debugFP, "ECHO ");
2145                         /* Reply only if this is a change, according
2146                            to the protocol rules. */
2147                         if (remoteEchoOption) break;
2148                         if (appData.localLineEditing &&
2149                             atoi(appData.icsPort) == TN_PORT) {
2150                             TelnetRequest(TN_DONT, TN_ECHO);
2151                         } else {
2152                             EchoOff();
2153                             TelnetRequest(TN_DO, TN_ECHO);
2154                             remoteEchoOption = TRUE;
2155                         }
2156                         break;
2157                       default:
2158                         if (appData.debugMode)
2159                           fprintf(debugFP, "%d ", option);
2160                         /* Whatever this is, we don't want it. */
2161                         TelnetRequest(TN_DONT, option);
2162                         break;
2163                     }
2164                     break;
2165                   case TN_WONT:
2166                     if (appData.debugMode)
2167                       fprintf(debugFP, "\n<WONT ");
2168                     switch (option = (unsigned char) buf[++i]) {
2169                       case TN_ECHO:
2170                         if (appData.debugMode)
2171                           fprintf(debugFP, "ECHO ");
2172                         /* Reply only if this is a change, according
2173                            to the protocol rules. */
2174                         if (!remoteEchoOption) break;
2175                         EchoOn();
2176                         TelnetRequest(TN_DONT, TN_ECHO);
2177                         remoteEchoOption = FALSE;
2178                         break;
2179                       default:
2180                         if (appData.debugMode)
2181                           fprintf(debugFP, "%d ", (unsigned char) option);
2182                         /* Whatever this is, it must already be turned
2183                            off, because we never agree to turn on
2184                            anything non-default, so according to the
2185                            protocol rules, we don't reply. */
2186                         break;
2187                     }
2188                     break;
2189                   case TN_DO:
2190                     if (appData.debugMode)
2191                       fprintf(debugFP, "\n<DO ");
2192                     switch (option = (unsigned char) buf[++i]) {
2193                       default:
2194                         /* Whatever this is, we refuse to do it. */
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "%d ", option);
2197                         TelnetRequest(TN_WONT, option);
2198                         break;
2199                     }
2200                     break;
2201                   case TN_DONT:
2202                     if (appData.debugMode)
2203                       fprintf(debugFP, "\n<DONT ");
2204                     switch (option = (unsigned char) buf[++i]) {
2205                       default:
2206                         if (appData.debugMode)
2207                           fprintf(debugFP, "%d ", option);
2208                         /* Whatever this is, we are already not doing
2209                            it, because we never agree to do anything
2210                            non-default, so according to the protocol
2211                            rules, we don't reply. */
2212                         break;
2213                     }
2214                     break;
2215                   case TN_IAC:
2216                     if (appData.debugMode)
2217                       fprintf(debugFP, "\n<IAC ");
2218                     /* Doubled IAC; pass it through */
2219                     i--;
2220                     break;
2221                   default:
2222                     if (appData.debugMode)
2223                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2224                     /* Drop all other telnet commands on the floor */
2225                     break;
2226                 }
2227                 if (oldi > next_out)
2228                   SendToPlayer(&buf[next_out], oldi - next_out);
2229                 if (++i > next_out)
2230                   next_out = i;
2231                 continue;
2232             }
2233                 
2234             /* OK, this at least will *usually* work */
2235             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2236                 loggedOn = TRUE;
2237             }
2238             
2239             if (loggedOn && !intfSet) {
2240                 if (ics_type == ICS_ICC) {
2241                   sprintf(str,
2242                           "/set-quietly interface %s\n/set-quietly style 12\n",
2243                           programVersion);
2244
2245                 } else if (ics_type == ICS_CHESSNET) {
2246                   sprintf(str, "/style 12\n");
2247                 } else {
2248                   strcpy(str, "alias $ @\n$set interface ");
2249                   strcat(str, programVersion);
2250                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2251 #ifdef WIN32
2252                   strcat(str, "$iset nohighlight 1\n");
2253 #endif
2254                   strcat(str, "$iset lock 1\n$style 12\n");
2255                 }
2256                 SendToICS(str);
2257                 intfSet = TRUE;
2258             }
2259
2260             if (started == STARTED_COMMENT) {
2261                 /* Accumulate characters in comment */
2262                 parse[parse_pos++] = buf[i];
2263                 if (buf[i] == '\n') {
2264                     parse[parse_pos] = NULLCHAR;
2265                     if(!suppressKibitz) // [HGM] kibitz
2266                         AppendComment(forwardMostMove, StripHighlight(parse));
2267                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2268                         int nrDigit = 0, nrAlph = 0, i;
2269                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2270                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2271                         parse[parse_pos] = NULLCHAR;
2272                         // try to be smart: if it does not look like search info, it should go to
2273                         // ICS interaction window after all, not to engine-output window.
2274                         for(i=0; i<parse_pos; i++) { // count letters and digits
2275                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2276                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2277                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2278                         }
2279                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2280                             int depth=0; float score;
2281                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2282                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2283                                 pvInfoList[forwardMostMove-1].depth = depth;
2284                                 pvInfoList[forwardMostMove-1].score = 100*score;
2285                             }
2286                             OutputKibitz(suppressKibitz, parse);
2287                         } else {
2288                             char tmp[MSG_SIZ];
2289                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2290                             SendToPlayer(tmp, strlen(tmp));
2291                         }
2292                     }
2293                     started = STARTED_NONE;
2294                 } else {
2295                     /* Don't match patterns against characters in chatter */
2296                     i++;
2297                     continue;
2298                 }
2299             }
2300             if (started == STARTED_CHATTER) {
2301                 if (buf[i] != '\n') {
2302                     /* Don't match patterns against characters in chatter */
2303                     i++;
2304                     continue;
2305                 }
2306                 started = STARTED_NONE;
2307             }
2308
2309             /* Kludge to deal with rcmd protocol */
2310             if (firstTime && looking_at(buf, &i, "\001*")) {
2311                 DisplayFatalError(&buf[1], 0, 1);
2312                 continue;
2313             } else {
2314                 firstTime = FALSE;
2315             }
2316
2317             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2318                 ics_type = ICS_ICC;
2319                 ics_prefix = "/";
2320                 if (appData.debugMode)
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);
2322                 continue;
2323             }
2324             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2325                 ics_type = ICS_FICS;
2326                 ics_prefix = "$";
2327                 if (appData.debugMode)
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);
2329                 continue;
2330             }
2331             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2332                 ics_type = ICS_CHESSNET;
2333                 ics_prefix = "/";
2334                 if (appData.debugMode)
2335                   fprintf(debugFP, "ics_type %d\n", ics_type);
2336                 continue;
2337             }
2338
2339             if (!loggedOn &&
2340                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2341                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2342                  looking_at(buf, &i, "will be \"*\""))) {
2343               strcpy(ics_handle, star_match[0]);
2344               continue;
2345             }
2346
2347             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2348               char buf[MSG_SIZ];
2349               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2350               DisplayIcsInteractionTitle(buf);
2351               have_set_title = TRUE;
2352             }
2353
2354             /* skip finger notes */
2355             if (started == STARTED_NONE &&
2356                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2357                  (buf[i] == '1' && buf[i+1] == '0')) &&
2358                 buf[i+2] == ':' && buf[i+3] == ' ') {
2359               started = STARTED_CHATTER;
2360               i += 3;
2361               continue;
2362             }
2363
2364             /* skip formula vars */
2365             if (started == STARTED_NONE &&
2366                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2367               started = STARTED_CHATTER;
2368               i += 3;
2369               continue;
2370             }
2371
2372             oldi = i;
2373             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2374             if (appData.autoKibitz && started == STARTED_NONE && 
2375                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2376                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2377                 if(looking_at(buf, &i, "* kibitzes: ") &&
2378                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2379                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2380                         suppressKibitz = TRUE;
2381                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2382                                 && (gameMode == IcsPlayingWhite)) ||
2383                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2384                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2385                             started = STARTED_CHATTER; // own kibitz we simply discard
2386                         else {
2387                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2388                             parse_pos = 0; parse[0] = NULLCHAR;
2389                             savingComment = TRUE;
2390                             suppressKibitz = gameMode != IcsObserving ? 2 :
2391                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2392                         } 
2393                         continue;
2394                 } else
2395                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2396                     started = STARTED_CHATTER;
2397                     suppressKibitz = TRUE;
2398                 }
2399             } // [HGM] kibitz: end of patch
2400
2401             if (appData.zippyTalk || appData.zippyPlay) {
2402                 /* [DM] Backup address for color zippy lines */
2403                 backup = i;
2404 #if ZIPPY
2405        #ifdef WIN32
2406                if (loggedOn == TRUE)
2407                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2408                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2409        #else
2410                 if (ZippyControl(buf, &i) ||
2411                     ZippyConverse(buf, &i) ||
2412                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2413                       loggedOn = TRUE;
2414                       if (!appData.colorize) continue;
2415                 }
2416        #endif
2417 #endif
2418             } // [DM] 'else { ' deleted
2419                 if (/* Don't color "message" or "messages" output */
2420                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2421                     looking_at(buf, &i, "*. * at *:*: ") ||
2422                     looking_at(buf, &i, "--* (*:*): ") ||
2423                     /* Regular tells and says */
2424                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2425                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2426                     looking_at(buf, &i, "* says: ") ||
2427                     /* Message notifications (same color as tells) */
2428                     looking_at(buf, &i, "* has left a message ") ||
2429                     looking_at(buf, &i, "* just sent you a message:\n") ||
2430                     /* Whispers and kibitzes */
2431                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2432                     looking_at(buf, &i, "* kibitzes: ") ||
2433                     /* Channel tells */
2434                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2435
2436                   if (tkind == 1 && strchr(star_match[0], ':')) {
2437                       /* Avoid "tells you:" spoofs in channels */
2438                      tkind = 3;
2439                   }
2440                   if (star_match[0][0] == NULLCHAR ||
2441                       strchr(star_match[0], ' ') ||
2442                       (tkind == 3 && strchr(star_match[1], ' '))) {
2443                     /* Reject bogus matches */
2444                     i = oldi;
2445                   } else {
2446                     if (appData.colorize) {
2447                       if (oldi > next_out) {
2448                         SendToPlayer(&buf[next_out], oldi - next_out);
2449                         next_out = oldi;
2450                       }
2451                       switch (tkind) {
2452                       case 1:
2453                         Colorize(ColorTell, FALSE);
2454                         curColor = ColorTell;
2455                         break;
2456                       case 2:
2457                         Colorize(ColorKibitz, FALSE);
2458                         curColor = ColorKibitz;
2459                         break;
2460                       case 3:
2461                         p = strrchr(star_match[1], '(');
2462                         if (p == NULL) {
2463                           p = star_match[1];
2464                         } else {
2465                           p++;
2466                         }
2467                         if (atoi(p) == 1) {
2468                           Colorize(ColorChannel1, FALSE);
2469                           curColor = ColorChannel1;
2470                         } else {
2471                           Colorize(ColorChannel, FALSE);
2472                           curColor = ColorChannel;
2473                         }
2474                         break;
2475                       case 5:
2476                         curColor = ColorNormal;
2477                         break;
2478                       }
2479                     }
2480                     if (started == STARTED_NONE && appData.autoComment &&
2481                         (gameMode == IcsObserving ||
2482                          gameMode == IcsPlayingWhite ||
2483                          gameMode == IcsPlayingBlack)) {
2484                       parse_pos = i - oldi;
2485                       memcpy(parse, &buf[oldi], parse_pos);
2486                       parse[parse_pos] = NULLCHAR;
2487                       started = STARTED_COMMENT;
2488                       savingComment = TRUE;
2489                     } else {
2490                       started = STARTED_CHATTER;
2491                       savingComment = FALSE;
2492                     }
2493                     loggedOn = TRUE;
2494                     continue;
2495                   }
2496                 }
2497
2498                 if (looking_at(buf, &i, "* s-shouts: ") ||
2499                     looking_at(buf, &i, "* c-shouts: ")) {
2500                     if (appData.colorize) {
2501                         if (oldi > next_out) {
2502                             SendToPlayer(&buf[next_out], oldi - next_out);
2503                             next_out = oldi;
2504                         }
2505                         Colorize(ColorSShout, FALSE);
2506                         curColor = ColorSShout;
2507                     }
2508                     loggedOn = TRUE;
2509                     started = STARTED_CHATTER;
2510                     continue;
2511                 }
2512
2513                 if (looking_at(buf, &i, "--->")) {
2514                     loggedOn = TRUE;
2515                     continue;
2516                 }
2517
2518                 if (looking_at(buf, &i, "* shouts: ") ||
2519                     looking_at(buf, &i, "--> ")) {
2520                     if (appData.colorize) {
2521                         if (oldi > next_out) {
2522                             SendToPlayer(&buf[next_out], oldi - next_out);
2523                             next_out = oldi;
2524                         }
2525                         Colorize(ColorShout, FALSE);
2526                         curColor = ColorShout;
2527                     }
2528                     loggedOn = TRUE;
2529                     started = STARTED_CHATTER;
2530                     continue;
2531                 }
2532
2533                 if (looking_at( buf, &i, "Challenge:")) {
2534                     if (appData.colorize) {
2535                         if (oldi > next_out) {
2536                             SendToPlayer(&buf[next_out], oldi - next_out);
2537                             next_out = oldi;
2538                         }
2539                         Colorize(ColorChallenge, FALSE);
2540                         curColor = ColorChallenge;
2541                     }
2542                     loggedOn = TRUE;
2543                     continue;
2544                 }
2545
2546                 if (looking_at(buf, &i, "* offers you") ||
2547                     looking_at(buf, &i, "* offers to be") ||
2548                     looking_at(buf, &i, "* would like to") ||
2549                     looking_at(buf, &i, "* requests to") ||
2550                     looking_at(buf, &i, "Your opponent offers") ||
2551                     looking_at(buf, &i, "Your opponent requests")) {
2552
2553                     if (appData.colorize) {
2554                         if (oldi > next_out) {
2555                             SendToPlayer(&buf[next_out], oldi - next_out);
2556                             next_out = oldi;
2557                         }
2558                         Colorize(ColorRequest, FALSE);
2559                         curColor = ColorRequest;
2560                     }
2561                     continue;
2562                 }
2563
2564                 if (looking_at(buf, &i, "* (*) seeking")) {
2565                     if (appData.colorize) {
2566                         if (oldi > next_out) {
2567                             SendToPlayer(&buf[next_out], oldi - next_out);
2568                             next_out = oldi;
2569                         }
2570                         Colorize(ColorSeek, FALSE);
2571                         curColor = ColorSeek;
2572                     }
2573                     continue;
2574             }
2575
2576             if (looking_at(buf, &i, "\\   ")) {
2577                 if (prevColor != ColorNormal) {
2578                     if (oldi > next_out) {
2579                         SendToPlayer(&buf[next_out], oldi - next_out);
2580                         next_out = oldi;
2581                     }
2582                     Colorize(prevColor, TRUE);
2583                     curColor = prevColor;
2584                 }
2585                 if (savingComment) {
2586                     parse_pos = i - oldi;
2587                     memcpy(parse, &buf[oldi], parse_pos);
2588                     parse[parse_pos] = NULLCHAR;
2589                     started = STARTED_COMMENT;
2590                 } else {
2591                     started = STARTED_CHATTER;
2592                 }
2593                 continue;
2594             }
2595
2596             if (looking_at(buf, &i, "Black Strength :") ||
2597                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2598                 looking_at(buf, &i, "<10>") ||
2599                 looking_at(buf, &i, "#@#")) {
2600                 /* Wrong board style */
2601                 loggedOn = TRUE;
2602                 SendToICS(ics_prefix);
2603                 SendToICS("set style 12\n");
2604                 SendToICS(ics_prefix);
2605                 SendToICS("refresh\n");
2606                 continue;
2607             }
2608             
2609             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2610                 ICSInitScript();
2611                 have_sent_ICS_logon = 1;
2612                 continue;
2613             }
2614               
2615             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2616                 (looking_at(buf, &i, "\n<12> ") ||
2617                  looking_at(buf, &i, "<12> "))) {
2618                 loggedOn = TRUE;
2619                 if (oldi > next_out) {
2620                     SendToPlayer(&buf[next_out], oldi - next_out);
2621                 }
2622                 next_out = i;
2623                 started = STARTED_BOARD;
2624                 parse_pos = 0;
2625                 continue;
2626             }
2627
2628             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2629                 looking_at(buf, &i, "<b1> ")) {
2630                 if (oldi > next_out) {
2631                     SendToPlayer(&buf[next_out], oldi - next_out);
2632                 }
2633                 next_out = i;
2634                 started = STARTED_HOLDINGS;
2635                 parse_pos = 0;
2636                 continue;
2637             }
2638
2639             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2640                 loggedOn = TRUE;
2641                 /* Header for a move list -- first line */
2642
2643                 switch (ics_getting_history) {
2644                   case H_FALSE:
2645                     switch (gameMode) {
2646                       case IcsIdle:
2647                       case BeginningOfGame:
2648                         /* User typed "moves" or "oldmoves" while we
2649                            were idle.  Pretend we asked for these
2650                            moves and soak them up so user can step
2651                            through them and/or save them.
2652                            */
2653                         Reset(FALSE, TRUE);
2654                         gameMode = IcsObserving;
2655                         ModeHighlight();
2656                         ics_gamenum = -1;
2657                         ics_getting_history = H_GOT_UNREQ_HEADER;
2658                         break;
2659                       case EditGame: /*?*/
2660                       case EditPosition: /*?*/
2661                         /* Should above feature work in these modes too? */
2662                         /* For now it doesn't */
2663                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2664                         break;
2665                       default:
2666                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2667                         break;
2668                     }
2669                     break;
2670                   case H_REQUESTED:
2671                     /* Is this the right one? */
2672                     if (gameInfo.white && gameInfo.black &&
2673                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2674                         strcmp(gameInfo.black, star_match[2]) == 0) {
2675                         /* All is well */
2676                         ics_getting_history = H_GOT_REQ_HEADER;
2677                     }
2678                     break;
2679                   case H_GOT_REQ_HEADER:
2680                   case H_GOT_UNREQ_HEADER:
2681                   case H_GOT_UNWANTED_HEADER:
2682                   case H_GETTING_MOVES:
2683                     /* Should not happen */
2684                     DisplayError(_("Error gathering move list: two headers"), 0);
2685                     ics_getting_history = H_FALSE;
2686                     break;
2687                 }
2688
2689                 /* Save player ratings into gameInfo if needed */
2690                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2691                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2692                     (gameInfo.whiteRating == -1 ||
2693                      gameInfo.blackRating == -1)) {
2694
2695                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2696                     gameInfo.blackRating = string_to_rating(star_match[3]);
2697                     if (appData.debugMode)
2698                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2699                               gameInfo.whiteRating, gameInfo.blackRating);
2700                 }
2701                 continue;
2702             }
2703
2704             if (looking_at(buf, &i,
2705               "* * match, initial time: * minute*, increment: * second")) {
2706                 /* Header for a move list -- second line */
2707                 /* Initial board will follow if this is a wild game */
2708                 if (gameInfo.event != NULL) free(gameInfo.event);
2709                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2710                 gameInfo.event = StrSave(str);
2711                 /* [HGM] we switched variant. Translate boards if needed. */
2712                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2713                 continue;
2714             }
2715
2716             if (looking_at(buf, &i, "Move  ")) {
2717                 /* Beginning of a move list */
2718                 switch (ics_getting_history) {
2719                   case H_FALSE:
2720                     /* Normally should not happen */
2721                     /* Maybe user hit reset while we were parsing */
2722                     break;
2723                   case H_REQUESTED:
2724                     /* Happens if we are ignoring a move list that is not
2725                      * the one we just requested.  Common if the user
2726                      * tries to observe two games without turning off
2727                      * getMoveList */
2728                     break;
2729                   case H_GETTING_MOVES:
2730                     /* Should not happen */
2731                     DisplayError(_("Error gathering move list: nested"), 0);
2732                     ics_getting_history = H_FALSE;
2733                     break;
2734                   case H_GOT_REQ_HEADER:
2735                     ics_getting_history = H_GETTING_MOVES;
2736                     started = STARTED_MOVES;
2737                     parse_pos = 0;
2738                     if (oldi > next_out) {
2739                         SendToPlayer(&buf[next_out], oldi - next_out);
2740                     }
2741                     break;
2742                   case H_GOT_UNREQ_HEADER:
2743                     ics_getting_history = H_GETTING_MOVES;
2744                     started = STARTED_MOVES_NOHIDE;
2745                     parse_pos = 0;
2746                     break;
2747                   case H_GOT_UNWANTED_HEADER:
2748                     ics_getting_history = H_FALSE;
2749                     break;
2750                 }
2751                 continue;
2752             }                           
2753             
2754             if (looking_at(buf, &i, "% ") ||
2755                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2756                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2757                 savingComment = FALSE;
2758                 switch (started) {
2759                   case STARTED_MOVES:
2760                   case STARTED_MOVES_NOHIDE:
2761                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2762                     parse[parse_pos + i - oldi] = NULLCHAR;
2763                     ParseGameHistory(parse);
2764 #if ZIPPY
2765                     if (appData.zippyPlay && first.initDone) {
2766                         FeedMovesToProgram(&first, forwardMostMove);
2767                         if (gameMode == IcsPlayingWhite) {
2768                             if (WhiteOnMove(forwardMostMove)) {
2769                                 if (first.sendTime) {
2770                                   if (first.useColors) {
2771                                     SendToProgram("black\n", &first); 
2772                                   }
2773                                   SendTimeRemaining(&first, TRUE);
2774                                 }
2775 #if 0
2776                                 if (first.useColors) {
2777                                   SendToProgram("white\ngo\n", &first);
2778                                 } else {
2779                                   SendToProgram("go\n", &first);
2780                                 }
2781 #else
2782                                 if (first.useColors) {
2783                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2784                                 }
2785                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2786 #endif
2787                                 first.maybeThinking = TRUE;
2788                             } else {
2789                                 if (first.usePlayother) {
2790                                   if (first.sendTime) {
2791                                     SendTimeRemaining(&first, TRUE);
2792                                   }
2793                                   SendToProgram("playother\n", &first);
2794                                   firstMove = FALSE;
2795                                 } else {
2796                                   firstMove = TRUE;
2797                                 }
2798                             }
2799                         } else if (gameMode == IcsPlayingBlack) {
2800                             if (!WhiteOnMove(forwardMostMove)) {
2801                                 if (first.sendTime) {
2802                                   if (first.useColors) {
2803                                     SendToProgram("white\n", &first);
2804                                   }
2805                                   SendTimeRemaining(&first, FALSE);
2806                                 }
2807 #if 0
2808                                 if (first.useColors) {
2809                                   SendToProgram("black\ngo\n", &first);
2810                                 } else {
2811                                   SendToProgram("go\n", &first);
2812                                 }
2813 #else
2814                                 if (first.useColors) {
2815                                   SendToProgram("black\n", &first);
2816                                 }
2817                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2818 #endif
2819                                 first.maybeThinking = TRUE;
2820                             } else {
2821                                 if (first.usePlayother) {
2822                                   if (first.sendTime) {
2823                                     SendTimeRemaining(&first, FALSE);
2824                                   }
2825                                   SendToProgram("playother\n", &first);
2826                                   firstMove = FALSE;
2827                                 } else {
2828                                   firstMove = TRUE;
2829                                 }
2830                             }
2831                         }                       
2832                     }
2833 #endif
2834                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2835                         /* Moves came from oldmoves or moves command
2836                            while we weren't doing anything else.
2837                            */
2838                         currentMove = forwardMostMove;
2839                         ClearHighlights();/*!!could figure this out*/
2840                         flipView = appData.flipView;
2841                         DrawPosition(FALSE, boards[currentMove]);
2842                         DisplayBothClocks();
2843                         sprintf(str, "%s vs. %s",
2844                                 gameInfo.white, gameInfo.black);
2845                         DisplayTitle(str);
2846                         gameMode = IcsIdle;
2847                     } else {
2848                         /* Moves were history of an active game */
2849                         if (gameInfo.resultDetails != NULL) {
2850                             free(gameInfo.resultDetails);
2851                             gameInfo.resultDetails = NULL;
2852                         }
2853                     }
2854                     HistorySet(parseList, backwardMostMove,
2855                                forwardMostMove, currentMove-1);
2856                     DisplayMove(currentMove - 1);
2857                     if (started == STARTED_MOVES) next_out = i;
2858                     started = STARTED_NONE;
2859                     ics_getting_history = H_FALSE;
2860                     break;
2861
2862                   case STARTED_OBSERVE:
2863                     started = STARTED_NONE;
2864                     SendToICS(ics_prefix);
2865                     SendToICS("refresh\n");
2866                     break;
2867
2868                   default:
2869                     break;
2870                 }
2871                 if(bookHit) { // [HGM] book: simulate book reply
2872                     static char bookMove[MSG_SIZ]; // a bit generous?
2873
2874                     programStats.nodes = programStats.depth = programStats.time = 
2875                     programStats.score = programStats.got_only_move = 0;
2876                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2877
2878                     strcpy(bookMove, "move ");
2879                     strcat(bookMove, bookHit);
2880                     HandleMachineMove(bookMove, &first);
2881                 }
2882                 continue;
2883             }
2884             
2885             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2886                  started == STARTED_HOLDINGS ||
2887                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2888                 /* Accumulate characters in move list or board */
2889                 parse[parse_pos++] = buf[i];
2890             }
2891             
2892             /* Start of game messages.  Mostly we detect start of game
2893                when the first board image arrives.  On some versions
2894                of the ICS, though, we need to do a "refresh" after starting
2895                to observe in order to get the current board right away. */
2896             if (looking_at(buf, &i, "Adding game * to observation list")) {
2897                 started = STARTED_OBSERVE;
2898                 continue;
2899             }
2900
2901             /* Handle auto-observe */
2902             if (appData.autoObserve &&
2903                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2904                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2905                 char *player;
2906                 /* Choose the player that was highlighted, if any. */
2907                 if (star_match[0][0] == '\033' ||
2908                     star_match[1][0] != '\033') {
2909                     player = star_match[0];
2910                 } else {
2911                     player = star_match[2];
2912                 }
2913                 sprintf(str, "%sobserve %s\n",
2914                         ics_prefix, StripHighlightAndTitle(player));
2915                 SendToICS(str);
2916
2917                 /* Save ratings from notify string */
2918                 strcpy(player1Name, star_match[0]);
2919                 player1Rating = string_to_rating(star_match[1]);
2920                 strcpy(player2Name, star_match[2]);
2921                 player2Rating = string_to_rating(star_match[3]);
2922
2923                 if (appData.debugMode)
2924                   fprintf(debugFP, 
2925                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2926                           player1Name, player1Rating,
2927                           player2Name, player2Rating);
2928
2929                 continue;
2930             }
2931
2932             /* Deal with automatic examine mode after a game,
2933                and with IcsObserving -> IcsExamining transition */
2934             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2935                 looking_at(buf, &i, "has made you an examiner of game *")) {
2936
2937                 int gamenum = atoi(star_match[0]);
2938                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2939                     gamenum == ics_gamenum) {
2940                     /* We were already playing or observing this game;
2941                        no need to refetch history */
2942                     gameMode = IcsExamining;
2943                     if (pausing) {
2944                         pauseExamForwardMostMove = forwardMostMove;
2945                     } else if (currentMove < forwardMostMove) {
2946                         ForwardInner(forwardMostMove);
2947                     }
2948                 } else {
2949                     /* I don't think this case really can happen */
2950                     SendToICS(ics_prefix);
2951                     SendToICS("refresh\n");
2952                 }
2953                 continue;
2954             }    
2955             
2956             /* Error messages */
2957 //          if (ics_user_moved) {
2958             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2959                 if (looking_at(buf, &i, "Illegal move") ||
2960                     looking_at(buf, &i, "Not a legal move") ||
2961                     looking_at(buf, &i, "Your king is in check") ||
2962                     looking_at(buf, &i, "It isn't your turn") ||
2963                     looking_at(buf, &i, "It is not your move")) {
2964                     /* Illegal move */
2965                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2966                         currentMove = --forwardMostMove;
2967                         DisplayMove(currentMove - 1); /* before DMError */
2968                         DrawPosition(FALSE, boards[currentMove]);
2969                         SwitchClocks();
2970                         DisplayBothClocks();
2971                     }
2972                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2973                     ics_user_moved = 0;
2974                     continue;
2975                 }
2976             }
2977
2978             if (looking_at(buf, &i, "still have time") ||
2979                 looking_at(buf, &i, "not out of time") ||
2980                 looking_at(buf, &i, "either player is out of time") ||
2981                 looking_at(buf, &i, "has timeseal; checking")) {
2982                 /* We must have called his flag a little too soon */
2983                 whiteFlag = blackFlag = FALSE;
2984                 continue;
2985             }
2986
2987             if (looking_at(buf, &i, "added * seconds to") ||
2988                 looking_at(buf, &i, "seconds were added to")) {
2989                 /* Update the clocks */
2990                 SendToICS(ics_prefix);
2991                 SendToICS("refresh\n");
2992                 continue;
2993             }
2994
2995             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2996                 ics_clock_paused = TRUE;
2997                 StopClocks();
2998                 continue;
2999             }
3000
3001             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3002                 ics_clock_paused = FALSE;
3003                 StartClocks();
3004                 continue;
3005             }
3006
3007             /* Grab player ratings from the Creating: message.
3008                Note we have to check for the special case when
3009                the ICS inserts things like [white] or [black]. */
3010             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3011                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3012                 /* star_matches:
3013                    0    player 1 name (not necessarily white)
3014                    1    player 1 rating
3015                    2    empty, white, or black (IGNORED)
3016                    3    player 2 name (not necessarily black)
3017                    4    player 2 rating
3018                    
3019                    The names/ratings are sorted out when the game
3020                    actually starts (below).
3021                 */
3022                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3023                 player1Rating = string_to_rating(star_match[1]);
3024                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3025                 player2Rating = string_to_rating(star_match[4]);
3026
3027                 if (appData.debugMode)
3028                   fprintf(debugFP, 
3029                           "Ratings from 'Creating:' %s %d, %s %d\n",
3030                           player1Name, player1Rating,
3031                           player2Name, player2Rating);
3032
3033                 continue;
3034             }
3035             
3036             /* Improved generic start/end-of-game messages */
3037             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3038                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3039                 /* If tkind == 0: */
3040                 /* star_match[0] is the game number */
3041                 /*           [1] is the white player's name */
3042                 /*           [2] is the black player's name */
3043                 /* For end-of-game: */
3044                 /*           [3] is the reason for the game end */
3045                 /*           [4] is a PGN end game-token, preceded by " " */
3046                 /* For start-of-game: */
3047                 /*           [3] begins with "Creating" or "Continuing" */
3048                 /*           [4] is " *" or empty (don't care). */
3049                 int gamenum = atoi(star_match[0]);
3050                 char *whitename, *blackname, *why, *endtoken;
3051                 ChessMove endtype = (ChessMove) 0;
3052
3053                 if (tkind == 0) {
3054                   whitename = star_match[1];
3055                   blackname = star_match[2];
3056                   why = star_match[3];
3057                   endtoken = star_match[4];
3058                 } else {
3059                   whitename = star_match[1];
3060                   blackname = star_match[3];
3061                   why = star_match[5];
3062                   endtoken = star_match[6];
3063                 }
3064
3065                 /* Game start messages */
3066                 if (strncmp(why, "Creating ", 9) == 0 ||
3067                     strncmp(why, "Continuing ", 11) == 0) {
3068                     gs_gamenum = gamenum;
3069                     strcpy(gs_kind, strchr(why, ' ') + 1);
3070 #if ZIPPY
3071                     if (appData.zippyPlay) {
3072                         ZippyGameStart(whitename, blackname);
3073                     }
3074 #endif /*ZIPPY*/
3075                     continue;
3076                 }
3077
3078                 /* Game end messages */
3079                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3080                     ics_gamenum != gamenum) {
3081                     continue;
3082                 }
3083                 while (endtoken[0] == ' ') endtoken++;
3084                 switch (endtoken[0]) {
3085                   case '*':
3086                   default:
3087                     endtype = GameUnfinished;
3088                     break;
3089                   case '0':
3090                     endtype = BlackWins;
3091                     break;
3092                   case '1':
3093                     if (endtoken[1] == '/')
3094                       endtype = GameIsDrawn;
3095                     else
3096                       endtype = WhiteWins;
3097                     break;
3098                 }
3099                 GameEnds(endtype, why, GE_ICS);
3100 #if ZIPPY
3101                 if (appData.zippyPlay && first.initDone) {
3102                     ZippyGameEnd(endtype, why);
3103                     if (first.pr == NULL) {
3104                       /* Start the next process early so that we'll
3105                          be ready for the next challenge */
3106                       StartChessProgram(&first);
3107                     }
3108                     /* Send "new" early, in case this command takes
3109                        a long time to finish, so that we'll be ready
3110                        for the next challenge. */
3111                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3112                     Reset(TRUE, TRUE);
3113                 }
3114 #endif /*ZIPPY*/
3115                 continue;
3116             }
3117
3118             if (looking_at(buf, &i, "Removing game * from observation") ||
3119                 looking_at(buf, &i, "no longer observing game *") ||
3120                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3121                 if (gameMode == IcsObserving &&
3122                     atoi(star_match[0]) == ics_gamenum)
3123                   {
3124                       /* icsEngineAnalyze */
3125                       if (appData.icsEngineAnalyze) {
3126                             ExitAnalyzeMode();
3127                             ModeHighlight();
3128                       }
3129                       StopClocks();
3130                       gameMode = IcsIdle;
3131                       ics_gamenum = -1;
3132                       ics_user_moved = FALSE;
3133                   }
3134                 continue;
3135             }
3136
3137             if (looking_at(buf, &i, "no longer examining game *")) {
3138                 if (gameMode == IcsExamining &&
3139                     atoi(star_match[0]) == ics_gamenum)
3140                   {
3141                       gameMode = IcsIdle;
3142                       ics_gamenum = -1;
3143                       ics_user_moved = FALSE;
3144                   }
3145                 continue;
3146             }
3147
3148             /* Advance leftover_start past any newlines we find,
3149                so only partial lines can get reparsed */
3150             if (looking_at(buf, &i, "\n")) {
3151                 prevColor = curColor;
3152                 if (curColor != ColorNormal) {
3153                     if (oldi > next_out) {
3154                         SendToPlayer(&buf[next_out], oldi - next_out);
3155                         next_out = oldi;
3156                     }
3157                     Colorize(ColorNormal, FALSE);
3158                     curColor = ColorNormal;
3159                 }
3160                 if (started == STARTED_BOARD) {
3161                     started = STARTED_NONE;
3162                     parse[parse_pos] = NULLCHAR;
3163                     ParseBoard12(parse);
3164                     ics_user_moved = 0;
3165
3166                     /* Send premove here */
3167                     if (appData.premove) {
3168                       char str[MSG_SIZ];
3169                       if (currentMove == 0 &&
3170                           gameMode == IcsPlayingWhite &&
3171                           appData.premoveWhite) {
3172                         sprintf(str, "%s%s\n", ics_prefix,
3173                                 appData.premoveWhiteText);
3174                         if (appData.debugMode)
3175                           fprintf(debugFP, "Sending premove:\n");
3176                         SendToICS(str);
3177                       } else if (currentMove == 1 &&
3178                                  gameMode == IcsPlayingBlack &&
3179                                  appData.premoveBlack) {
3180                         sprintf(str, "%s%s\n", ics_prefix,
3181                                 appData.premoveBlackText);
3182                         if (appData.debugMode)
3183                           fprintf(debugFP, "Sending premove:\n");
3184                         SendToICS(str);
3185                       } else if (gotPremove) {
3186                         gotPremove = 0;
3187                         ClearPremoveHighlights();
3188                         if (appData.debugMode)
3189                           fprintf(debugFP, "Sending premove:\n");
3190                           UserMoveEvent(premoveFromX, premoveFromY, 
3191                                         premoveToX, premoveToY, 
3192                                         premovePromoChar);
3193                       }
3194                     }
3195
3196                     /* Usually suppress following prompt */
3197                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3198                         if (looking_at(buf, &i, "*% ")) {
3199                             savingComment = FALSE;
3200                         }
3201                     }
3202                     next_out = i;
3203                 } else if (started == STARTED_HOLDINGS) {
3204                     int gamenum;
3205                     char new_piece[MSG_SIZ];
3206                     started = STARTED_NONE;
3207                     parse[parse_pos] = NULLCHAR;
3208                     if (appData.debugMode)
3209                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3210                                                         parse, currentMove);
3211                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3212                         gamenum == ics_gamenum) {
3213                         if (gameInfo.variant == VariantNormal) {
3214                           /* [HGM] We seem to switch variant during a game!
3215                            * Presumably no holdings were displayed, so we have
3216                            * to move the position two files to the right to
3217                            * create room for them!
3218                            */
3219                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3220                           /* Get a move list just to see the header, which
3221                              will tell us whether this is really bug or zh */
3222                           if (ics_getting_history == H_FALSE) {
3223                             ics_getting_history = H_REQUESTED;
3224                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3225                             SendToICS(str);
3226                           }
3227                         }
3228                         new_piece[0] = NULLCHAR;
3229                         sscanf(parse, "game %d white [%s black [%s <- %s",
3230                                &gamenum, white_holding, black_holding,
3231                                new_piece);
3232                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3233                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3234                         /* [HGM] copy holdings to board holdings area */
3235                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3236                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3237 #if ZIPPY
3238                         if (appData.zippyPlay && first.initDone) {
3239                             ZippyHoldings(white_holding, black_holding,
3240                                           new_piece);
3241                         }
3242 #endif /*ZIPPY*/
3243                         if (tinyLayout || smallLayout) {
3244                             char wh[16], bh[16];
3245                             PackHolding(wh, white_holding);
3246                             PackHolding(bh, black_holding);
3247                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3248                                     gameInfo.white, gameInfo.black);
3249                         } else {
3250                             sprintf(str, "%s [%s] vs. %s [%s]",
3251                                     gameInfo.white, white_holding,
3252                                     gameInfo.black, black_holding);
3253                         }
3254
3255                         DrawPosition(FALSE, boards[currentMove]);
3256                         DisplayTitle(str);
3257                     }
3258                     /* Suppress following prompt */
3259                     if (looking_at(buf, &i, "*% ")) {
3260                         savingComment = FALSE;
3261                     }
3262                     next_out = i;
3263                 }
3264                 continue;
3265             }
3266
3267             i++;                /* skip unparsed character and loop back */
3268         }
3269         
3270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3271             started != STARTED_HOLDINGS && i > next_out) {
3272             SendToPlayer(&buf[next_out], i - next_out);
3273             next_out = i;
3274         }
3275         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3276         
3277         leftover_len = buf_len - leftover_start;
3278         /* if buffer ends with something we couldn't parse,
3279            reparse it after appending the next read */
3280         
3281     } else if (count == 0) {
3282         RemoveInputSource(isr);
3283         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3284     } else {
3285         DisplayFatalError(_("Error reading from ICS"), error, 1);
3286     }
3287 }
3288
3289
3290 /* Board style 12 looks like this:
3291    
3292    <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
3293    
3294  * The "<12> " is stripped before it gets to this routine.  The two
3295  * trailing 0's (flip state and clock ticking) are later addition, and
3296  * some chess servers may not have them, or may have only the first.
3297  * Additional trailing fields may be added in the future.  
3298  */
3299
3300 #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"
3301
3302 #define RELATION_OBSERVING_PLAYED    0
3303 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3304 #define RELATION_PLAYING_MYMOVE      1
3305 #define RELATION_PLAYING_NOTMYMOVE  -1
3306 #define RELATION_EXAMINING           2
3307 #define RELATION_ISOLATED_BOARD     -3
3308 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3309
3310 void
3311 ParseBoard12(string)
3312      char *string;
3313
3314     GameMode newGameMode;
3315     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3316     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3317     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3318     char to_play, board_chars[200];
3319     char move_str[500], str[500], elapsed_time[500];
3320     char black[32], white[32];
3321     Board board;
3322     int prevMove = currentMove;
3323     int ticking = 2;
3324     ChessMove moveType;
3325     int fromX, fromY, toX, toY;
3326     char promoChar;
3327     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3328     char *bookHit = NULL; // [HGM] book
3329
3330     fromX = fromY = toX = toY = -1;
3331     
3332     newGame = FALSE;
3333
3334     if (appData.debugMode)
3335       fprintf(debugFP, _("Parsing board: %s\n"), string);
3336
3337     move_str[0] = NULLCHAR;
3338     elapsed_time[0] = NULLCHAR;
3339     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3340         int  i = 0, j;
3341         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3342             if(string[i] == ' ') { ranks++; files = 0; }
3343             else files++;
3344             i++;
3345         }
3346         for(j = 0; j <i; j++) board_chars[j] = string[j];
3347         board_chars[i] = '\0';
3348         string += i + 1;
3349     }
3350     n = sscanf(string, PATTERN, &to_play, &double_push,
3351                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3352                &gamenum, white, black, &relation, &basetime, &increment,
3353                &white_stren, &black_stren, &white_time, &black_time,
3354                &moveNum, str, elapsed_time, move_str, &ics_flip,
3355                &ticking);
3356
3357     if (n < 21) {
3358         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3359         DisplayError(str, 0);
3360         return;
3361     }
3362
3363     /* Convert the move number to internal form */
3364     moveNum = (moveNum - 1) * 2;
3365     if (to_play == 'B') moveNum++;
3366     if (moveNum >= MAX_MOVES) {
3367       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3368                         0, 1);
3369       return;
3370     }
3371     
3372     switch (relation) {
3373       case RELATION_OBSERVING_PLAYED:
3374       case RELATION_OBSERVING_STATIC:
3375         if (gamenum == -1) {
3376             /* Old ICC buglet */
3377             relation = RELATION_OBSERVING_STATIC;
3378         }
3379         newGameMode = IcsObserving;
3380         break;
3381       case RELATION_PLAYING_MYMOVE:
3382       case RELATION_PLAYING_NOTMYMOVE:
3383         newGameMode =
3384           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3385             IcsPlayingWhite : IcsPlayingBlack;
3386         break;
3387       case RELATION_EXAMINING:
3388         newGameMode = IcsExamining;
3389         break;
3390       case RELATION_ISOLATED_BOARD:
3391       default:
3392         /* Just display this board.  If user was doing something else,
3393            we will forget about it until the next board comes. */ 
3394         newGameMode = IcsIdle;
3395         break;
3396       case RELATION_STARTING_POSITION:
3397         newGameMode = gameMode;
3398         break;
3399     }
3400     
3401     /* Modify behavior for initial board display on move listing
3402        of wild games.
3403        */
3404     switch (ics_getting_history) {
3405       case H_FALSE:
3406       case H_REQUESTED:
3407         break;
3408       case H_GOT_REQ_HEADER:
3409       case H_GOT_UNREQ_HEADER:
3410         /* This is the initial position of the current game */
3411         gamenum = ics_gamenum;
3412         moveNum = 0;            /* old ICS bug workaround */
3413         if (to_play == 'B') {
3414           startedFromSetupPosition = TRUE;
3415           blackPlaysFirst = TRUE;
3416           moveNum = 1;
3417           if (forwardMostMove == 0) forwardMostMove = 1;
3418           if (backwardMostMove == 0) backwardMostMove = 1;
3419           if (currentMove == 0) currentMove = 1;
3420         }
3421         newGameMode = gameMode;
3422         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3423         break;
3424       case H_GOT_UNWANTED_HEADER:
3425         /* This is an initial board that we don't want */
3426         return;
3427       case H_GETTING_MOVES:
3428         /* Should not happen */
3429         DisplayError(_("Error gathering move list: extra board"), 0);
3430         ics_getting_history = H_FALSE;
3431         return;
3432     }
3433     
3434     /* Take action if this is the first board of a new game, or of a
3435        different game than is currently being displayed.  */
3436     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3437         relation == RELATION_ISOLATED_BOARD) {
3438         
3439         /* Forget the old game and get the history (if any) of the new one */
3440         if (gameMode != BeginningOfGame) {
3441           Reset(FALSE, TRUE);
3442         }
3443         newGame = TRUE;
3444         if (appData.autoRaiseBoard) BoardToTop();
3445         prevMove = -3;
3446         if (gamenum == -1) {
3447             newGameMode = IcsIdle;
3448         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3449                    appData.getMoveList) {
3450             /* Need to get game history */
3451             ics_getting_history = H_REQUESTED;
3452             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3453             SendToICS(str);
3454         }
3455         
3456         /* Initially flip the board to have black on the bottom if playing
3457            black or if the ICS flip flag is set, but let the user change
3458            it with the Flip View button. */
3459         flipView = appData.autoFlipView ? 
3460           (newGameMode == IcsPlayingBlack) || ics_flip :
3461           appData.flipView;
3462         
3463         /* Done with values from previous mode; copy in new ones */
3464         gameMode = newGameMode;
3465         ModeHighlight();
3466         ics_gamenum = gamenum;
3467         if (gamenum == gs_gamenum) {
3468             int klen = strlen(gs_kind);
3469             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3470             sprintf(str, "ICS %s", gs_kind);
3471             gameInfo.event = StrSave(str);
3472         } else {
3473             gameInfo.event = StrSave("ICS game");
3474         }
3475         gameInfo.site = StrSave(appData.icsHost);
3476         gameInfo.date = PGNDate();
3477         gameInfo.round = StrSave("-");
3478         gameInfo.white = StrSave(white);
3479         gameInfo.black = StrSave(black);
3480         timeControl = basetime * 60 * 1000;
3481         timeControl_2 = 0;
3482         timeIncrement = increment * 1000;
3483         movesPerSession = 0;
3484         gameInfo.timeControl = TimeControlTagValue();
3485         VariantSwitch(board, StringToVariant(gameInfo.event) );
3486   if (appData.debugMode) {
3487     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3488     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3489     setbuf(debugFP, NULL);
3490   }
3491
3492         gameInfo.outOfBook = NULL;
3493         
3494         /* Do we have the ratings? */
3495         if (strcmp(player1Name, white) == 0 &&
3496             strcmp(player2Name, black) == 0) {
3497             if (appData.debugMode)
3498               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3499                       player1Rating, player2Rating);
3500             gameInfo.whiteRating = player1Rating;
3501             gameInfo.blackRating = player2Rating;
3502         } else if (strcmp(player2Name, white) == 0 &&
3503                    strcmp(player1Name, black) == 0) {
3504             if (appData.debugMode)
3505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3506                       player2Rating, player1Rating);
3507             gameInfo.whiteRating = player2Rating;
3508             gameInfo.blackRating = player1Rating;
3509         }
3510         player1Name[0] = player2Name[0] = NULLCHAR;
3511
3512         /* Silence shouts if requested */
3513         if (appData.quietPlay &&
3514             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3515             SendToICS(ics_prefix);
3516             SendToICS("set shout 0\n");
3517         }
3518     }
3519     
3520     /* Deal with midgame name changes */
3521     if (!newGame) {
3522         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3523             if (gameInfo.white) free(gameInfo.white);
3524             gameInfo.white = StrSave(white);
3525         }
3526         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3527             if (gameInfo.black) free(gameInfo.black);
3528             gameInfo.black = StrSave(black);
3529         }
3530     }
3531     
3532     /* Throw away game result if anything actually changes in examine mode */
3533     if (gameMode == IcsExamining && !newGame) {
3534         gameInfo.result = GameUnfinished;
3535         if (gameInfo.resultDetails != NULL) {
3536             free(gameInfo.resultDetails);
3537             gameInfo.resultDetails = NULL;
3538         }
3539     }
3540     
3541     /* In pausing && IcsExamining mode, we ignore boards coming
3542        in if they are in a different variation than we are. */
3543     if (pauseExamInvalid) return;
3544     if (pausing && gameMode == IcsExamining) {
3545         if (moveNum <= pauseExamForwardMostMove) {
3546             pauseExamInvalid = TRUE;
3547             forwardMostMove = pauseExamForwardMostMove;
3548             return;
3549         }
3550     }
3551     
3552   if (appData.debugMode) {
3553     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3554   }
3555     /* Parse the board */
3556     for (k = 0; k < ranks; k++) {
3557       for (j = 0; j < files; j++)
3558         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3559       if(gameInfo.holdingsWidth > 1) {
3560            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3561            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3562       }
3563     }
3564     CopyBoard(boards[moveNum], board);
3565     if (moveNum == 0) {
3566         startedFromSetupPosition =
3567           !CompareBoards(board, initialPosition);
3568         if(startedFromSetupPosition)
3569             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3570     }
3571
3572     /* [HGM] Set castling rights. Take the outermost Rooks,
3573        to make it also work for FRC opening positions. Note that board12
3574        is really defective for later FRC positions, as it has no way to
3575        indicate which Rook can castle if they are on the same side of King.
3576        For the initial position we grant rights to the outermost Rooks,
3577        and remember thos rights, and we then copy them on positions
3578        later in an FRC game. This means WB might not recognize castlings with
3579        Rooks that have moved back to their original position as illegal,
3580        but in ICS mode that is not its job anyway.
3581     */
3582     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3583     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3584
3585         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3586             if(board[0][i] == WhiteRook) j = i;
3587         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3588         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3589             if(board[0][i] == WhiteRook) j = i;
3590         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3591         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3592             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3593         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3595             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3596         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597
3598         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3601         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3602             if(board[BOARD_HEIGHT-1][k] == bKing)
3603                 initialRights[5] = castlingRights[moveNum][5] = k;
3604     } else { int r;
3605         r = castlingRights[moveNum][0] = initialRights[0];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3607         r = castlingRights[moveNum][1] = initialRights[1];
3608         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3609         r = castlingRights[moveNum][3] = initialRights[3];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3611         r = castlingRights[moveNum][4] = initialRights[4];
3612         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3613         /* wildcastle kludge: always assume King has rights */
3614         r = castlingRights[moveNum][2] = initialRights[2];
3615         r = castlingRights[moveNum][5] = initialRights[5];
3616     }
3617     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3618     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3619
3620     
3621     if (ics_getting_history == H_GOT_REQ_HEADER ||
3622         ics_getting_history == H_GOT_UNREQ_HEADER) {
3623         /* This was an initial position from a move list, not
3624            the current position */
3625         return;
3626     }
3627     
3628     /* Update currentMove and known move number limits */
3629     newMove = newGame || moveNum > forwardMostMove;
3630
3631     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3632     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3633         takeback = forwardMostMove - moveNum;
3634         for (i = 0; i < takeback; i++) {
3635              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3636              SendToProgram("undo\n", &first);
3637         }
3638     }
3639
3640     if (newGame) {
3641         forwardMostMove = backwardMostMove = currentMove = moveNum;
3642         if (gameMode == IcsExamining && moveNum == 0) {
3643           /* Workaround for ICS limitation: we are not told the wild
3644              type when starting to examine a game.  But if we ask for
3645              the move list, the move list header will tell us */
3646             ics_getting_history = H_REQUESTED;
3647             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3648             SendToICS(str);
3649         }
3650     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3651                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3652         forwardMostMove = moveNum;
3653         if (!pausing || currentMove > forwardMostMove)
3654           currentMove = forwardMostMove;
3655     } else {
3656         /* New part of history that is not contiguous with old part */ 
3657         if (pausing && gameMode == IcsExamining) {
3658             pauseExamInvalid = TRUE;
3659             forwardMostMove = pauseExamForwardMostMove;
3660             return;
3661         }
3662         forwardMostMove = backwardMostMove = currentMove = moveNum;
3663         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3664             ics_getting_history = H_REQUESTED;
3665             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3666             SendToICS(str);
3667         }
3668     }
3669     
3670     /* Update the clocks */
3671     if (strchr(elapsed_time, '.')) {
3672       /* Time is in ms */
3673       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3674       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3675     } else {
3676       /* Time is in seconds */
3677       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3678       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3679     }
3680       
3681
3682 #if ZIPPY
3683     if (appData.zippyPlay && newGame &&
3684         gameMode != IcsObserving && gameMode != IcsIdle &&
3685         gameMode != IcsExamining)
3686       ZippyFirstBoard(moveNum, basetime, increment);
3687 #endif
3688     
3689     /* Put the move on the move list, first converting
3690        to canonical algebraic form. */
3691     if (moveNum > 0) {
3692   if (appData.debugMode) {
3693     if (appData.debugMode) { int f = forwardMostMove;
3694         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3695                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3696     }
3697     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3698     fprintf(debugFP, "moveNum = %d\n", moveNum);
3699     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3700     setbuf(debugFP, NULL);
3701   }
3702         if (moveNum <= backwardMostMove) {
3703             /* We don't know what the board looked like before
3704                this move.  Punt. */
3705             strcpy(parseList[moveNum - 1], move_str);
3706             strcat(parseList[moveNum - 1], " ");
3707             strcat(parseList[moveNum - 1], elapsed_time);
3708             moveList[moveNum - 1][0] = NULLCHAR;
3709         } else if (strcmp(move_str, "none") == 0) {
3710             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3711             /* Again, we don't know what the board looked like;
3712                this is really the start of the game. */
3713             parseList[moveNum - 1][0] = NULLCHAR;
3714             moveList[moveNum - 1][0] = NULLCHAR;
3715             backwardMostMove = moveNum;
3716             startedFromSetupPosition = TRUE;
3717             fromX = fromY = toX = toY = -1;
3718         } else {
3719           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3720           //                 So we parse the long-algebraic move string in stead of the SAN move
3721           int valid; char buf[MSG_SIZ], *prom;
3722
3723           // str looks something like "Q/a1-a2"; kill the slash
3724           if(str[1] == '/') 
3725                 sprintf(buf, "%c%s", str[0], str+2);
3726           else  strcpy(buf, str); // might be castling
3727           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3728                 strcat(buf, prom); // long move lacks promo specification!
3729           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3730                 if(appData.debugMode) 
3731                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3732                 strcpy(move_str, buf);
3733           }
3734           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar)
3736                || ParseOneMove(buf, moveNum - 1, &moveType,
3737                                 &fromX, &fromY, &toX, &toY, &promoChar);
3738           // end of long SAN patch
3739           if (valid) {
3740             (void) CoordsToAlgebraic(boards[moveNum - 1],
3741                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3742                                      fromY, fromX, toY, toX, promoChar,
3743                                      parseList[moveNum-1]);
3744             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3745                              castlingRights[moveNum]) ) {
3746               case MT_NONE:
3747               case MT_STALEMATE:
3748               default:
3749                 break;
3750               case MT_CHECK:
3751                 if(gameInfo.variant != VariantShogi)
3752                     strcat(parseList[moveNum - 1], "+");
3753                 break;
3754               case MT_CHECKMATE:
3755               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3756                 strcat(parseList[moveNum - 1], "#");
3757                 break;
3758             }
3759             strcat(parseList[moveNum - 1], " ");
3760             strcat(parseList[moveNum - 1], elapsed_time);
3761             /* currentMoveString is set as a side-effect of ParseOneMove */
3762             strcpy(moveList[moveNum - 1], currentMoveString);
3763             strcat(moveList[moveNum - 1], "\n");
3764           } else {
3765             /* Move from ICS was illegal!?  Punt. */
3766   if (appData.debugMode) {
3767     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3768     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3769   }
3770 #if 0
3771             if (appData.testLegality && appData.debugMode) {
3772                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3773                 DisplayError(str, 0);
3774             }
3775 #endif
3776             strcpy(parseList[moveNum - 1], move_str);
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             moveList[moveNum - 1][0] = NULLCHAR;
3780             fromX = fromY = toX = toY = -1;
3781           }
3782         }
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3785     setbuf(debugFP, NULL);
3786   }
3787
3788 #if ZIPPY
3789         /* Send move to chess program (BEFORE animating it). */
3790         if (appData.zippyPlay && !newGame && newMove && 
3791            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3792
3793             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3794                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3795                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3796                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3797                             move_str);
3798                     DisplayError(str, 0);
3799                 } else {
3800                     if (first.sendTime) {
3801                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3802                     }
3803                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3804                     if (firstMove && !bookHit) {
3805                         firstMove = FALSE;
3806                         if (first.useColors) {
3807                           SendToProgram(gameMode == IcsPlayingWhite ?
3808                                         "white\ngo\n" :
3809                                         "black\ngo\n", &first);
3810                         } else {
3811                           SendToProgram("go\n", &first);
3812                         }
3813                         first.maybeThinking = TRUE;
3814                     }
3815                 }
3816             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3817               if (moveList[moveNum - 1][0] == NULLCHAR) {
3818                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3819                 DisplayError(str, 0);
3820               } else {
3821                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3822                 SendMoveToProgram(moveNum - 1, &first);
3823               }
3824             }
3825         }
3826 #endif
3827     }
3828
3829     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3830         /* If move comes from a remote source, animate it.  If it
3831            isn't remote, it will have already been animated. */
3832         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3833             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3834         }
3835         if (!pausing && appData.highlightLastMove) {
3836             SetHighlights(fromX, fromY, toX, toY);
3837         }
3838     }
3839     
3840     /* Start the clocks */
3841     whiteFlag = blackFlag = FALSE;
3842     appData.clockMode = !(basetime == 0 && increment == 0);
3843     if (ticking == 0) {
3844       ics_clock_paused = TRUE;
3845       StopClocks();
3846     } else if (ticking == 1) {
3847       ics_clock_paused = FALSE;
3848     }
3849     if (gameMode == IcsIdle ||
3850         relation == RELATION_OBSERVING_STATIC ||
3851         relation == RELATION_EXAMINING ||
3852         ics_clock_paused)
3853       DisplayBothClocks();
3854     else
3855       StartClocks();
3856     
3857     /* Display opponents and material strengths */
3858     if (gameInfo.variant != VariantBughouse &&
3859         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3860         if (tinyLayout || smallLayout) {
3861             if(gameInfo.variant == VariantNormal)
3862                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3863                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3864                     basetime, increment);
3865             else
3866                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3868                     basetime, increment, (int) gameInfo.variant);
3869         } else {
3870             if(gameInfo.variant == VariantNormal)
3871                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3872                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3873                     basetime, increment);
3874             else
3875                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3876                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3877                     basetime, increment, VariantName(gameInfo.variant));
3878         }
3879         DisplayTitle(str);
3880   if (appData.debugMode) {
3881     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3882   }
3883     }
3884
3885    
3886     /* Display the board */
3887     if (!pausing && !appData.noGUI) {
3888       
3889       if (appData.premove)
3890           if (!gotPremove || 
3891              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893               ClearPremoveHighlights();
3894
3895       DrawPosition(FALSE, boards[currentMove]);
3896       DisplayMove(moveNum - 1);
3897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3901       }
3902     }
3903
3904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3905 #if ZIPPY
3906     if(bookHit) { // [HGM] book: simulate book reply
3907         static char bookMove[MSG_SIZ]; // a bit generous?
3908
3909         programStats.nodes = programStats.depth = programStats.time = 
3910         programStats.score = programStats.got_only_move = 0;
3911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3912
3913         strcpy(bookMove, "move ");
3914         strcat(bookMove, bookHit);
3915         HandleMachineMove(bookMove, &first);
3916     }
3917 #endif
3918 }
3919
3920 void
3921 GetMoveListEvent()
3922 {
3923     char buf[MSG_SIZ];
3924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925         ics_getting_history = H_REQUESTED;
3926         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3927         SendToICS(buf);
3928     }
3929 }
3930
3931 void
3932 AnalysisPeriodicEvent(force)
3933      int force;
3934 {
3935     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936          && !force) || !appData.periodicUpdates)
3937       return;
3938
3939     /* Send . command to Crafty to collect stats */
3940     SendToProgram(".\n", &first);
3941
3942     /* Don't send another until we get a response (this makes
3943        us stop sending to old Crafty's which don't understand
3944        the "." command (sending illegal cmds resets node count & time,
3945        which looks bad)) */
3946     programStats.ok_to_send = 0;
3947 }
3948
3949 void
3950 SendMoveToProgram(moveNum, cps)
3951      int moveNum;
3952      ChessProgramState *cps;
3953 {
3954     char buf[MSG_SIZ];
3955
3956     if (cps->useUsermove) {
3957       SendToProgram("usermove ", cps);
3958     }
3959     if (cps->useSAN) {
3960       char *space;
3961       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3962         int len = space - parseList[moveNum];
3963         memcpy(buf, parseList[moveNum], len);
3964         buf[len++] = '\n';
3965         buf[len] = NULLCHAR;
3966       } else {
3967         sprintf(buf, "%s\n", parseList[moveNum]);
3968       }
3969       SendToProgram(buf, cps);
3970     } else {
3971       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3972         AlphaRank(moveList[moveNum], 4);
3973         SendToProgram(moveList[moveNum], cps);
3974         AlphaRank(moveList[moveNum], 4); // and back
3975       } else
3976       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3977        * the engine. It would be nice to have a better way to identify castle 
3978        * moves here. */
3979       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3980                                                                          && cps->useOOCastle) {
3981         int fromX = moveList[moveNum][0] - AAA; 
3982         int fromY = moveList[moveNum][1] - ONE;
3983         int toX = moveList[moveNum][2] - AAA; 
3984         int toY = moveList[moveNum][3] - ONE;
3985         if((boards[moveNum][fromY][fromX] == WhiteKing 
3986             && boards[moveNum][toY][toX] == WhiteRook)
3987            || (boards[moveNum][fromY][fromX] == BlackKing 
3988                && boards[moveNum][toY][toX] == BlackRook)) {
3989           if(toX > fromX) SendToProgram("O-O\n", cps);
3990           else SendToProgram("O-O-O\n", cps);
3991         }
3992         else SendToProgram(moveList[moveNum], cps);
3993       }
3994       else SendToProgram(moveList[moveNum], cps);
3995       /* End of additions by Tord */
3996     }
3997
3998     /* [HGM] setting up the opening has brought engine in force mode! */
3999     /*       Send 'go' if we are in a mode where machine should play. */
4000     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4001         (gameMode == TwoMachinesPlay   ||
4002 #ifdef ZIPPY
4003          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4004 #endif
4005          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4006         SendToProgram("go\n", cps);
4007   if (appData.debugMode) {
4008     fprintf(debugFP, "(extra)\n");
4009   }
4010     }
4011     setboardSpoiledMachineBlack = 0;
4012 }
4013
4014 void
4015 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4016      ChessMove moveType;
4017      int fromX, fromY, toX, toY;
4018 {
4019     char user_move[MSG_SIZ];
4020
4021     switch (moveType) {
4022       default:
4023         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4024                 (int)moveType, fromX, fromY, toX, toY);
4025         DisplayError(user_move + strlen("say "), 0);
4026         break;
4027       case WhiteKingSideCastle:
4028       case BlackKingSideCastle:
4029       case WhiteQueenSideCastleWild:
4030       case BlackQueenSideCastleWild:
4031       /* PUSH Fabien */
4032       case WhiteHSideCastleFR:
4033       case BlackHSideCastleFR:
4034       /* POP Fabien */
4035         sprintf(user_move, "o-o\n");
4036         break;
4037       case WhiteQueenSideCastle:
4038       case BlackQueenSideCastle:
4039       case WhiteKingSideCastleWild:
4040       case BlackKingSideCastleWild:
4041       /* PUSH Fabien */
4042       case WhiteASideCastleFR:
4043       case BlackASideCastleFR:
4044       /* POP Fabien */
4045         sprintf(user_move, "o-o-o\n");
4046         break;
4047       case WhitePromotionQueen:
4048       case BlackPromotionQueen:
4049       case WhitePromotionRook:
4050       case BlackPromotionRook:
4051       case WhitePromotionBishop:
4052       case BlackPromotionBishop:
4053       case WhitePromotionKnight:
4054       case BlackPromotionKnight:
4055       case WhitePromotionKing:
4056       case BlackPromotionKing:
4057       case WhitePromotionChancellor:
4058       case BlackPromotionChancellor:
4059       case WhitePromotionArchbishop:
4060       case BlackPromotionArchbishop:
4061         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4062             sprintf(user_move, "%c%c%c%c=%c\n",
4063                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4064                 PieceToChar(WhiteFerz));
4065         else if(gameInfo.variant == VariantGreat)
4066             sprintf(user_move, "%c%c%c%c=%c\n",
4067                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068                 PieceToChar(WhiteMan));
4069         else
4070             sprintf(user_move, "%c%c%c%c=%c\n",
4071                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072                 PieceToChar(PromoPiece(moveType)));
4073         break;
4074       case WhiteDrop:
4075       case BlackDrop:
4076         sprintf(user_move, "%c@%c%c\n",
4077                 ToUpper(PieceToChar((ChessSquare) fromX)),
4078                 AAA + toX, ONE + toY);
4079         break;
4080       case NormalMove:
4081       case WhiteCapturesEnPassant:
4082       case BlackCapturesEnPassant:
4083       case IllegalMove:  /* could be a variant we don't quite understand */
4084         sprintf(user_move, "%c%c%c%c\n",
4085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4086         break;
4087     }
4088     SendToICS(user_move);
4089 }
4090
4091 void
4092 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4093      int rf, ff, rt, ft;
4094      char promoChar;
4095      char move[7];
4096 {
4097     if (rf == DROP_RANK) {
4098         sprintf(move, "%c@%c%c\n",
4099                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4100     } else {
4101         if (promoChar == 'x' || promoChar == NULLCHAR) {
4102             sprintf(move, "%c%c%c%c\n",
4103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4104         } else {
4105             sprintf(move, "%c%c%c%c%c\n",
4106                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4107         }
4108     }
4109 }
4110
4111 void
4112 ProcessICSInitScript(f)
4113      FILE *f;
4114 {
4115     char buf[MSG_SIZ];
4116
4117     while (fgets(buf, MSG_SIZ, f)) {
4118         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4119     }
4120
4121     fclose(f);
4122 }
4123
4124
4125 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4126 void
4127 AlphaRank(char *move, int n)
4128 {
4129 //    char *p = move, c; int x, y;
4130
4131     if (appData.debugMode) {
4132         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4133     }
4134
4135     if(move[1]=='*' && 
4136        move[2]>='0' && move[2]<='9' &&
4137        move[3]>='a' && move[3]<='x'    ) {
4138         move[1] = '@';
4139         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4140         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4141     } else
4142     if(move[0]>='0' && move[0]<='9' &&
4143        move[1]>='a' && move[1]<='x' &&
4144        move[2]>='0' && move[2]<='9' &&
4145        move[3]>='a' && move[3]<='x'    ) {
4146         /* input move, Shogi -> normal */
4147         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4148         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4149         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4150         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4151     } else
4152     if(move[1]=='@' &&
4153        move[3]>='0' && move[3]<='9' &&
4154        move[2]>='a' && move[2]<='x'    ) {
4155         move[1] = '*';
4156         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4157         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4158     } else
4159     if(
4160        move[0]>='a' && move[0]<='x' &&
4161        move[3]>='0' && move[3]<='9' &&
4162        move[2]>='a' && move[2]<='x'    ) {
4163          /* output move, normal -> Shogi */
4164         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4165         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4166         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4167         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4168         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4169     }
4170     if (appData.debugMode) {
4171         fprintf(debugFP, "   out = '%s'\n", move);
4172     }
4173 }
4174
4175 /* Parser for moves from gnuchess, ICS, or user typein box */
4176 Boolean
4177 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4178      char *move;
4179      int moveNum;
4180      ChessMove *moveType;
4181      int *fromX, *fromY, *toX, *toY;
4182      char *promoChar;
4183 {       
4184     if (appData.debugMode) {
4185         fprintf(debugFP, "move to parse: %s\n", move);
4186     }
4187     *moveType = yylexstr(moveNum, move);
4188
4189     switch (*moveType) {
4190       case WhitePromotionChancellor:
4191       case BlackPromotionChancellor:
4192       case WhitePromotionArchbishop:
4193       case BlackPromotionArchbishop:
4194       case WhitePromotionQueen:
4195       case BlackPromotionQueen:
4196       case WhitePromotionRook:
4197       case BlackPromotionRook:
4198       case WhitePromotionBishop:
4199       case BlackPromotionBishop:
4200       case WhitePromotionKnight:
4201       case BlackPromotionKnight:
4202       case WhitePromotionKing:
4203       case BlackPromotionKing:
4204       case NormalMove:
4205       case WhiteCapturesEnPassant:
4206       case BlackCapturesEnPassant:
4207       case WhiteKingSideCastle:
4208       case WhiteQueenSideCastle:
4209       case BlackKingSideCastle:
4210       case BlackQueenSideCastle:
4211       case WhiteKingSideCastleWild:
4212       case WhiteQueenSideCastleWild:
4213       case BlackKingSideCastleWild:
4214       case BlackQueenSideCastleWild:
4215       /* Code added by Tord: */
4216       case WhiteHSideCastleFR:
4217       case WhiteASideCastleFR:
4218       case BlackHSideCastleFR:
4219       case BlackASideCastleFR:
4220       /* End of code added by Tord */
4221       case IllegalMove:         /* bug or odd chess variant */
4222         *fromX = currentMoveString[0] - AAA;
4223         *fromY = currentMoveString[1] - ONE;
4224         *toX = currentMoveString[2] - AAA;
4225         *toY = currentMoveString[3] - ONE;
4226         *promoChar = currentMoveString[4];
4227         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4228             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4229     if (appData.debugMode) {
4230         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4231     }
4232             *fromX = *fromY = *toX = *toY = 0;
4233             return FALSE;
4234         }
4235         if (appData.testLegality) {
4236           return (*moveType != IllegalMove);
4237         } else {
4238           return !(fromX == fromY && toX == toY);
4239         }
4240
4241       case WhiteDrop:
4242       case BlackDrop:
4243         *fromX = *moveType == WhiteDrop ?
4244           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4245           (int) CharToPiece(ToLower(currentMoveString[0]));
4246         *fromY = DROP_RANK;
4247         *toX = currentMoveString[2] - AAA;
4248         *toY = currentMoveString[3] - ONE;
4249         *promoChar = NULLCHAR;
4250         return TRUE;
4251
4252       case AmbiguousMove:
4253       case ImpossibleMove:
4254       case (ChessMove) 0:       /* end of file */
4255       case ElapsedTime:
4256       case Comment:
4257       case PGNTag:
4258       case NAG:
4259       case WhiteWins:
4260       case BlackWins:
4261       case GameIsDrawn:
4262       default:
4263     if (appData.debugMode) {
4264         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4265     }
4266         /* bug? */
4267         *fromX = *fromY = *toX = *toY = 0;
4268         *promoChar = NULLCHAR;
4269         return FALSE;
4270     }
4271 }
4272
4273 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4274 // All positions will have equal probability, but the current method will not provide a unique
4275 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4276 #define DARK 1
4277 #define LITE 2
4278 #define ANY 3
4279
4280 int squaresLeft[4];
4281 int piecesLeft[(int)BlackPawn];
4282 int seed, nrOfShuffles;
4283
4284 void GetPositionNumber()
4285 {       // sets global variable seed
4286         int i;
4287
4288         seed = appData.defaultFrcPosition;
4289         if(seed < 0) { // randomize based on time for negative FRC position numbers
4290                 for(i=0; i<50; i++) seed += random();
4291                 seed = random() ^ random() >> 8 ^ random() << 8;
4292                 if(seed<0) seed = -seed;
4293         }
4294 }
4295
4296 int put(Board board, int pieceType, int rank, int n, int shade)
4297 // put the piece on the (n-1)-th empty squares of the given shade
4298 {
4299         int i;
4300
4301         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4302                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4303                         board[rank][i] = (ChessSquare) pieceType;
4304                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4305                         squaresLeft[ANY]--;
4306                         piecesLeft[pieceType]--; 
4307                         return i;
4308                 }
4309         }
4310         return -1;
4311 }
4312
4313
4314 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4315 // calculate where the next piece goes, (any empty square), and put it there
4316 {
4317         int i;
4318
4319         i = seed % squaresLeft[shade];
4320         nrOfShuffles *= squaresLeft[shade];
4321         seed /= squaresLeft[shade];
4322         put(board, pieceType, rank, i, shade);
4323 }
4324
4325 void AddTwoPieces(Board board, int pieceType, int rank)
4326 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4327 {
4328         int i, n=squaresLeft[ANY], j=n-1, k;
4329
4330         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4331         i = seed % k;  // pick one
4332         nrOfShuffles *= k;
4333         seed /= k;
4334         while(i >= j) i -= j--;
4335         j = n - 1 - j; i += j;
4336         put(board, pieceType, rank, j, ANY);
4337         put(board, pieceType, rank, i, ANY);
4338 }
4339
4340 void SetUpShuffle(Board board, int number)
4341 {
4342         int i, p, first=1;
4343
4344         GetPositionNumber(); nrOfShuffles = 1;
4345
4346         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4347         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4348         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4349
4350         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4351
4352         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4353             p = (int) board[0][i];
4354             if(p < (int) BlackPawn) piecesLeft[p] ++;
4355             board[0][i] = EmptySquare;
4356         }
4357
4358         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4359             // shuffles restricted to allow normal castling put KRR first
4360             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4361                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4363                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4365                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4366             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4367                 put(board, WhiteRook, 0, 0, ANY);
4368             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4369         }
4370
4371         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4372             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4373             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4374                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4375                 while(piecesLeft[p] >= 2) {
4376                     AddOnePiece(board, p, 0, LITE);
4377                     AddOnePiece(board, p, 0, DARK);
4378                 }
4379                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4380             }
4381
4382         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4383             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4384             // but we leave King and Rooks for last, to possibly obey FRC restriction
4385             if(p == (int)WhiteRook) continue;
4386             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4387             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4388         }
4389
4390         // now everything is placed, except perhaps King (Unicorn) and Rooks
4391
4392         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4393             // Last King gets castling rights
4394             while(piecesLeft[(int)WhiteUnicorn]) {
4395                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4397             }
4398
4399             while(piecesLeft[(int)WhiteKing]) {
4400                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4402             }
4403
4404
4405         } else {
4406             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4407             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4408         }
4409
4410         // Only Rooks can be left; simply place them all
4411         while(piecesLeft[(int)WhiteRook]) {
4412                 i = put(board, WhiteRook, 0, 0, ANY);
4413                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4414                         if(first) {
4415                                 first=0;
4416                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4417                         }
4418                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4419                 }
4420         }
4421         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4422             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4423         }
4424
4425         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4426 }
4427
4428 int SetCharTable( char *table, const char * map )
4429 /* [HGM] moved here from winboard.c because of its general usefulness */
4430 /*       Basically a safe strcpy that uses the last character as King */
4431 {
4432     int result = FALSE; int NrPieces;
4433
4434     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4435                     && NrPieces >= 12 && !(NrPieces&1)) {
4436         int i; /* [HGM] Accept even length from 12 to 34 */
4437
4438         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4439         for( i=0; i<NrPieces/2-1; i++ ) {
4440             table[i] = map[i];
4441             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4442         }
4443         table[(int) WhiteKing]  = map[NrPieces/2-1];
4444         table[(int) BlackKing]  = map[NrPieces-1];
4445
4446         result = TRUE;
4447     }
4448
4449     return result;
4450 }
4451
4452 void Prelude(Board board)
4453 {       // [HGM] superchess: random selection of exo-pieces
4454         int i, j, k; ChessSquare p; 
4455         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4456
4457         GetPositionNumber(); // use FRC position number
4458
4459         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4460             SetCharTable(pieceToChar, appData.pieceToCharTable);
4461             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4462                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4463         }
4464
4465         j = seed%4;                 seed /= 4; 
4466         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4467         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4468         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4469         j = seed%3 + (seed%3 >= j); seed /= 3; 
4470         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4471         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4472         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4473         j = seed%3;                 seed /= 3; 
4474         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4475         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4476         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4477         j = seed%2 + (seed%2 >= j); seed /= 2; 
4478         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4479         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4480         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4481         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4482         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4483         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4484         put(board, exoPieces[0],    0, 0, ANY);
4485         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4486 }
4487
4488 void
4489 InitPosition(redraw)
4490      int redraw;
4491 {
4492     ChessSquare (* pieces)[BOARD_SIZE];
4493     int i, j, pawnRow, overrule,
4494     oldx = gameInfo.boardWidth,
4495     oldy = gameInfo.boardHeight,
4496     oldh = gameInfo.holdingsWidth,
4497     oldv = gameInfo.variant;
4498
4499     currentMove = forwardMostMove = backwardMostMove = 0;
4500     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4501
4502     /* [AS] Initialize pv info list [HGM] and game status */
4503     {
4504         for( i=0; i<MAX_MOVES; i++ ) {
4505             pvInfoList[i].depth = 0;
4506             epStatus[i]=EP_NONE;
4507             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4508         }
4509
4510         initialRulePlies = 0; /* 50-move counter start */
4511
4512         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4513         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4514     }
4515
4516     
4517     /* [HGM] logic here is completely changed. In stead of full positions */
4518     /* the initialized data only consist of the two backranks. The switch */
4519     /* selects which one we will use, which is than copied to the Board   */
4520     /* initialPosition, which for the rest is initialized by Pawns and    */
4521     /* empty squares. This initial position is then copied to boards[0],  */
4522     /* possibly after shuffling, so that it remains available.            */
4523
4524     gameInfo.holdingsWidth = 0; /* default board sizes */
4525     gameInfo.boardWidth    = 8;
4526     gameInfo.boardHeight   = 8;
4527     gameInfo.holdingsSize  = 0;
4528     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4529     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4530     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4531
4532     switch (gameInfo.variant) {
4533     case VariantFischeRandom:
4534       shuffleOpenings = TRUE;
4535     default:
4536       pieces = FIDEArray;
4537       break;
4538     case VariantShatranj:
4539       pieces = ShatranjArray;
4540       nrCastlingRights = 0;
4541       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4542       break;
4543     case VariantTwoKings:
4544       pieces = twoKingsArray;
4545       break;
4546     case VariantCapaRandom:
4547       shuffleOpenings = TRUE;
4548     case VariantCapablanca:
4549       pieces = CapablancaArray;
4550       gameInfo.boardWidth = 10;
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4552       break;
4553     case VariantGothic:
4554       pieces = GothicArray;
4555       gameInfo.boardWidth = 10;
4556       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4557       break;
4558     case VariantJanus:
4559       pieces = JanusArray;
4560       gameInfo.boardWidth = 10;
4561       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4562       nrCastlingRights = 6;
4563         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4564         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4565         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4566         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4567         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4568         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4569       break;
4570     case VariantFalcon:
4571       pieces = FalconArray;
4572       gameInfo.boardWidth = 10;
4573       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4574       break;
4575     case VariantXiangqi:
4576       pieces = XiangqiArray;
4577       gameInfo.boardWidth  = 9;
4578       gameInfo.boardHeight = 10;
4579       nrCastlingRights = 0;
4580       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4581       break;
4582     case VariantShogi:
4583       pieces = ShogiArray;
4584       gameInfo.boardWidth  = 9;
4585       gameInfo.boardHeight = 9;
4586       gameInfo.holdingsSize = 7;
4587       nrCastlingRights = 0;
4588       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4589       break;
4590     case VariantCourier:
4591       pieces = CourierArray;
4592       gameInfo.boardWidth  = 12;
4593       nrCastlingRights = 0;
4594       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4595       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4596       break;
4597     case VariantKnightmate:
4598       pieces = KnightmateArray;
4599       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4600       break;
4601     case VariantFairy:
4602       pieces = fairyArray;
4603       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4604       break;
4605     case VariantGreat:
4606       pieces = GreatArray;
4607       gameInfo.boardWidth = 10;
4608       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4609       gameInfo.holdingsSize = 8;
4610       break;
4611     case VariantSuper:
4612       pieces = FIDEArray;
4613       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4614       gameInfo.holdingsSize = 8;
4615       startedFromSetupPosition = TRUE;
4616       break;
4617     case VariantCrazyhouse:
4618     case VariantBughouse:
4619       pieces = FIDEArray;
4620       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4621       gameInfo.holdingsSize = 5;
4622       break;
4623     case VariantWildCastle:
4624       pieces = FIDEArray;
4625       /* !!?shuffle with kings guaranteed to be on d or e file */
4626       shuffleOpenings = 1;
4627       break;
4628     case VariantNoCastle:
4629       pieces = FIDEArray;
4630       nrCastlingRights = 0;
4631       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4632       /* !!?unconstrained back-rank shuffle */
4633       shuffleOpenings = 1;
4634       break;
4635     }
4636
4637     overrule = 0;
4638     if(appData.NrFiles >= 0) {
4639         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4640         gameInfo.boardWidth = appData.NrFiles;
4641     }
4642     if(appData.NrRanks >= 0) {
4643         gameInfo.boardHeight = appData.NrRanks;
4644     }
4645     if(appData.holdingsSize >= 0) {
4646         i = appData.holdingsSize;
4647         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4648         gameInfo.holdingsSize = i;
4649     }
4650     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4651     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4652         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4653
4654     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4655     if(pawnRow < 1) pawnRow = 1;
4656
4657     /* User pieceToChar list overrules defaults */
4658     if(appData.pieceToCharTable != NULL)
4659         SetCharTable(pieceToChar, appData.pieceToCharTable);
4660
4661     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4662
4663         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4664             s = (ChessSquare) 0; /* account holding counts in guard band */
4665         for( i=0; i<BOARD_HEIGHT; i++ )
4666             initialPosition[i][j] = s;
4667
4668         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4669         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4670         initialPosition[pawnRow][j] = WhitePawn;
4671         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4672         if(gameInfo.variant == VariantXiangqi) {
4673             if(j&1) {
4674                 initialPosition[pawnRow][j] = 
4675                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4676                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4677                    initialPosition[2][j] = WhiteCannon;
4678                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4679                 }
4680             }
4681         }
4682         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4683     }
4684     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4685
4686             j=BOARD_LEFT+1;
4687             initialPosition[1][j] = WhiteBishop;
4688             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4689             j=BOARD_RGHT-2;
4690             initialPosition[1][j] = WhiteRook;
4691             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4692     }
4693
4694     if( nrCastlingRights == -1) {
4695         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4696         /*       This sets default castling rights from none to normal corners   */
4697         /* Variants with other castling rights must set them themselves above    */
4698         nrCastlingRights = 6;
4699        
4700         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4701         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4702         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4703         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4704         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4705         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4706      }
4707
4708      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4709      if(gameInfo.variant == VariantGreat) { // promotion commoners
4710         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4711         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4712         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4713         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4714      }
4715 #if 0
4716     if(gameInfo.variant == VariantFischeRandom) {
4717       if( appData.defaultFrcPosition < 0 ) {
4718         ShuffleFRC( initialPosition );
4719       }
4720       else {
4721         SetupFRC( initialPosition, appData.defaultFrcPosition );
4722       }
4723       startedFromSetupPosition = TRUE;
4724     } else 
4725 #else
4726   if (appData.debugMode) {
4727     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4728   }
4729     if(shuffleOpenings) {
4730         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731         startedFromSetupPosition = TRUE;
4732     }
4733 #endif
4734     if(startedFromPositionFile) {
4735       /* [HGM] loadPos: use PositionFile for every new game */
4736       CopyBoard(initialPosition, filePosition);
4737       for(i=0; i<nrCastlingRights; i++)
4738           castlingRights[0][i] = initialRights[i] = fileRights[i];
4739       startedFromSetupPosition = TRUE;
4740     }
4741
4742     CopyBoard(boards[0], initialPosition);
4743
4744     if(oldx != gameInfo.boardWidth ||
4745        oldy != gameInfo.boardHeight ||
4746        oldh != gameInfo.holdingsWidth
4747 #ifdef GOTHIC
4748        || oldv == VariantGothic ||        // For licensing popups
4749        gameInfo.variant == VariantGothic
4750 #endif
4751 #ifdef FALCON
4752        || oldv == VariantFalcon ||
4753        gameInfo.variant == VariantFalcon
4754 #endif
4755                                          )
4756             InitDrawingSizes(-2 ,0);
4757
4758     if (redraw)
4759       DrawPosition(TRUE, boards[currentMove]);
4760 }
4761
4762 void
4763 SendBoard(cps, moveNum)
4764      ChessProgramState *cps;
4765      int moveNum;
4766 {
4767     char message[MSG_SIZ];
4768     
4769     if (cps->useSetboard) {
4770       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771       sprintf(message, "setboard %s\n", fen);
4772       SendToProgram(message, cps);
4773       free(fen);
4774
4775     } else {
4776       ChessSquare *bp;
4777       int i, j;
4778       /* Kludge to set black to move, avoiding the troublesome and now
4779        * deprecated "black" command.
4780        */
4781       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782
4783       SendToProgram("edit\n", cps);
4784       SendToProgram("#\n", cps);
4785       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786         bp = &boards[moveNum][i][BOARD_LEFT];
4787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788           if ((int) *bp < (int) BlackPawn) {
4789             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4790                     AAA + j, ONE + i);
4791             if(message[0] == '+' || message[0] == '~') {
4792                 sprintf(message, "%c%c%c+\n",
4793                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4794                         AAA + j, ONE + i);
4795             }
4796             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797                 message[1] = BOARD_RGHT   - 1 - j + '1';
4798                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799             }
4800             SendToProgram(message, cps);
4801           }
4802         }
4803       }
4804     
4805       SendToProgram("c\n", cps);
4806       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807         bp = &boards[moveNum][i][BOARD_LEFT];
4808         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809           if (((int) *bp != (int) EmptySquare)
4810               && ((int) *bp >= (int) BlackPawn)) {
4811             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826     
4827       SendToProgram(".\n", cps);
4828     }
4829     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4830 }
4831
4832 int
4833 IsPromotion(fromX, fromY, toX, toY)
4834      int fromX, fromY, toX, toY;
4835 {
4836     /* [HGM] add Shogi promotions */
4837     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4838     ChessSquare piece;
4839
4840     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842    /* [HGM] Note to self: line above also weeds out drops */
4843     piece = boards[currentMove][fromY][fromX];
4844     if(gameInfo.variant == VariantShogi) {
4845         promotionZoneSize = 3;
4846         highestPromotingPiece = (int)WhiteKing;
4847         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848            and if in normal chess we then allow promotion to King, why not
4849            allow promotion of other piece in Shogi?                         */
4850     }
4851     if((int)piece >= BlackPawn) {
4852         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853              return FALSE;
4854         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855     } else {
4856         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4857            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858     }
4859     return ( (int)piece <= highestPromotingPiece );
4860 }
4861
4862 int
4863 InPalace(row, column)
4864      int row, column;
4865 {   /* [HGM] for Xiangqi */
4866     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867          column < (BOARD_WIDTH + 4)/2 &&
4868          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4869     return FALSE;
4870 }
4871
4872 int
4873 PieceForSquare (x, y)
4874      int x;
4875      int y;
4876 {
4877   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4878      return -1;
4879   else
4880      return boards[currentMove][y][x];
4881 }
4882
4883 int
4884 OKToStartUserMove(x, y)
4885      int x, y;
4886 {
4887     ChessSquare from_piece;
4888     int white_piece;
4889
4890     if (matchMode) return FALSE;
4891     if (gameMode == EditPosition) return TRUE;
4892
4893     if (x >= 0 && y >= 0)
4894       from_piece = boards[currentMove][y][x];
4895     else
4896       from_piece = EmptySquare;
4897
4898     if (from_piece == EmptySquare) return FALSE;
4899
4900     white_piece = (int)from_piece >= (int)WhitePawn &&
4901       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4902
4903     switch (gameMode) {
4904       case PlayFromGameFile:
4905       case AnalyzeFile:
4906       case TwoMachinesPlay:
4907       case EndOfGame:
4908         return FALSE;
4909
4910       case IcsObserving:
4911       case IcsIdle:
4912         return FALSE;
4913
4914       case MachinePlaysWhite:
4915       case IcsPlayingBlack:
4916         if (appData.zippyPlay) return FALSE;
4917         if (white_piece) {
4918             DisplayMoveError(_("You are playing Black"));
4919             return FALSE;
4920         }
4921         break;
4922
4923       case MachinePlaysBlack:
4924       case IcsPlayingWhite:
4925         if (appData.zippyPlay) return FALSE;
4926         if (!white_piece) {
4927             DisplayMoveError(_("You are playing White"));
4928             return FALSE;
4929         }
4930         break;
4931
4932       case EditGame:
4933         if (!white_piece && WhiteOnMove(currentMove)) {
4934             DisplayMoveError(_("It is White's turn"));
4935             return FALSE;
4936         }           
4937         if (white_piece && !WhiteOnMove(currentMove)) {
4938             DisplayMoveError(_("It is Black's turn"));
4939             return FALSE;
4940         }           
4941         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942             /* Editing correspondence game history */
4943             /* Could disallow this or prompt for confirmation */
4944             cmailOldMove = -1;
4945         }
4946         if (currentMove < forwardMostMove) {
4947             /* Discarding moves */
4948             /* Could prompt for confirmation here,
4949                but I don't think that's such a good idea */
4950             forwardMostMove = currentMove;
4951         }
4952         break;
4953
4954       case BeginningOfGame:
4955         if (appData.icsActive) return FALSE;
4956         if (!appData.noChessProgram) {
4957             if (!white_piece) {
4958                 DisplayMoveError(_("You are playing White"));
4959                 return FALSE;
4960             }
4961         }
4962         break;
4963         
4964       case Training:
4965         if (!white_piece && WhiteOnMove(currentMove)) {
4966             DisplayMoveError(_("It is White's turn"));
4967             return FALSE;
4968         }           
4969         if (white_piece && !WhiteOnMove(currentMove)) {
4970             DisplayMoveError(_("It is Black's turn"));
4971             return FALSE;
4972         }           
4973         break;
4974
4975       default:
4976       case IcsExamining:
4977         break;
4978     }
4979     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980         && gameMode != AnalyzeFile && gameMode != Training) {
4981         DisplayMoveError(_("Displayed position is not current"));
4982         return FALSE;
4983     }
4984     return TRUE;
4985 }
4986
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4992
4993
4994 ChessMove
4995 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4996      int fromX, fromY, toX, toY;
4997      int promoChar;
4998 {
4999     ChessMove moveType;
5000     ChessSquare pdown, pup;
5001
5002     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003     if ((fromX == toX) && (fromY == toY)) {
5004         return ImpossibleMove;
5005     }
5006
5007     /* [HGM] suppress all moves into holdings area and guard band */
5008     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009             return ImpossibleMove;
5010
5011     /* [HGM] <sameColor> moved to here from winboard.c */
5012     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5013     pdown = boards[currentMove][fromY][fromX];
5014     pup = boards[currentMove][toY][toX];
5015     if (    gameMode != EditPosition &&
5016             (WhitePawn <= pdown && pdown < BlackPawn &&
5017              WhitePawn <= pup && pup < BlackPawn  ||
5018              BlackPawn <= pdown && pdown < EmptySquare &&
5019              BlackPawn <= pup && pup < EmptySquare 
5020             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5023         )           )
5024          return ImpossibleMove;
5025
5026     /* Check if the user is playing in turn.  This is complicated because we
5027        let the user "pick up" a piece before it is his turn.  So the piece he
5028        tried to pick up may have been captured by the time he puts it down!
5029        Therefore we use the color the user is supposed to be playing in this
5030        test, not the color of the piece that is currently on the starting
5031        square---except in EditGame mode, where the user is playing both
5032        sides; fortunately there the capture race can't happen.  (It can
5033        now happen in IcsExamining mode, but that's just too bad.  The user
5034        will get a somewhat confusing message in that case.)
5035        */
5036
5037     switch (gameMode) {
5038       case PlayFromGameFile:
5039       case AnalyzeFile:
5040       case TwoMachinesPlay:
5041       case EndOfGame:
5042       case IcsObserving:
5043       case IcsIdle:
5044         /* We switched into a game mode where moves are not accepted,
5045            perhaps while the mouse button was down. */
5046         return ImpossibleMove;
5047
5048       case MachinePlaysWhite:
5049         /* User is moving for Black */
5050         if (WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is White's turn"));
5052             return ImpossibleMove;
5053         }
5054         break;
5055
5056       case MachinePlaysBlack:
5057         /* User is moving for White */
5058         if (!WhiteOnMove(currentMove)) {
5059             DisplayMoveError(_("It is Black's turn"));
5060             return ImpossibleMove;
5061         }
5062         break;
5063
5064       case EditGame:
5065       case IcsExamining:
5066       case BeginningOfGame:
5067       case AnalyzeMode:
5068       case Training:
5069         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5070             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5071             /* User is moving for Black */
5072             if (WhiteOnMove(currentMove)) {
5073                 DisplayMoveError(_("It is White's turn"));
5074                 return ImpossibleMove;
5075             }
5076         } else {
5077             /* User is moving for White */
5078             if (!WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is Black's turn"));
5080                 return ImpossibleMove;
5081             }
5082         }
5083         break;
5084
5085       case IcsPlayingBlack:
5086         /* User is moving for Black */
5087         if (WhiteOnMove(currentMove)) {
5088             if (!appData.premove) {
5089                 DisplayMoveError(_("It is White's turn"));
5090             } else if (toX >= 0 && toY >= 0) {
5091                 premoveToX = toX;
5092                 premoveToY = toY;
5093                 premoveFromX = fromX;
5094                 premoveFromY = fromY;
5095                 premovePromoChar = promoChar;
5096                 gotPremove = 1;
5097                 if (appData.debugMode) 
5098                     fprintf(debugFP, "Got premove: fromX %d,"
5099                             "fromY %d, toX %d, toY %d\n",
5100                             fromX, fromY, toX, toY);
5101             }
5102             return ImpossibleMove;
5103         }
5104         break;
5105
5106       case IcsPlayingWhite:
5107         /* User is moving for White */
5108         if (!WhiteOnMove(currentMove)) {
5109             if (!appData.premove) {
5110                 DisplayMoveError(_("It is Black's turn"));
5111             } else if (toX >= 0 && toY >= 0) {
5112                 premoveToX = toX;
5113                 premoveToY = toY;
5114                 premoveFromX = fromX;
5115                 premoveFromY = fromY;
5116                 premovePromoChar = promoChar;
5117                 gotPremove = 1;
5118                 if (appData.debugMode) 
5119                     fprintf(debugFP, "Got premove: fromX %d,"
5120                             "fromY %d, toX %d, toY %d\n",
5121                             fromX, fromY, toX, toY);
5122             }
5123             return ImpossibleMove;
5124         }
5125         break;
5126
5127       default:
5128         break;
5129
5130       case EditPosition:
5131         /* EditPosition, empty square, or different color piece;
5132            click-click move is possible */
5133         if (toX == -2 || toY == -2) {
5134             boards[0][fromY][fromX] = EmptySquare;
5135             return AmbiguousMove;
5136         } else if (toX >= 0 && toY >= 0) {
5137             boards[0][toY][toX] = boards[0][fromY][fromX];
5138             boards[0][fromY][fromX] = EmptySquare;
5139             return AmbiguousMove;
5140         }
5141         return ImpossibleMove;
5142     }
5143
5144     /* [HGM] If move started in holdings, it means a drop */
5145     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5146          if( pup != EmptySquare ) return ImpossibleMove;
5147          if(appData.testLegality) {
5148              /* it would be more logical if LegalityTest() also figured out
5149               * which drops are legal. For now we forbid pawns on back rank.
5150               * Shogi is on its own here...
5151               */
5152              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5153                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5154                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155          }
5156          return WhiteDrop; /* Not needed to specify white or black yet */
5157     }
5158
5159     userOfferedDraw = FALSE;
5160         
5161     /* [HGM] always test for legality, to get promotion info */
5162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163                           epStatus[currentMove], castlingRights[currentMove],
5164                                          fromY, fromX, toY, toX, promoChar);
5165
5166     /* [HGM] but possibly ignore an IllegalMove result */
5167     if (appData.testLegality) {
5168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5169             DisplayMoveError(_("Illegal move"));
5170             return ImpossibleMove;
5171         }
5172     }
5173 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174     return moveType;
5175     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5176        function is made into one that returns an OK move type if FinishMove
5177        should be called. This to give the calling driver routine the
5178        opportunity to finish the userMove input with a promotion popup,
5179        without bothering the user with this for invalid or illegal moves */
5180
5181 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5182 }
5183
5184 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 int
5186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187      ChessMove moveType;
5188      int fromX, fromY, toX, toY;
5189      /*char*/int promoChar;
5190 {
5191     char *bookHit = 0;
5192 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5193     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5194         // [HGM] superchess: suppress promotions to non-available piece
5195         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5196         if(WhiteOnMove(currentMove)) {
5197             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5198         } else {
5199             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5200         }
5201     }
5202
5203     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5204        move type in caller when we know the move is a legal promotion */
5205     if(moveType == NormalMove && promoChar)
5206         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5207 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5208     /* [HGM] convert drag-and-drop piece drops to standard form */
5209     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5210          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5211            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5212                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5213 //         fromX = boards[currentMove][fromY][fromX];
5214            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5215            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5216            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5217            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5218          fromY = DROP_RANK;
5219     }
5220
5221     /* [HGM] <popupFix> The following if has been moved here from
5222        UserMoveEvent(). Because it seemed to belon here (why not allow
5223        piece drops in training games?), and because it can only be
5224        performed after it is known to what we promote. */
5225     if (gameMode == Training) {
5226       /* compare the move played on the board to the next move in the
5227        * game. If they match, display the move and the opponent's response. 
5228        * If they don't match, display an error message.
5229        */
5230       int saveAnimate;
5231       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5232       CopyBoard(testBoard, boards[currentMove]);
5233       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5234
5235       if (CompareBoards(testBoard, boards[currentMove+1])) {
5236         ForwardInner(currentMove+1);
5237
5238         /* Autoplay the opponent's response.
5239          * if appData.animate was TRUE when Training mode was entered,
5240          * the response will be animated.
5241          */
5242         saveAnimate = appData.animate;
5243         appData.animate = animateTraining;
5244         ForwardInner(currentMove+1);
5245         appData.animate = saveAnimate;
5246
5247         /* check for the end of the game */
5248         if (currentMove >= forwardMostMove) {
5249           gameMode = PlayFromGameFile;
5250           ModeHighlight();
5251           SetTrainingModeOff();
5252           DisplayInformation(_("End of game"));
5253         }
5254       } else {
5255         DisplayError(_("Incorrect move"), 0);
5256       }
5257       return 1;
5258     }
5259
5260   /* Ok, now we know that the move is good, so we can kill
5261      the previous line in Analysis Mode */
5262   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5263     forwardMostMove = currentMove;
5264   }
5265
5266   /* If we need the chess program but it's dead, restart it */
5267   ResurrectChessProgram();
5268
5269   /* A user move restarts a paused game*/
5270   if (pausing)
5271     PauseEvent();
5272
5273   thinkOutput[0] = NULLCHAR;
5274
5275   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5276
5277   if (gameMode == BeginningOfGame) {
5278     if (appData.noChessProgram) {
5279       gameMode = EditGame;
5280       SetGameInfo();
5281     } else {
5282       char buf[MSG_SIZ];
5283       gameMode = MachinePlaysBlack;
5284       StartClocks();
5285       SetGameInfo();
5286       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5287       DisplayTitle(buf);
5288       if (first.sendName) {
5289         sprintf(buf, "name %s\n", gameInfo.white);
5290         SendToProgram(buf, &first);
5291       }
5292       StartClocks();
5293     }
5294     ModeHighlight();
5295   }
5296 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5297   /* Relay move to ICS or chess engine */
5298   if (appData.icsActive) {
5299     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5300         gameMode == IcsExamining) {
5301       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5302       ics_user_moved = 1;
5303     }
5304   } else {
5305     if (first.sendTime && (gameMode == BeginningOfGame ||
5306                            gameMode == MachinePlaysWhite ||
5307                            gameMode == MachinePlaysBlack)) {
5308       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5309     }
5310     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5311          // [HGM] book: if program might be playing, let it use book
5312         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5313         first.maybeThinking = TRUE;
5314     } else SendMoveToProgram(forwardMostMove-1, &first);
5315     if (currentMove == cmailOldMove + 1) {
5316       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5317     }
5318   }
5319
5320   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5321
5322   switch (gameMode) {
5323   case EditGame:
5324     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5325                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5326     case MT_NONE:
5327     case MT_CHECK:
5328       break;
5329     case MT_CHECKMATE:
5330     case MT_STAINMATE:
5331       if (WhiteOnMove(currentMove)) {
5332         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5333       } else {
5334         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5335       }
5336       break;
5337     case MT_STALEMATE:
5338       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5339       break;
5340     }
5341     break;
5342     
5343   case MachinePlaysBlack:
5344   case MachinePlaysWhite:
5345     /* disable certain menu options while machine is thinking */
5346     SetMachineThinkingEnables();
5347     break;
5348
5349   default:
5350     break;
5351   }
5352
5353   if(bookHit) { // [HGM] book: simulate book reply
5354         static char bookMove[MSG_SIZ]; // a bit generous?
5355
5356         programStats.nodes = programStats.depth = programStats.time = 
5357         programStats.score = programStats.got_only_move = 0;
5358         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5359
5360         strcpy(bookMove, "move ");
5361         strcat(bookMove, bookHit);
5362         HandleMachineMove(bookMove, &first);
5363   }
5364   return 1;
5365 }
5366
5367 void
5368 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5369      int fromX, fromY, toX, toY;
5370      int promoChar;
5371 {
5372     /* [HGM] This routine was added to allow calling of its two logical
5373        parts from other modules in the old way. Before, UserMoveEvent()
5374        automatically called FinishMove() if the move was OK, and returned
5375        otherwise. I separated the two, in order to make it possible to
5376        slip a promotion popup in between. But that it always needs two
5377        calls, to the first part, (now called UserMoveTest() ), and to
5378        FinishMove if the first part succeeded. Calls that do not need
5379        to do anything in between, can call this routine the old way. 
5380     */
5381     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5382 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5383     if(moveType != ImpossibleMove)
5384         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5385 }
5386
5387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5388 {
5389 //    char * hint = lastHint;
5390     FrontEndProgramStats stats;
5391
5392     stats.which = cps == &first ? 0 : 1;
5393     stats.depth = cpstats->depth;
5394     stats.nodes = cpstats->nodes;
5395     stats.score = cpstats->score;
5396     stats.time = cpstats->time;
5397     stats.pv = cpstats->movelist;
5398     stats.hint = lastHint;
5399     stats.an_move_index = 0;
5400     stats.an_move_count = 0;
5401
5402     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5403         stats.hint = cpstats->move_name;
5404         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5405         stats.an_move_count = cpstats->nr_moves;
5406     }
5407
5408     SetProgramStats( &stats );
5409 }
5410
5411 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5412 {   // [HGM] book: this routine intercepts moves to simulate book replies
5413     char *bookHit = NULL;
5414
5415     //first determine if the incoming move brings opponent into his book
5416     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5417         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5418     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5419     if(bookHit != NULL && !cps->bookSuspend) {
5420         // make sure opponent is not going to reply after receiving move to book position
5421         SendToProgram("force\n", cps);
5422         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5423     }
5424     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5425     // now arrange restart after book miss
5426     if(bookHit) {
5427         // after a book hit we never send 'go', and the code after the call to this routine
5428         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5429         char buf[MSG_SIZ];
5430         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5431         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5432         SendToProgram(buf, cps);
5433         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5434     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5435         SendToProgram("go\n", cps);
5436         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5437     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5438         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5439             SendToProgram("go\n", cps); 
5440         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5441     }
5442     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5443 }
5444
5445 char *savedMessage;
5446 ChessProgramState *savedState;
5447 void DeferredBookMove(void)
5448 {
5449         if(savedState->lastPing != savedState->lastPong)
5450                     ScheduleDelayedEvent(DeferredBookMove, 10);
5451         else
5452         HandleMachineMove(savedMessage, savedState);
5453 }
5454
5455 void
5456 HandleMachineMove(message, cps)
5457      char *message;
5458      ChessProgramState *cps;
5459 {
5460     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5461     char realname[MSG_SIZ];
5462     int fromX, fromY, toX, toY;
5463     ChessMove moveType;
5464     char promoChar;
5465     char *p;
5466     int machineWhite;
5467     char *bookHit;
5468
5469 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5470     /*
5471      * Kludge to ignore BEL characters
5472      */
5473     while (*message == '\007') message++;
5474
5475     /*
5476      * [HGM] engine debug message: ignore lines starting with '#' character
5477      */
5478     if(cps->debug && *message == '#') return;
5479
5480     /*
5481      * Look for book output
5482      */
5483     if (cps == &first && bookRequested) {
5484         if (message[0] == '\t' || message[0] == ' ') {
5485             /* Part of the book output is here; append it */
5486             strcat(bookOutput, message);
5487             strcat(bookOutput, "  \n");
5488             return;
5489         } else if (bookOutput[0] != NULLCHAR) {
5490             /* All of book output has arrived; display it */
5491             char *p = bookOutput;
5492             while (*p != NULLCHAR) {
5493                 if (*p == '\t') *p = ' ';
5494                 p++;
5495             }
5496             DisplayInformation(bookOutput);
5497             bookRequested = FALSE;
5498             /* Fall through to parse the current output */
5499         }
5500     }
5501
5502     /*
5503      * Look for machine move.
5504      */
5505     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5506         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5507     {
5508         /* This method is only useful on engines that support ping */
5509         if (cps->lastPing != cps->lastPong) {
5510           if (gameMode == BeginningOfGame) {
5511             /* Extra move from before last new; ignore */
5512             if (appData.debugMode) {
5513                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5514             }
5515           } else {
5516             if (appData.debugMode) {
5517                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5518                         cps->which, gameMode);
5519             }
5520
5521             SendToProgram("undo\n", cps);
5522           }
5523           return;
5524         }
5525
5526         switch (gameMode) {
5527           case BeginningOfGame:
5528             /* Extra move from before last reset; ignore */
5529             if (appData.debugMode) {
5530                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5531             }
5532             return;
5533
5534           case EndOfGame:
5535           case IcsIdle:
5536           default:
5537             /* Extra move after we tried to stop.  The mode test is
5538                not a reliable way of detecting this problem, but it's
5539                the best we can do on engines that don't support ping.
5540             */
5541             if (appData.debugMode) {
5542                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5543                         cps->which, gameMode);
5544             }
5545             SendToProgram("undo\n", cps);
5546             return;
5547
5548           case MachinePlaysWhite:
5549           case IcsPlayingWhite:
5550             machineWhite = TRUE;
5551             break;
5552
5553           case MachinePlaysBlack:
5554           case IcsPlayingBlack:
5555             machineWhite = FALSE;
5556             break;
5557
5558           case TwoMachinesPlay:
5559             machineWhite = (cps->twoMachinesColor[0] == 'w');
5560             break;
5561         }
5562         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5563             if (appData.debugMode) {
5564                 fprintf(debugFP,
5565                         "Ignoring move out of turn by %s, gameMode %d"
5566                         ", forwardMost %d\n",
5567                         cps->which, gameMode, forwardMostMove);
5568             }
5569             return;
5570         }
5571
5572     if (appData.debugMode) { int f = forwardMostMove;
5573         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5574                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5575     }
5576         if(cps->alphaRank) AlphaRank(machineMove, 4);
5577         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5578                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5579             /* Machine move could not be parsed; ignore it. */
5580             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5581                     machineMove, cps->which);
5582             DisplayError(buf1, 0);
5583             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5584                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5585             if (gameMode == TwoMachinesPlay) {
5586               GameEnds(machineWhite ? BlackWins : WhiteWins,
5587                        buf1, GE_XBOARD);
5588             }
5589             return;
5590         }
5591
5592         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5593         /* So we have to redo legality test with true e.p. status here,  */
5594         /* to make sure an illegal e.p. capture does not slip through,   */
5595         /* to cause a forfeit on a justified illegal-move complaint      */
5596         /* of the opponent.                                              */
5597         if( gameMode==TwoMachinesPlay && appData.testLegality
5598             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5599                                                               ) {
5600            ChessMove moveType;
5601            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5602                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5603                              fromY, fromX, toY, toX, promoChar);
5604             if (appData.debugMode) {
5605                 int i;
5606                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5607                     castlingRights[forwardMostMove][i], castlingRank[i]);
5608                 fprintf(debugFP, "castling rights\n");
5609             }
5610             if(moveType == IllegalMove) {
5611                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5612                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5613                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5614                            buf1, GE_XBOARD);
5615                 return;
5616            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5617            /* [HGM] Kludge to handle engines that send FRC-style castling
5618               when they shouldn't (like TSCP-Gothic) */
5619            switch(moveType) {
5620              case WhiteASideCastleFR:
5621              case BlackASideCastleFR:
5622                toX+=2;
5623                currentMoveString[2]++;
5624                break;
5625              case WhiteHSideCastleFR:
5626              case BlackHSideCastleFR:
5627                toX--;
5628                currentMoveString[2]--;
5629                break;
5630              default: ; // nothing to do, but suppresses warning of pedantic compilers
5631            }
5632         }
5633         hintRequested = FALSE;
5634         lastHint[0] = NULLCHAR;
5635         bookRequested = FALSE;
5636         /* Program may be pondering now */
5637         cps->maybeThinking = TRUE;
5638         if (cps->sendTime == 2) cps->sendTime = 1;
5639         if (cps->offeredDraw) cps->offeredDraw--;
5640
5641 #if ZIPPY
5642         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5643             first.initDone) {
5644           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5645           ics_user_moved = 1;
5646           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5647                 char buf[3*MSG_SIZ];
5648
5649                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5650                         programStats.score / 100.,
5651                         programStats.depth,
5652                         programStats.time / 100.,
5653                         (unsigned int)programStats.nodes,
5654                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5655                         programStats.movelist);
5656                 SendToICS(buf);
5657           }
5658         }
5659 #endif
5660         /* currentMoveString is set as a side-effect of ParseOneMove */
5661         strcpy(machineMove, currentMoveString);
5662         strcat(machineMove, "\n");
5663         strcpy(moveList[forwardMostMove], machineMove);
5664
5665         /* [AS] Save move info and clear stats for next move */
5666         pvInfoList[ forwardMostMove ].score = programStats.score;
5667         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5668         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5669         ClearProgramStats();
5670         thinkOutput[0] = NULLCHAR;
5671         hiddenThinkOutputState = 0;
5672
5673         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5674
5675         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5676         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5677             int count = 0;
5678
5679             while( count < adjudicateLossPlies ) {
5680                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5681
5682                 if( count & 1 ) {
5683                     score = -score; /* Flip score for winning side */
5684                 }
5685
5686                 if( score > adjudicateLossThreshold ) {
5687                     break;
5688                 }
5689
5690                 count++;
5691             }
5692
5693             if( count >= adjudicateLossPlies ) {
5694                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5695
5696                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5697                     "Xboard adjudication", 
5698                     GE_XBOARD );
5699
5700                 return;
5701             }
5702         }
5703
5704         if( gameMode == TwoMachinesPlay ) {
5705           // [HGM] some adjudications useful with buggy engines
5706             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5707           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5708
5709
5710             if( appData.testLegality )
5711             {   /* [HGM] Some more adjudications for obstinate engines */
5712                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5713                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5714                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5715                 static int moveCount = 6;
5716                 ChessMove result;
5717                 char *reason = NULL;
5718
5719                 /* Count what is on board. */
5720                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5721                 {   ChessSquare p = boards[forwardMostMove][i][j];
5722                     int m=i;
5723
5724                     switch((int) p)
5725                     {   /* count B,N,R and other of each side */
5726                         case WhiteKing:
5727                         case BlackKing:
5728                              NrK++; break; // [HGM] atomic: count Kings
5729                         case WhiteKnight:
5730                              NrWN++; break;
5731                         case WhiteBishop:
5732                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5733                              bishopsColor |= 1 << ((i^j)&1);
5734                              NrWB++; break;
5735                         case BlackKnight:
5736                              NrBN++; break;
5737                         case BlackBishop:
5738                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5739                              bishopsColor |= 1 << ((i^j)&1);
5740                              NrBB++; break;
5741                         case WhiteRook:
5742                              NrWR++; break;
5743                         case BlackRook:
5744                              NrBR++; break;
5745                         case WhiteQueen:
5746                              NrWQ++; break;
5747                         case BlackQueen:
5748                              NrBQ++; break;
5749                         case EmptySquare: 
5750                              break;
5751                         case BlackPawn:
5752                              m = 7-i;
5753                         case WhitePawn:
5754                              PawnAdvance += m; NrPawns++;
5755                     }
5756                     NrPieces += (p != EmptySquare);
5757                     NrW += ((int)p < (int)BlackPawn);
5758                     if(gameInfo.variant == VariantXiangqi && 
5759                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5760                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5761                         NrW -= ((int)p < (int)BlackPawn);
5762                     }
5763                 }
5764
5765                 /* Some material-based adjudications that have to be made before stalemate test */
5766                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5767                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5768                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5769                      if(appData.checkMates) {
5770                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5771                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5772                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5773                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5774                          return;
5775                      }
5776                 }
5777
5778                 /* Bare King in Shatranj (loses) or Losers (wins) */
5779                 if( NrW == 1 || NrPieces - NrW == 1) {
5780                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5781                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5782                      if(appData.checkMates) {
5783                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5784                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5785                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5786                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5787                          return;
5788                      }
5789                   } else
5790                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5791                   {    /* bare King */
5792                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5793                         if(appData.checkMates) {
5794                             /* but only adjudicate if adjudication enabled */
5795                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5796                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5797                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5798                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5799                             return;
5800                         }
5801                   }
5802                 } else bare = 1;
5803
5804
5805             // don't wait for engine to announce game end if we can judge ourselves
5806             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5807                                        castlingRights[forwardMostMove]) ) {
5808               case MT_CHECK:
5809                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5810                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5811                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5812                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5813                             checkCnt++;
5814                         if(checkCnt >= 2) {
5815                             reason = "Xboard adjudication: 3rd check";
5816                             epStatus[forwardMostMove] = EP_CHECKMATE;
5817                             break;
5818                         }
5819                     }
5820                 }
5821               case MT_NONE:
5822               default:
5823                 break;
5824               case MT_STALEMATE:
5825               case MT_STAINMATE:
5826                 reason = "Xboard adjudication: Stalemate";
5827                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5828                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5829                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5830                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5831                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5832                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5833                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5834                                                                         EP_CHECKMATE : EP_WINS);
5835                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5836                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5837                 }
5838                 break;
5839               case MT_CHECKMATE:
5840                 reason = "Xboard adjudication: Checkmate";
5841                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5842                 break;
5843             }
5844
5845                 switch(i = epStatus[forwardMostMove]) {
5846                     case EP_STALEMATE:
5847                         result = GameIsDrawn; break;
5848                     case EP_CHECKMATE:
5849                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5850                     case EP_WINS:
5851                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5852                     default:
5853                         result = (ChessMove) 0;
5854                 }
5855                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5856                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5857                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858                     GameEnds( result, reason, GE_XBOARD );
5859                     return;
5860                 }
5861
5862                 /* Next absolutely insufficient mating material. */
5863                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5864                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5865                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5866                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5867                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5868
5869                      /* always flag draws, for judging claims */
5870                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5871
5872                      if(appData.materialDraws) {
5873                          /* but only adjudicate them if adjudication enabled */
5874                          SendToProgram("force\n", cps->other); // suppress reply
5875                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5876                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5877                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5878                          return;
5879                      }
5880                 }
5881
5882                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5883                 if(NrPieces == 4 && 
5884                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5885                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5886                    || NrWN==2 || NrBN==2     /* KNNK */
5887                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5888                   ) ) {
5889                      if(--moveCount < 0 && appData.trivialDraws)
5890                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5891                           SendToProgram("force\n", cps->other); // suppress reply
5892                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5893                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5894                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5895                           return;
5896                      }
5897                 } else moveCount = 6;
5898             }
5899           }
5900 #if 1
5901     if (appData.debugMode) { int i;
5902       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5903               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5904               appData.drawRepeats);
5905       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5906            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5907
5908     }
5909 #endif
5910                 /* Check for rep-draws */
5911                 count = 0;
5912                 for(k = forwardMostMove-2;
5913                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5914                         epStatus[k] < EP_UNKNOWN &&
5915                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5916                     k-=2)
5917                 {   int rights=0;
5918 #if 0
5919     if (appData.debugMode) {
5920       fprintf(debugFP, " loop\n");
5921     }
5922 #endif
5923                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5924 #if 0
5925     if (appData.debugMode) {
5926       fprintf(debugFP, "match\n");
5927     }
5928 #endif
5929                         /* compare castling rights */
5930                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5931                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5932                                 rights++; /* King lost rights, while rook still had them */
5933                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5934                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5935                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5936                                    rights++; /* but at least one rook lost them */
5937                         }
5938                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5939                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5940                                 rights++; 
5941                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5942                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5943                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5944                                    rights++;
5945                         }
5946 #if 0
5947     if (appData.debugMode) {
5948       for(i=0; i<nrCastlingRights; i++)
5949       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5950     }
5951
5952     if (appData.debugMode) {
5953       fprintf(debugFP, " %d %d\n", rights, k);
5954     }
5955 #endif
5956                         if( rights == 0 && ++count > appData.drawRepeats-2
5957                             && appData.drawRepeats > 1) {
5958                              /* adjudicate after user-specified nr of repeats */
5959                              SendToProgram("force\n", cps->other); // suppress reply
5960                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5961                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5962                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5963                                 // [HGM] xiangqi: check for forbidden perpetuals
5964                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5965                                 for(m=forwardMostMove; m>k; m-=2) {
5966                                     if(MateTest(boards[m], PosFlags(m), 
5967                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5968                                         ourPerpetual = 0; // the current mover did not always check
5969                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5970                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5971                                         hisPerpetual = 0; // the opponent did not always check
5972                                 }
5973                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5974                                                                         ourPerpetual, hisPerpetual);
5975                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5976                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5977                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5978                                     return;
5979                                 }
5980                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5981                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5982                                 // Now check for perpetual chases
5983                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5984                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5985                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5986                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5987                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5988                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5989                                         return;
5990                                     }
5991                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5992                                         break; // Abort repetition-checking loop.
5993                                 }
5994                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5995                              }
5996                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5997                              return;
5998                         }
5999                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6000                              epStatus[forwardMostMove] = EP_REP_DRAW;
6001                     }
6002                 }
6003
6004                 /* Now we test for 50-move draws. Determine ply count */
6005                 count = forwardMostMove;
6006                 /* look for last irreversble move */
6007                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6008                     count--;
6009                 /* if we hit starting position, add initial plies */
6010                 if( count == backwardMostMove )
6011                     count -= initialRulePlies;
6012                 count = forwardMostMove - count; 
6013                 if( count >= 100)
6014                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6015                          /* this is used to judge if draw claims are legal */
6016                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6017                          SendToProgram("force\n", cps->other); // suppress reply
6018                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6019                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6020                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6021                          return;
6022                 }
6023
6024                 /* if draw offer is pending, treat it as a draw claim
6025                  * when draw condition present, to allow engines a way to
6026                  * claim draws before making their move to avoid a race
6027                  * condition occurring after their move
6028                  */
6029                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6030                          char *p = NULL;
6031                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6032                              p = "Draw claim: 50-move rule";
6033                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6034                              p = "Draw claim: 3-fold repetition";
6035                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6036                              p = "Draw claim: insufficient mating material";
6037                          if( p != NULL ) {
6038                              SendToProgram("force\n", cps->other); // suppress reply
6039                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6040                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6041                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6042                              return;
6043                          }
6044                 }
6045
6046
6047                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6048                     SendToProgram("force\n", cps->other); // suppress reply
6049                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6050                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6051
6052                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6053
6054                     return;
6055                 }
6056         }
6057
6058         bookHit = NULL;
6059         if (gameMode == TwoMachinesPlay) {
6060             /* [HGM] relaying draw offers moved to after reception of move */
6061             /* and interpreting offer as claim if it brings draw condition */
6062             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6063                 SendToProgram("draw\n", cps->other);
6064             }
6065             if (cps->other->sendTime) {
6066                 SendTimeRemaining(cps->other,
6067                                   cps->other->twoMachinesColor[0] == 'w');
6068             }
6069             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6070             if (firstMove && !bookHit) {
6071                 firstMove = FALSE;
6072                 if (cps->other->useColors) {
6073                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6074                 }
6075                 SendToProgram("go\n", cps->other);
6076             }
6077             cps->other->maybeThinking = TRUE;
6078         }
6079
6080         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081         
6082         if (!pausing && appData.ringBellAfterMoves) {
6083             RingBell();
6084         }
6085
6086         /* 
6087          * Reenable menu items that were disabled while
6088          * machine was thinking
6089          */
6090         if (gameMode != TwoMachinesPlay)
6091             SetUserThinkingEnables();
6092
6093         // [HGM] book: after book hit opponent has received move and is now in force mode
6094         // force the book reply into it, and then fake that it outputted this move by jumping
6095         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6096         if(bookHit) {
6097                 static char bookMove[MSG_SIZ]; // a bit generous?
6098
6099                 strcpy(bookMove, "move ");
6100                 strcat(bookMove, bookHit);
6101                 message = bookMove;
6102                 cps = cps->other;
6103                 programStats.nodes = programStats.depth = programStats.time = 
6104                 programStats.score = programStats.got_only_move = 0;
6105                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6106
6107                 if(cps->lastPing != cps->lastPong) {
6108                     savedMessage = message; // args for deferred call
6109                     savedState = cps;
6110                     ScheduleDelayedEvent(DeferredBookMove, 10);
6111                     return;
6112                 }
6113                 goto FakeBookMove;
6114         }
6115
6116         return;
6117     }
6118
6119     /* Set special modes for chess engines.  Later something general
6120      *  could be added here; for now there is just one kludge feature,
6121      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6122      *  when "xboard" is given as an interactive command.
6123      */
6124     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6125         cps->useSigint = FALSE;
6126         cps->useSigterm = FALSE;
6127     }
6128     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6129       ParseFeatures(message+8, cps);
6130       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6131     }
6132
6133     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6134      * want this, I was asked to put it in, and obliged.
6135      */
6136     if (!strncmp(message, "setboard ", 9)) {
6137         Board initial_position; int i;
6138
6139         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6140
6141         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6142             DisplayError(_("Bad FEN received from engine"), 0);
6143             return ;
6144         } else {
6145            Reset(FALSE, FALSE);
6146            CopyBoard(boards[0], initial_position);
6147            initialRulePlies = FENrulePlies;
6148            epStatus[0] = FENepStatus;
6149            for( i=0; i<nrCastlingRights; i++ )
6150                 castlingRights[0][i] = FENcastlingRights[i];
6151            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6152            else gameMode = MachinePlaysBlack;                 
6153            DrawPosition(FALSE, boards[currentMove]);
6154         }
6155         return;
6156     }
6157
6158     /*
6159      * Look for communication commands
6160      */
6161     if (!strncmp(message, "telluser ", 9)) {
6162         DisplayNote(message + 9);
6163         return;
6164     }
6165     if (!strncmp(message, "tellusererror ", 14)) {
6166         DisplayError(message + 14, 0);
6167         return;
6168     }
6169     if (!strncmp(message, "tellopponent ", 13)) {
6170       if (appData.icsActive) {
6171         if (loggedOn) {
6172           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6173           SendToICS(buf1);
6174         }
6175       } else {
6176         DisplayNote(message + 13);
6177       }
6178       return;
6179     }
6180     if (!strncmp(message, "tellothers ", 11)) {
6181       if (appData.icsActive) {
6182         if (loggedOn) {
6183           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6184           SendToICS(buf1);
6185         }
6186       }
6187       return;
6188     }
6189     if (!strncmp(message, "tellall ", 8)) {
6190       if (appData.icsActive) {
6191         if (loggedOn) {
6192           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6193           SendToICS(buf1);
6194         }
6195       } else {
6196         DisplayNote(message + 8);
6197       }
6198       return;
6199     }
6200     if (strncmp(message, "warning", 7) == 0) {
6201         /* Undocumented feature, use tellusererror in new code */
6202         DisplayError(message, 0);
6203         return;
6204     }
6205     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6206         strcpy(realname, cps->tidy);
6207         strcat(realname, " query");
6208         AskQuestion(realname, buf2, buf1, cps->pr);
6209         return;
6210     }
6211     /* Commands from the engine directly to ICS.  We don't allow these to be 
6212      *  sent until we are logged on. Crafty kibitzes have been known to 
6213      *  interfere with the login process.
6214      */
6215     if (loggedOn) {
6216         if (!strncmp(message, "tellics ", 8)) {
6217             SendToICS(message + 8);
6218             SendToICS("\n");
6219             return;
6220         }
6221         if (!strncmp(message, "tellicsnoalias ", 15)) {
6222             SendToICS(ics_prefix);
6223             SendToICS(message + 15);
6224             SendToICS("\n");
6225             return;
6226         }
6227         /* The following are for backward compatibility only */
6228         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6229             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6230             SendToICS(ics_prefix);
6231             SendToICS(message);
6232             SendToICS("\n");
6233             return;
6234         }
6235     }
6236     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6237         return;
6238     }
6239     /*
6240      * If the move is illegal, cancel it and redraw the board.
6241      * Also deal with other error cases.  Matching is rather loose
6242      * here to accommodate engines written before the spec.
6243      */
6244     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6245         strncmp(message, "Error", 5) == 0) {
6246         if (StrStr(message, "name") || 
6247             StrStr(message, "rating") || StrStr(message, "?") ||
6248             StrStr(message, "result") || StrStr(message, "board") ||
6249             StrStr(message, "bk") || StrStr(message, "computer") ||
6250             StrStr(message, "variant") || StrStr(message, "hint") ||
6251             StrStr(message, "random") || StrStr(message, "depth") ||
6252             StrStr(message, "accepted")) {
6253             return;
6254         }
6255         if (StrStr(message, "protover")) {
6256           /* Program is responding to input, so it's apparently done
6257              initializing, and this error message indicates it is
6258              protocol version 1.  So we don't need to wait any longer
6259              for it to initialize and send feature commands. */
6260           FeatureDone(cps, 1);
6261           cps->protocolVersion = 1;
6262           return;
6263         }
6264         cps->maybeThinking = FALSE;
6265
6266         if (StrStr(message, "draw")) {
6267             /* Program doesn't have "draw" command */
6268             cps->sendDrawOffers = 0;
6269             return;
6270         }
6271         if (cps->sendTime != 1 &&
6272             (StrStr(message, "time") || StrStr(message, "otim"))) {
6273           /* Program apparently doesn't have "time" or "otim" command */
6274           cps->sendTime = 0;
6275           return;
6276         }
6277         if (StrStr(message, "analyze")) {
6278             cps->analysisSupport = FALSE;
6279             cps->analyzing = FALSE;
6280             Reset(FALSE, TRUE);
6281             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6282             DisplayError(buf2, 0);
6283             return;
6284         }
6285         if (StrStr(message, "(no matching move)st")) {
6286           /* Special kludge for GNU Chess 4 only */
6287           cps->stKludge = TRUE;
6288           SendTimeControl(cps, movesPerSession, timeControl,
6289                           timeIncrement, appData.searchDepth,
6290                           searchTime);
6291           return;
6292         }
6293         if (StrStr(message, "(no matching move)sd")) {
6294           /* Special kludge for GNU Chess 4 only */
6295           cps->sdKludge = TRUE;
6296           SendTimeControl(cps, movesPerSession, timeControl,
6297                           timeIncrement, appData.searchDepth,
6298                           searchTime);
6299           return;
6300         }
6301         if (!StrStr(message, "llegal")) {
6302             return;
6303         }
6304         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6305             gameMode == IcsIdle) return;
6306         if (forwardMostMove <= backwardMostMove) return;
6307 #if 0
6308         /* Following removed: it caused a bug where a real illegal move
6309            message in analyze mored would be ignored. */
6310         if (cps == &first && programStats.ok_to_send == 0) {
6311             /* Bogus message from Crafty responding to "."  This filtering
6312                can miss some of the bad messages, but fortunately the bug 
6313                is fixed in current Crafty versions, so it doesn't matter. */
6314             return;
6315         }
6316 #endif
6317         if (pausing) PauseEvent();
6318         if (gameMode == PlayFromGameFile) {
6319             /* Stop reading this game file */
6320             gameMode = EditGame;
6321             ModeHighlight();
6322         }
6323         currentMove = --forwardMostMove;
6324         DisplayMove(currentMove-1); /* before DisplayMoveError */
6325         SwitchClocks();
6326         DisplayBothClocks();
6327         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6328                 parseList[currentMove], cps->which);
6329         DisplayMoveError(buf1);
6330         DrawPosition(FALSE, boards[currentMove]);
6331
6332         /* [HGM] illegal-move claim should forfeit game when Xboard */
6333         /* only passes fully legal moves                            */
6334         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6335             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6336                                 "False illegal-move claim", GE_XBOARD );
6337         }
6338         return;
6339     }
6340     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6341         /* Program has a broken "time" command that
6342            outputs a string not ending in newline.
6343            Don't use it. */
6344         cps->sendTime = 0;
6345     }
6346     
6347     /*
6348      * If chess program startup fails, exit with an error message.
6349      * Attempts to recover here are futile.
6350      */
6351     if ((StrStr(message, "unknown host") != NULL)
6352         || (StrStr(message, "No remote directory") != NULL)
6353         || (StrStr(message, "not found") != NULL)
6354         || (StrStr(message, "No such file") != NULL)
6355         || (StrStr(message, "can't alloc") != NULL)
6356         || (StrStr(message, "Permission denied") != NULL)) {
6357
6358         cps->maybeThinking = FALSE;
6359         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6360                 cps->which, cps->program, cps->host, message);
6361         RemoveInputSource(cps->isr);
6362         DisplayFatalError(buf1, 0, 1);
6363         return;
6364     }
6365     
6366     /* 
6367      * Look for hint output
6368      */
6369     if (sscanf(message, "Hint: %s", buf1) == 1) {
6370         if (cps == &first && hintRequested) {
6371             hintRequested = FALSE;
6372             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6373                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6374                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6375                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6376                                     fromY, fromX, toY, toX, promoChar, buf1);
6377                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6378                 DisplayInformation(buf2);
6379             } else {
6380                 /* Hint move could not be parsed!? */
6381               snprintf(buf2, sizeof(buf2),
6382                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6383                         buf1, cps->which);
6384                 DisplayError(buf2, 0);
6385             }
6386         } else {
6387             strcpy(lastHint, buf1);
6388         }
6389         return;
6390     }
6391
6392     /*
6393      * Ignore other messages if game is not in progress
6394      */
6395     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6396         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6397
6398     /*
6399      * look for win, lose, draw, or draw offer
6400      */
6401     if (strncmp(message, "1-0", 3) == 0) {
6402         char *p, *q, *r = "";
6403         p = strchr(message, '{');
6404         if (p) {
6405             q = strchr(p, '}');
6406             if (q) {
6407                 *q = NULLCHAR;
6408                 r = p + 1;
6409             }
6410         }
6411         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6412         return;
6413     } else if (strncmp(message, "0-1", 3) == 0) {
6414         char *p, *q, *r = "";
6415         p = strchr(message, '{');
6416         if (p) {
6417             q = strchr(p, '}');
6418             if (q) {
6419                 *q = NULLCHAR;
6420                 r = p + 1;
6421             }
6422         }
6423         /* Kludge for Arasan 4.1 bug */
6424         if (strcmp(r, "Black resigns") == 0) {
6425             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6426             return;
6427         }
6428         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6429         return;
6430     } else if (strncmp(message, "1/2", 3) == 0) {
6431         char *p, *q, *r = "";
6432         p = strchr(message, '{');
6433         if (p) {
6434             q = strchr(p, '}');
6435             if (q) {
6436                 *q = NULLCHAR;
6437                 r = p + 1;
6438             }
6439         }
6440             
6441         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6442         return;
6443
6444     } else if (strncmp(message, "White resign", 12) == 0) {
6445         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "Black resign", 12) == 0) {
6448         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6449         return;
6450     } else if (strncmp(message, "White matches", 13) == 0 ||
6451                strncmp(message, "Black matches", 13) == 0   ) {
6452         /* [HGM] ignore GNUShogi noises */
6453         return;
6454     } else if (strncmp(message, "White", 5) == 0 &&
6455                message[5] != '(' &&
6456                StrStr(message, "Black") == NULL) {
6457         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6458         return;
6459     } else if (strncmp(message, "Black", 5) == 0 &&
6460                message[5] != '(') {
6461         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6462         return;
6463     } else if (strcmp(message, "resign") == 0 ||
6464                strcmp(message, "computer resigns") == 0) {
6465         switch (gameMode) {
6466           case MachinePlaysBlack:
6467           case IcsPlayingBlack:
6468             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6469             break;
6470           case MachinePlaysWhite:
6471           case IcsPlayingWhite:
6472             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6473             break;
6474           case TwoMachinesPlay:
6475             if (cps->twoMachinesColor[0] == 'w')
6476               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6477             else
6478               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6479             break;
6480           default:
6481             /* can't happen */
6482             break;
6483         }
6484         return;
6485     } else if (strncmp(message, "opponent mates", 14) == 0) {
6486         switch (gameMode) {
6487           case MachinePlaysBlack:
6488           case IcsPlayingBlack:
6489             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6490             break;
6491           case MachinePlaysWhite:
6492           case IcsPlayingWhite:
6493             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6494             break;
6495           case TwoMachinesPlay:
6496             if (cps->twoMachinesColor[0] == 'w')
6497               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6498             else
6499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6500             break;
6501           default:
6502             /* can't happen */
6503             break;
6504         }
6505         return;
6506     } else if (strncmp(message, "computer mates", 14) == 0) {
6507         switch (gameMode) {
6508           case MachinePlaysBlack:
6509           case IcsPlayingBlack:
6510             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6511             break;
6512           case MachinePlaysWhite:
6513           case IcsPlayingWhite:
6514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6515             break;
6516           case TwoMachinesPlay:
6517             if (cps->twoMachinesColor[0] == 'w')
6518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6519             else
6520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6521             break;
6522           default:
6523             /* can't happen */
6524             break;
6525         }
6526         return;
6527     } else if (strncmp(message, "checkmate", 9) == 0) {
6528         if (WhiteOnMove(forwardMostMove)) {
6529             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6530         } else {
6531             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6532         }
6533         return;
6534     } else if (strstr(message, "Draw") != NULL ||
6535                strstr(message, "game is a draw") != NULL) {
6536         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6537         return;
6538     } else if (strstr(message, "offer") != NULL &&
6539                strstr(message, "draw") != NULL) {
6540 #if ZIPPY
6541         if (appData.zippyPlay && first.initDone) {
6542             /* Relay offer to ICS */
6543             SendToICS(ics_prefix);
6544             SendToICS("draw\n");
6545         }
6546 #endif
6547         cps->offeredDraw = 2; /* valid until this engine moves twice */
6548         if (gameMode == TwoMachinesPlay) {
6549             if (cps->other->offeredDraw) {
6550                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6551             /* [HGM] in two-machine mode we delay relaying draw offer      */
6552             /* until after we also have move, to see if it is really claim */
6553             }
6554 #if 0
6555               else {
6556                 if (cps->other->sendDrawOffers) {
6557                     SendToProgram("draw\n", cps->other);
6558                 }
6559             }
6560 #endif
6561         } else if (gameMode == MachinePlaysWhite ||
6562                    gameMode == MachinePlaysBlack) {
6563           if (userOfferedDraw) {
6564             DisplayInformation(_("Machine accepts your draw offer"));
6565             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6566           } else {
6567             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6568           }
6569         }
6570     }
6571
6572     
6573     /*
6574      * Look for thinking output
6575      */
6576     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6577           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6578                                 ) {
6579         int plylev, mvleft, mvtot, curscore, time;
6580         char mvname[MOVE_LEN];
6581         u64 nodes; // [DM]
6582         char plyext;
6583         int ignore = FALSE;
6584         int prefixHint = FALSE;
6585         mvname[0] = NULLCHAR;
6586
6587         switch (gameMode) {
6588           case MachinePlaysBlack:
6589           case IcsPlayingBlack:
6590             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6591             break;
6592           case MachinePlaysWhite:
6593           case IcsPlayingWhite:
6594             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6595             break;
6596           case AnalyzeMode:
6597           case AnalyzeFile:
6598             break;
6599           case IcsObserving: /* [DM] icsEngineAnalyze */
6600             if (!appData.icsEngineAnalyze) ignore = TRUE;
6601             break;
6602           case TwoMachinesPlay:
6603             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6604                 ignore = TRUE;
6605             }
6606             break;
6607           default:
6608             ignore = TRUE;
6609             break;
6610         }
6611
6612         if (!ignore) {
6613             buf1[0] = NULLCHAR;
6614             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6615                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6616
6617                 if (plyext != ' ' && plyext != '\t') {
6618                     time *= 100;
6619                 }
6620
6621                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6622                 if( cps->scoreIsAbsolute && 
6623                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6624                 {
6625                     curscore = -curscore;
6626                 }
6627
6628
6629                 programStats.depth = plylev;
6630                 programStats.nodes = nodes;
6631                 programStats.time = time;
6632                 programStats.score = curscore;
6633                 programStats.got_only_move = 0;
6634
6635                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6636                         int ticklen;
6637
6638                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6639                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6640                         if(WhiteOnMove(forwardMostMove)) 
6641                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6642                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6643                 }
6644
6645                 /* Buffer overflow protection */
6646                 if (buf1[0] != NULLCHAR) {
6647                     if (strlen(buf1) >= sizeof(programStats.movelist)
6648                         && appData.debugMode) {
6649                         fprintf(debugFP,
6650                                 "PV is too long; using the first %d bytes.\n",
6651                                 sizeof(programStats.movelist) - 1);
6652                     }
6653
6654                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6655                 } else {
6656                     sprintf(programStats.movelist, " no PV\n");
6657                 }
6658
6659                 if (programStats.seen_stat) {
6660                     programStats.ok_to_send = 1;
6661                 }
6662
6663                 if (strchr(programStats.movelist, '(') != NULL) {
6664                     programStats.line_is_book = 1;
6665                     programStats.nr_moves = 0;
6666                     programStats.moves_left = 0;
6667                 } else {
6668                     programStats.line_is_book = 0;
6669                 }
6670
6671                 SendProgramStatsToFrontend( cps, &programStats );
6672
6673                 /* 
6674                     [AS] Protect the thinkOutput buffer from overflow... this
6675                     is only useful if buf1 hasn't overflowed first!
6676                 */
6677                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6678                         plylev, 
6679                         (gameMode == TwoMachinesPlay ?
6680                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6681                         ((double) curscore) / 100.0,
6682                         prefixHint ? lastHint : "",
6683                         prefixHint ? " " : "" );
6684
6685                 if( buf1[0] != NULLCHAR ) {
6686                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6687
6688                     if( strlen(buf1) > max_len ) {
6689                         if( appData.debugMode) {
6690                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6691                         }
6692                         buf1[max_len+1] = '\0';
6693                     }
6694
6695                     strcat( thinkOutput, buf1 );
6696                 }
6697
6698                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6699                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6700                     DisplayMove(currentMove - 1);
6701                     DisplayAnalysis();
6702                 }
6703                 return;
6704
6705             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6706                 /* crafty (9.25+) says "(only move) <move>"
6707                  * if there is only 1 legal move
6708                  */
6709                 sscanf(p, "(only move) %s", buf1);
6710                 sprintf(thinkOutput, "%s (only move)", buf1);
6711                 sprintf(programStats.movelist, "%s (only move)", buf1);
6712                 programStats.depth = 1;
6713                 programStats.nr_moves = 1;
6714                 programStats.moves_left = 1;
6715                 programStats.nodes = 1;
6716                 programStats.time = 1;
6717                 programStats.got_only_move = 1;
6718
6719                 /* Not really, but we also use this member to
6720                    mean "line isn't going to change" (Crafty
6721                    isn't searching, so stats won't change) */
6722                 programStats.line_is_book = 1;
6723
6724                 SendProgramStatsToFrontend( cps, &programStats );
6725                 
6726                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6727                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6728                     DisplayMove(currentMove - 1);
6729                     DisplayAnalysis();
6730                 }
6731                 return;
6732             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6733                               &time, &nodes, &plylev, &mvleft,
6734                               &mvtot, mvname) >= 5) {
6735                 /* The stat01: line is from Crafty (9.29+) in response
6736                    to the "." command */
6737                 programStats.seen_stat = 1;
6738                 cps->maybeThinking = TRUE;
6739
6740                 if (programStats.got_only_move || !appData.periodicUpdates)
6741                   return;
6742
6743                 programStats.depth = plylev;
6744                 programStats.time = time;
6745                 programStats.nodes = nodes;
6746                 programStats.moves_left = mvleft;
6747                 programStats.nr_moves = mvtot;
6748                 strcpy(programStats.move_name, mvname);
6749                 programStats.ok_to_send = 1;
6750                 programStats.movelist[0] = '\0';
6751
6752                 SendProgramStatsToFrontend( cps, &programStats );
6753
6754                 DisplayAnalysis();
6755                 return;
6756
6757             } else if (strncmp(message,"++",2) == 0) {
6758                 /* Crafty 9.29+ outputs this */
6759                 programStats.got_fail = 2;
6760                 return;
6761
6762             } else if (strncmp(message,"--",2) == 0) {
6763                 /* Crafty 9.29+ outputs this */
6764                 programStats.got_fail = 1;
6765                 return;
6766
6767             } else if (thinkOutput[0] != NULLCHAR &&
6768                        strncmp(message, "    ", 4) == 0) {
6769                 unsigned message_len;
6770
6771                 p = message;
6772                 while (*p && *p == ' ') p++;
6773
6774                 message_len = strlen( p );
6775
6776                 /* [AS] Avoid buffer overflow */
6777                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6778                     strcat(thinkOutput, " ");
6779                     strcat(thinkOutput, p);
6780                 }
6781
6782                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6783                     strcat(programStats.movelist, " ");
6784                     strcat(programStats.movelist, p);
6785                 }
6786
6787                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6788                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6789                     DisplayMove(currentMove - 1);
6790                     DisplayAnalysis();
6791                 }
6792                 return;
6793             }
6794         }
6795         else {
6796             buf1[0] = NULLCHAR;
6797
6798             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6799                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6800             {
6801                 ChessProgramStats cpstats;
6802
6803                 if (plyext != ' ' && plyext != '\t') {
6804                     time *= 100;
6805                 }
6806
6807                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6808                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6809                     curscore = -curscore;
6810                 }
6811
6812                 cpstats.depth = plylev;
6813                 cpstats.nodes = nodes;
6814                 cpstats.time = time;
6815                 cpstats.score = curscore;
6816                 cpstats.got_only_move = 0;
6817                 cpstats.movelist[0] = '\0';
6818
6819                 if (buf1[0] != NULLCHAR) {
6820                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6821                 }
6822
6823                 cpstats.ok_to_send = 0;
6824                 cpstats.line_is_book = 0;
6825                 cpstats.nr_moves = 0;
6826                 cpstats.moves_left = 0;
6827
6828                 SendProgramStatsToFrontend( cps, &cpstats );
6829             }
6830         }
6831     }
6832 }
6833
6834
6835 /* Parse a game score from the character string "game", and
6836    record it as the history of the current game.  The game
6837    score is NOT assumed to start from the standard position. 
6838    The display is not updated in any way.
6839    */
6840 void
6841 ParseGameHistory(game)
6842      char *game;
6843 {
6844     ChessMove moveType;
6845     int fromX, fromY, toX, toY, boardIndex;
6846     char promoChar;
6847     char *p, *q;
6848     char buf[MSG_SIZ];
6849
6850     if (appData.debugMode)
6851       fprintf(debugFP, "Parsing game history: %s\n", game);
6852
6853     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6854     gameInfo.site = StrSave(appData.icsHost);
6855     gameInfo.date = PGNDate();
6856     gameInfo.round = StrSave("-");
6857
6858     /* Parse out names of players */
6859     while (*game == ' ') game++;
6860     p = buf;
6861     while (*game != ' ') *p++ = *game++;
6862     *p = NULLCHAR;
6863     gameInfo.white = StrSave(buf);
6864     while (*game == ' ') game++;
6865     p = buf;
6866     while (*game != ' ' && *game != '\n') *p++ = *game++;
6867     *p = NULLCHAR;
6868     gameInfo.black = StrSave(buf);
6869
6870     /* Parse moves */
6871     boardIndex = blackPlaysFirst ? 1 : 0;
6872     yynewstr(game);
6873     for (;;) {
6874         yyboardindex = boardIndex;
6875         moveType = (ChessMove) yylex();
6876         switch (moveType) {
6877           case IllegalMove:             /* maybe suicide chess, etc. */
6878   if (appData.debugMode) {
6879     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6880     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6881     setbuf(debugFP, NULL);
6882   }
6883           case WhitePromotionChancellor:
6884           case BlackPromotionChancellor:
6885           case WhitePromotionArchbishop:
6886           case BlackPromotionArchbishop:
6887           case WhitePromotionQueen:
6888           case BlackPromotionQueen:
6889           case WhitePromotionRook:
6890           case BlackPromotionRook:
6891           case WhitePromotionBishop:
6892           case BlackPromotionBishop:
6893           case WhitePromotionKnight:
6894           case BlackPromotionKnight:
6895           case WhitePromotionKing:
6896           case BlackPromotionKing:
6897           case NormalMove:
6898           case WhiteCapturesEnPassant:
6899           case BlackCapturesEnPassant:
6900           case WhiteKingSideCastle:
6901           case WhiteQueenSideCastle:
6902           case BlackKingSideCastle:
6903           case BlackQueenSideCastle:
6904           case WhiteKingSideCastleWild:
6905           case WhiteQueenSideCastleWild:
6906           case BlackKingSideCastleWild:
6907           case BlackQueenSideCastleWild:
6908           /* PUSH Fabien */
6909           case WhiteHSideCastleFR:
6910           case WhiteASideCastleFR:
6911           case BlackHSideCastleFR:
6912           case BlackASideCastleFR:
6913           /* POP Fabien */
6914             fromX = currentMoveString[0] - AAA;
6915             fromY = currentMoveString[1] - ONE;
6916             toX = currentMoveString[2] - AAA;
6917             toY = currentMoveString[3] - ONE;
6918             promoChar = currentMoveString[4];
6919             break;
6920           case WhiteDrop:
6921           case BlackDrop:
6922             fromX = moveType == WhiteDrop ?
6923               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6924             (int) CharToPiece(ToLower(currentMoveString[0]));
6925             fromY = DROP_RANK;
6926             toX = currentMoveString[2] - AAA;
6927             toY = currentMoveString[3] - ONE;
6928             promoChar = NULLCHAR;
6929             break;
6930           case AmbiguousMove:
6931             /* bug? */
6932             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6933   if (appData.debugMode) {
6934     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6935     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6936     setbuf(debugFP, NULL);
6937   }
6938             DisplayError(buf, 0);
6939             return;
6940           case ImpossibleMove:
6941             /* bug? */
6942             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6943   if (appData.debugMode) {
6944     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6946     setbuf(debugFP, NULL);
6947   }
6948             DisplayError(buf, 0);
6949             return;
6950           case (ChessMove) 0:   /* end of file */
6951             if (boardIndex < backwardMostMove) {
6952                 /* Oops, gap.  How did that happen? */
6953                 DisplayError(_("Gap in move list"), 0);
6954                 return;
6955             }
6956             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6957             if (boardIndex > forwardMostMove) {
6958                 forwardMostMove = boardIndex;
6959             }
6960             return;
6961           case ElapsedTime:
6962             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6963                 strcat(parseList[boardIndex-1], " ");
6964                 strcat(parseList[boardIndex-1], yy_text);
6965             }
6966             continue;
6967           case Comment:
6968           case PGNTag:
6969           case NAG:
6970           default:
6971             /* ignore */
6972             continue;
6973           case WhiteWins:
6974           case BlackWins:
6975           case GameIsDrawn:
6976           case GameUnfinished:
6977             if (gameMode == IcsExamining) {
6978                 if (boardIndex < backwardMostMove) {
6979                     /* Oops, gap.  How did that happen? */
6980                     return;
6981                 }
6982                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6983                 return;
6984             }
6985             gameInfo.result = moveType;
6986             p = strchr(yy_text, '{');
6987             if (p == NULL) p = strchr(yy_text, '(');
6988             if (p == NULL) {
6989                 p = yy_text;
6990                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6991             } else {
6992                 q = strchr(p, *p == '{' ? '}' : ')');
6993                 if (q != NULL) *q = NULLCHAR;
6994                 p++;
6995             }
6996             gameInfo.resultDetails = StrSave(p);
6997             continue;
6998         }
6999         if (boardIndex >= forwardMostMove &&
7000             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7001             backwardMostMove = blackPlaysFirst ? 1 : 0;
7002             return;
7003         }
7004         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7005                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7006                                  parseList[boardIndex]);
7007         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7008         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7009         /* currentMoveString is set as a side-effect of yylex */
7010         strcpy(moveList[boardIndex], currentMoveString);
7011         strcat(moveList[boardIndex], "\n");
7012         boardIndex++;
7013         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7014                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7015         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7016                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7017           case MT_NONE:
7018           case MT_STALEMATE:
7019           default:
7020             break;
7021           case MT_CHECK:
7022             if(gameInfo.variant != VariantShogi)
7023                 strcat(parseList[boardIndex - 1], "+");
7024             break;
7025           case MT_CHECKMATE:
7026           case MT_STAINMATE:
7027             strcat(parseList[boardIndex - 1], "#");
7028             break;
7029         }
7030     }
7031 }
7032
7033
7034 /* Apply a move to the given board  */
7035 void
7036 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7037      int fromX, fromY, toX, toY;
7038      int promoChar;
7039      Board board;
7040      char *castling;
7041      char *ep;
7042 {
7043   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7044
7045     /* [HGM] compute & store e.p. status and castling rights for new position */
7046     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7047     { int i;
7048
7049       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7050       oldEP = *ep;
7051       *ep = EP_NONE;
7052
7053       if( board[toY][toX] != EmptySquare ) 
7054            *ep = EP_CAPTURE;  
7055
7056       if( board[fromY][fromX] == WhitePawn ) {
7057            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7058                *ep = EP_PAWN_MOVE;
7059            if( toY-fromY==2) {
7060                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7061                         gameInfo.variant != VariantBerolina || toX < fromX)
7062                       *ep = toX | berolina;
7063                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7064                         gameInfo.variant != VariantBerolina || toX > fromX) 
7065                       *ep = toX;
7066            }
7067       } else 
7068       if( board[fromY][fromX] == BlackPawn ) {
7069            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7070                *ep = EP_PAWN_MOVE; 
7071            if( toY-fromY== -2) {
7072                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7073                         gameInfo.variant != VariantBerolina || toX < fromX)
7074                       *ep = toX | berolina;
7075                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7076                         gameInfo.variant != VariantBerolina || toX > fromX) 
7077                       *ep = toX;
7078            }
7079        }
7080
7081        for(i=0; i<nrCastlingRights; i++) {
7082            if(castling[i] == fromX && castlingRank[i] == fromY ||
7083               castling[i] == toX   && castlingRank[i] == toY   
7084              ) castling[i] = -1; // revoke for moved or captured piece
7085        }
7086
7087     }
7088
7089   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7090   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7091        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7092          
7093   if (fromX == toX && fromY == toY) return;
7094
7095   if (fromY == DROP_RANK) {
7096         /* must be first */
7097         piece = board[toY][toX] = (ChessSquare) fromX;
7098   } else {
7099      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7100      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7101      if(gameInfo.variant == VariantKnightmate)
7102          king += (int) WhiteUnicorn - (int) WhiteKing;
7103
7104     /* Code added by Tord: */
7105     /* FRC castling assumed when king captures friendly rook. */
7106     if (board[fromY][fromX] == WhiteKing &&
7107              board[toY][toX] == WhiteRook) {
7108       board[fromY][fromX] = EmptySquare;
7109       board[toY][toX] = EmptySquare;
7110       if(toX > fromX) {
7111         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7112       } else {
7113         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7114       }
7115     } else if (board[fromY][fromX] == BlackKing &&
7116                board[toY][toX] == BlackRook) {
7117       board[fromY][fromX] = EmptySquare;
7118       board[toY][toX] = EmptySquare;
7119       if(toX > fromX) {
7120         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7121       } else {
7122         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7123       }
7124     /* End of code added by Tord */
7125
7126     } else if (board[fromY][fromX] == king
7127         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7128         && toY == fromY && toX > fromX+1) {
7129         board[fromY][fromX] = EmptySquare;
7130         board[toY][toX] = king;
7131         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7132         board[fromY][BOARD_RGHT-1] = EmptySquare;
7133     } else if (board[fromY][fromX] == king
7134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7135                && toY == fromY && toX < fromX-1) {
7136         board[fromY][fromX] = EmptySquare;
7137         board[toY][toX] = king;
7138         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7139         board[fromY][BOARD_LEFT] = EmptySquare;
7140     } else if (board[fromY][fromX] == WhitePawn
7141                && toY == BOARD_HEIGHT-1
7142                && gameInfo.variant != VariantXiangqi
7143                ) {
7144         /* white pawn promotion */
7145         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7146         if (board[toY][toX] == EmptySquare) {
7147             board[toY][toX] = WhiteQueen;
7148         }
7149         if(gameInfo.variant==VariantBughouse ||
7150            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7151             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7152         board[fromY][fromX] = EmptySquare;
7153     } else if ((fromY == BOARD_HEIGHT-4)
7154                && (toX != fromX)
7155                && gameInfo.variant != VariantXiangqi
7156                && gameInfo.variant != VariantBerolina
7157                && (board[fromY][fromX] == WhitePawn)
7158                && (board[toY][toX] == EmptySquare)) {
7159         board[fromY][fromX] = EmptySquare;
7160         board[toY][toX] = WhitePawn;
7161         captured = board[toY - 1][toX];
7162         board[toY - 1][toX] = EmptySquare;
7163     } else if ((fromY == BOARD_HEIGHT-4)
7164                && (toX == fromX)
7165                && gameInfo.variant == VariantBerolina
7166                && (board[fromY][fromX] == WhitePawn)
7167                && (board[toY][toX] == EmptySquare)) {
7168         board[fromY][fromX] = EmptySquare;
7169         board[toY][toX] = WhitePawn;
7170         if(oldEP & EP_BEROLIN_A) {
7171                 captured = board[fromY][fromX-1];
7172                 board[fromY][fromX-1] = EmptySquare;
7173         }else{  captured = board[fromY][fromX+1];
7174                 board[fromY][fromX+1] = EmptySquare;
7175         }
7176     } else if (board[fromY][fromX] == king
7177         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7178                && toY == fromY && toX > fromX+1) {
7179         board[fromY][fromX] = EmptySquare;
7180         board[toY][toX] = king;
7181         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7182         board[fromY][BOARD_RGHT-1] = EmptySquare;
7183     } else if (board[fromY][fromX] == king
7184         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7185                && toY == fromY && toX < fromX-1) {
7186         board[fromY][fromX] = EmptySquare;
7187         board[toY][toX] = king;
7188         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7189         board[fromY][BOARD_LEFT] = EmptySquare;
7190     } else if (fromY == 7 && fromX == 3
7191                && board[fromY][fromX] == BlackKing
7192                && toY == 7 && toX == 5) {
7193         board[fromY][fromX] = EmptySquare;
7194         board[toY][toX] = BlackKing;
7195         board[fromY][7] = EmptySquare;
7196         board[toY][4] = BlackRook;
7197     } else if (fromY == 7 && fromX == 3
7198                && board[fromY][fromX] == BlackKing
7199                && toY == 7 && toX == 1) {
7200         board[fromY][fromX] = EmptySquare;
7201         board[toY][toX] = BlackKing;
7202         board[fromY][0] = EmptySquare;
7203         board[toY][2] = BlackRook;
7204     } else if (board[fromY][fromX] == BlackPawn
7205                && toY == 0
7206                && gameInfo.variant != VariantXiangqi
7207                ) {
7208         /* black pawn promotion */
7209         board[0][toX] = CharToPiece(ToLower(promoChar));
7210         if (board[0][toX] == EmptySquare) {
7211             board[0][toX] = BlackQueen;
7212         }
7213         if(gameInfo.variant==VariantBughouse ||
7214            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7215             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7216         board[fromY][fromX] = EmptySquare;
7217     } else if ((fromY == 3)
7218                && (toX != fromX)
7219                && gameInfo.variant != VariantXiangqi
7220                && gameInfo.variant != VariantBerolina
7221                && (board[fromY][fromX] == BlackPawn)
7222                && (board[toY][toX] == EmptySquare)) {
7223         board[fromY][fromX] = EmptySquare;
7224         board[toY][toX] = BlackPawn;
7225         captured = board[toY + 1][toX];
7226         board[toY + 1][toX] = EmptySquare;
7227     } else if ((fromY == 3)
7228                && (toX == fromX)
7229                && gameInfo.variant == VariantBerolina
7230                && (board[fromY][fromX] == BlackPawn)
7231                && (board[toY][toX] == EmptySquare)) {
7232         board[fromY][fromX] = EmptySquare;
7233         board[toY][toX] = BlackPawn;
7234         if(oldEP & EP_BEROLIN_A) {
7235                 captured = board[fromY][fromX-1];
7236                 board[fromY][fromX-1] = EmptySquare;
7237         }else{  captured = board[fromY][fromX+1];
7238                 board[fromY][fromX+1] = EmptySquare;
7239         }
7240     } else {
7241         board[toY][toX] = board[fromY][fromX];
7242         board[fromY][fromX] = EmptySquare;
7243     }
7244
7245     /* [HGM] now we promote for Shogi, if needed */
7246     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7247         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7248   }
7249
7250     if (gameInfo.holdingsWidth != 0) {
7251
7252       /* !!A lot more code needs to be written to support holdings  */
7253       /* [HGM] OK, so I have written it. Holdings are stored in the */
7254       /* penultimate board files, so they are automaticlly stored   */
7255       /* in the game history.                                       */
7256       if (fromY == DROP_RANK) {
7257         /* Delete from holdings, by decreasing count */
7258         /* and erasing image if necessary            */
7259         p = (int) fromX;
7260         if(p < (int) BlackPawn) { /* white drop */
7261              p -= (int)WhitePawn;
7262              if(p >= gameInfo.holdingsSize) p = 0;
7263              if(--board[p][BOARD_WIDTH-2] == 0)
7264                   board[p][BOARD_WIDTH-1] = EmptySquare;
7265         } else {                  /* black drop */
7266              p -= (int)BlackPawn;
7267              if(p >= gameInfo.holdingsSize) p = 0;
7268              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7269                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7270         }
7271       }
7272       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7273           && gameInfo.variant != VariantBughouse        ) {
7274         /* [HGM] holdings: Add to holdings, if holdings exist */
7275         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7276                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7277                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7278         }
7279         p = (int) captured;
7280         if (p >= (int) BlackPawn) {
7281           p -= (int)BlackPawn;
7282           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7283                   /* in Shogi restore piece to its original  first */
7284                   captured = (ChessSquare) (DEMOTED captured);
7285                   p = DEMOTED p;
7286           }
7287           p = PieceToNumber((ChessSquare)p);
7288           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7289           board[p][BOARD_WIDTH-2]++;
7290           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7291         } else {
7292           p -= (int)WhitePawn;
7293           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7294                   captured = (ChessSquare) (DEMOTED captured);
7295                   p = DEMOTED p;
7296           }
7297           p = PieceToNumber((ChessSquare)p);
7298           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7299           board[BOARD_HEIGHT-1-p][1]++;
7300           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7301         }
7302       }
7303
7304     } else if (gameInfo.variant == VariantAtomic) {
7305       if (captured != EmptySquare) {
7306         int y, x;
7307         for (y = toY-1; y <= toY+1; y++) {
7308           for (x = toX-1; x <= toX+1; x++) {
7309             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7310                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7311               board[y][x] = EmptySquare;
7312             }
7313           }
7314         }
7315         board[toY][toX] = EmptySquare;
7316       }
7317     }
7318     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7319         /* [HGM] Shogi promotions */
7320         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7321     }
7322
7323     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7324                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7325         // [HGM] superchess: take promotion piece out of holdings
7326         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7327         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7328             if(!--board[k][BOARD_WIDTH-2])
7329                 board[k][BOARD_WIDTH-1] = EmptySquare;
7330         } else {
7331             if(!--board[BOARD_HEIGHT-1-k][1])
7332                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7333         }
7334     }
7335
7336 }
7337
7338 /* Updates forwardMostMove */
7339 void
7340 MakeMove(fromX, fromY, toX, toY, promoChar)
7341      int fromX, fromY, toX, toY;
7342      int promoChar;
7343 {
7344 //    forwardMostMove++; // [HGM] bare: moved downstream
7345
7346     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7347         int timeLeft; static int lastLoadFlag=0; int king, piece;
7348         piece = boards[forwardMostMove][fromY][fromX];
7349         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7350         if(gameInfo.variant == VariantKnightmate)
7351             king += (int) WhiteUnicorn - (int) WhiteKing;
7352         if(forwardMostMove == 0) {
7353             if(blackPlaysFirst) 
7354                 fprintf(serverMoves, "%s;", second.tidy);
7355             fprintf(serverMoves, "%s;", first.tidy);
7356             if(!blackPlaysFirst) 
7357                 fprintf(serverMoves, "%s;", second.tidy);
7358         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7359         lastLoadFlag = loadFlag;
7360         // print base move
7361         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7362         // print castling suffix
7363         if( toY == fromY && piece == king ) {
7364             if(toX-fromX > 1)
7365                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7366             if(fromX-toX >1)
7367                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7368         }
7369         // e.p. suffix
7370         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7371              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7372              boards[forwardMostMove][toY][toX] == EmptySquare
7373              && fromX != toX )
7374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7375         // promotion suffix
7376         if(promoChar != NULLCHAR)
7377                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7378         if(!loadFlag) {
7379             fprintf(serverMoves, "/%d/%d",
7380                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7381             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7382             else                      timeLeft = blackTimeRemaining/1000;
7383             fprintf(serverMoves, "/%d", timeLeft);
7384         }
7385         fflush(serverMoves);
7386     }
7387
7388     if (forwardMostMove+1 >= MAX_MOVES) {
7389       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7390                         0, 1);
7391       return;
7392     }
7393     SwitchClocks();
7394     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7395     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7396     if (commentList[forwardMostMove+1] != NULL) {
7397         free(commentList[forwardMostMove+1]);
7398         commentList[forwardMostMove+1] = NULL;
7399     }
7400     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7401     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7402     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7403                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7404     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7405     gameInfo.result = GameUnfinished;
7406     if (gameInfo.resultDetails != NULL) {
7407         free(gameInfo.resultDetails);
7408         gameInfo.resultDetails = NULL;
7409     }
7410     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7411                               moveList[forwardMostMove - 1]);
7412     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7413                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7414                              fromY, fromX, toY, toX, promoChar,
7415                              parseList[forwardMostMove - 1]);
7416     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7417                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7418                             castlingRights[forwardMostMove]) ) {
7419       case MT_NONE:
7420       case MT_STALEMATE:
7421       default:
7422         break;
7423       case MT_CHECK:
7424         if(gameInfo.variant != VariantShogi)
7425             strcat(parseList[forwardMostMove - 1], "+");
7426         break;
7427       case MT_CHECKMATE:
7428       case MT_STAINMATE:
7429         strcat(parseList[forwardMostMove - 1], "#");
7430         break;
7431     }
7432     if (appData.debugMode) {
7433         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7434     }
7435
7436 }
7437
7438 /* Updates currentMove if not pausing */
7439 void
7440 ShowMove(fromX, fromY, toX, toY)
7441 {
7442     int instant = (gameMode == PlayFromGameFile) ?
7443         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7444     if(appData.noGUI) return;
7445     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7446         if (!instant) {
7447             if (forwardMostMove == currentMove + 1) {
7448                 AnimateMove(boards[forwardMostMove - 1],
7449                             fromX, fromY, toX, toY);
7450             }
7451             if (appData.highlightLastMove) {
7452                 SetHighlights(fromX, fromY, toX, toY);
7453             }
7454         }
7455         currentMove = forwardMostMove;
7456     }
7457
7458     if (instant) return;
7459
7460     DisplayMove(currentMove - 1);
7461     DrawPosition(FALSE, boards[currentMove]);
7462     DisplayBothClocks();
7463     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7464 }
7465
7466 void SendEgtPath(ChessProgramState *cps)
7467 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7468         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7469
7470         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7471
7472         while(*p) {
7473             char c, *q = name+1, *r, *s;
7474
7475             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7476             while(*p && *p != ',') *q++ = *p++;
7477             *q++ = ':'; *q = 0;
7478             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7479                 strcmp(name, ",nalimov:") == 0 ) {
7480                 // take nalimov path from the menu-changeable option first, if it is defined
7481                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7482                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7483             } else
7484             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7485                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7486                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7487                 s = r = StrStr(s, ":") + 1; // beginning of path info
7488                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7489                 c = *r; *r = 0;             // temporarily null-terminate path info
7490                     *--q = 0;               // strip of trailig ':' from name
7491                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7492                 *r = c;
7493                 SendToProgram(buf,cps);     // send egtbpath command for this format
7494             }
7495             if(*p == ',') p++; // read away comma to position for next format name
7496         }
7497 }
7498
7499 void
7500 InitChessProgram(cps, setup)
7501      ChessProgramState *cps;
7502      int setup; /* [HGM] needed to setup FRC opening position */
7503 {
7504     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7505     if (appData.noChessProgram) return;
7506     hintRequested = FALSE;
7507     bookRequested = FALSE;
7508
7509     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7510     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7511     if(cps->memSize) { /* [HGM] memory */
7512         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7513         SendToProgram(buf, cps);
7514     }
7515     SendEgtPath(cps); /* [HGM] EGT */
7516     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7517         sprintf(buf, "cores %d\n", appData.smpCores);
7518         SendToProgram(buf, cps);
7519     }
7520
7521     SendToProgram(cps->initString, cps);
7522     if (gameInfo.variant != VariantNormal &&
7523         gameInfo.variant != VariantLoadable
7524         /* [HGM] also send variant if board size non-standard */
7525         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7526                                             ) {
7527       char *v = VariantName(gameInfo.variant);
7528       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7529         /* [HGM] in protocol 1 we have to assume all variants valid */
7530         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7531         DisplayFatalError(buf, 0, 1);
7532         return;
7533       }
7534
7535       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7536       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7537       if( gameInfo.variant == VariantXiangqi )
7538            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7539       if( gameInfo.variant == VariantShogi )
7540            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7541       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7542            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7543       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7544                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7545            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7546       if( gameInfo.variant == VariantCourier )
7547            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7548       if( gameInfo.variant == VariantSuper )
7549            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7550       if( gameInfo.variant == VariantGreat )
7551            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7552
7553       if(overruled) {
7554            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7555                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7556            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7557            if(StrStr(cps->variants, b) == NULL) { 
7558                // specific sized variant not known, check if general sizing allowed
7559                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7560                    if(StrStr(cps->variants, "boardsize") == NULL) {
7561                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7562                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7563                        DisplayFatalError(buf, 0, 1);
7564                        return;
7565                    }
7566                    /* [HGM] here we really should compare with the maximum supported board size */
7567                }
7568            }
7569       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7570       sprintf(buf, "variant %s\n", b);
7571       SendToProgram(buf, cps);
7572     }
7573     currentlyInitializedVariant = gameInfo.variant;
7574
7575     /* [HGM] send opening position in FRC to first engine */
7576     if(setup) {
7577           SendToProgram("force\n", cps);
7578           SendBoard(cps, 0);
7579           /* engine is now in force mode! Set flag to wake it up after first move. */
7580           setboardSpoiledMachineBlack = 1;
7581     }
7582
7583     if (cps->sendICS) {
7584       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7585       SendToProgram(buf, cps);
7586     }
7587     cps->maybeThinking = FALSE;
7588     cps->offeredDraw = 0;
7589     if (!appData.icsActive) {
7590         SendTimeControl(cps, movesPerSession, timeControl,
7591                         timeIncrement, appData.searchDepth,
7592                         searchTime);
7593     }
7594     if (appData.showThinking 
7595         // [HGM] thinking: four options require thinking output to be sent
7596         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7597                                 ) {
7598         SendToProgram("post\n", cps);
7599     }
7600     SendToProgram("hard\n", cps);
7601     if (!appData.ponderNextMove) {
7602         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7603            it without being sure what state we are in first.  "hard"
7604            is not a toggle, so that one is OK.
7605          */
7606         SendToProgram("easy\n", cps);
7607     }
7608     if (cps->usePing) {
7609       sprintf(buf, "ping %d\n", ++cps->lastPing);
7610       SendToProgram(buf, cps);
7611     }
7612     cps->initDone = TRUE;
7613 }   
7614
7615
7616 void
7617 StartChessProgram(cps)
7618      ChessProgramState *cps;
7619 {
7620     char buf[MSG_SIZ];
7621     int err;
7622
7623     if (appData.noChessProgram) return;
7624     cps->initDone = FALSE;
7625
7626     if (strcmp(cps->host, "localhost") == 0) {
7627         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7628     } else if (*appData.remoteShell == NULLCHAR) {
7629         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7630     } else {
7631         if (*appData.remoteUser == NULLCHAR) {
7632           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7633                     cps->program);
7634         } else {
7635           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7636                     cps->host, appData.remoteUser, cps->program);
7637         }
7638         err = StartChildProcess(buf, "", &cps->pr);
7639     }
7640     
7641     if (err != 0) {
7642         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7643         DisplayFatalError(buf, err, 1);
7644         cps->pr = NoProc;
7645         cps->isr = NULL;
7646         return;
7647     }
7648     
7649     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7650     if (cps->protocolVersion > 1) {
7651       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7652       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7653       cps->comboCnt = 0;  //                and values of combo boxes
7654       SendToProgram(buf, cps);
7655     } else {
7656       SendToProgram("xboard\n", cps);
7657     }
7658 }
7659
7660
7661 void
7662 TwoMachinesEventIfReady P((void))
7663 {
7664   if (first.lastPing != first.lastPong) {
7665     DisplayMessage("", _("Waiting for first chess program"));
7666     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7667     return;
7668   }
7669   if (second.lastPing != second.lastPong) {
7670     DisplayMessage("", _("Waiting for second chess program"));
7671     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7672     return;
7673   }
7674   ThawUI();
7675   TwoMachinesEvent();
7676 }
7677
7678 void
7679 NextMatchGame P((void))
7680 {
7681     int index; /* [HGM] autoinc: step lod index during match */
7682     Reset(FALSE, TRUE);
7683     if (*appData.loadGameFile != NULLCHAR) {
7684         index = appData.loadGameIndex;
7685         if(index < 0) { // [HGM] autoinc
7686             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7687             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7688         } 
7689         LoadGameFromFile(appData.loadGameFile,
7690                          index,
7691                          appData.loadGameFile, FALSE);
7692     } else if (*appData.loadPositionFile != NULLCHAR) {
7693         index = appData.loadPositionIndex;
7694         if(index < 0) { // [HGM] autoinc
7695             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7696             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7697         } 
7698         LoadPositionFromFile(appData.loadPositionFile,
7699                              index,
7700                              appData.loadPositionFile);
7701     }
7702     TwoMachinesEventIfReady();
7703 }
7704
7705 void UserAdjudicationEvent( int result )
7706 {
7707     ChessMove gameResult = GameIsDrawn;
7708
7709     if( result > 0 ) {
7710         gameResult = WhiteWins;
7711     }
7712     else if( result < 0 ) {
7713         gameResult = BlackWins;
7714     }
7715
7716     if( gameMode == TwoMachinesPlay ) {
7717         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7718     }
7719 }
7720
7721
7722 // [HGM] save: calculate checksum of game to make games easily identifiable
7723 int StringCheckSum(char *s)
7724 {
7725         int i = 0;
7726         if(s==NULL) return 0;
7727         while(*s) i = i*259 + *s++;
7728         return i;
7729 }
7730
7731 int GameCheckSum()
7732 {
7733         int i, sum=0;
7734         for(i=backwardMostMove; i<forwardMostMove; i++) {
7735                 sum += pvInfoList[i].depth;
7736                 sum += StringCheckSum(parseList[i]);
7737                 sum += StringCheckSum(commentList[i]);
7738                 sum *= 261;
7739         }
7740         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7741         return sum + StringCheckSum(commentList[i]);
7742 } // end of save patch
7743
7744 void
7745 GameEnds(result, resultDetails, whosays)
7746      ChessMove result;
7747      char *resultDetails;
7748      int whosays;
7749 {
7750     GameMode nextGameMode;
7751     int isIcsGame;
7752     char buf[MSG_SIZ];
7753
7754     if(endingGame) return; /* [HGM] crash: forbid recursion */
7755     endingGame = 1;
7756
7757     if (appData.debugMode) {
7758       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7759               result, resultDetails ? resultDetails : "(null)", whosays);
7760     }
7761
7762     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7763         /* If we are playing on ICS, the server decides when the
7764            game is over, but the engine can offer to draw, claim 
7765            a draw, or resign. 
7766          */
7767 #if ZIPPY
7768         if (appData.zippyPlay && first.initDone) {
7769             if (result == GameIsDrawn) {
7770                 /* In case draw still needs to be claimed */
7771                 SendToICS(ics_prefix);
7772                 SendToICS("draw\n");
7773             } else if (StrCaseStr(resultDetails, "resign")) {
7774                 SendToICS(ics_prefix);
7775                 SendToICS("resign\n");
7776             }
7777         }
7778 #endif
7779         endingGame = 0; /* [HGM] crash */
7780         return;
7781     }
7782
7783     /* If we're loading the game from a file, stop */
7784     if (whosays == GE_FILE) {
7785       (void) StopLoadGameTimer();
7786       gameFileFP = NULL;
7787     }
7788
7789     /* Cancel draw offers */
7790     first.offeredDraw = second.offeredDraw = 0;
7791
7792     /* If this is an ICS game, only ICS can really say it's done;
7793        if not, anyone can. */
7794     isIcsGame = (gameMode == IcsPlayingWhite || 
7795                  gameMode == IcsPlayingBlack || 
7796                  gameMode == IcsObserving    || 
7797                  gameMode == IcsExamining);
7798
7799     if (!isIcsGame || whosays == GE_ICS) {
7800         /* OK -- not an ICS game, or ICS said it was done */
7801         StopClocks();
7802         if (!isIcsGame && !appData.noChessProgram) 
7803           SetUserThinkingEnables();
7804     
7805         /* [HGM] if a machine claims the game end we verify this claim */
7806         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7807             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7808                 char claimer;
7809                 ChessMove trueResult = (ChessMove) -1;
7810
7811                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7812                                             first.twoMachinesColor[0] :
7813                                             second.twoMachinesColor[0] ;
7814
7815                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7816                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7817                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7818                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7819                 } else
7820                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7821                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7822                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7823                 } else
7824                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7825                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7826                 }
7827
7828                 // now verify win claims, but not in drop games, as we don't understand those yet
7829                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7830                                                  || gameInfo.variant == VariantGreat) &&
7831                     (result == WhiteWins && claimer == 'w' ||
7832                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7833                       if (appData.debugMode) {
7834                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7835                                 result, epStatus[forwardMostMove], forwardMostMove);
7836                       }
7837                       if(result != trueResult) {
7838                               sprintf(buf, "False win claim: '%s'", resultDetails);
7839                               result = claimer == 'w' ? BlackWins : WhiteWins;
7840                               resultDetails = buf;
7841                       }
7842                 } else
7843                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7844                     && (forwardMostMove <= backwardMostMove ||
7845                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7846                         (claimer=='b')==(forwardMostMove&1))
7847                                                                                   ) {
7848                       /* [HGM] verify: draws that were not flagged are false claims */
7849                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7850                       result = claimer == 'w' ? BlackWins : WhiteWins;
7851                       resultDetails = buf;
7852                 }
7853                 /* (Claiming a loss is accepted no questions asked!) */
7854             }
7855             /* [HGM] bare: don't allow bare King to win */
7856             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7857                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7858                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7859                && result != GameIsDrawn)
7860             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7861                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7862                         int p = (int)boards[forwardMostMove][i][j] - color;
7863                         if(p >= 0 && p <= (int)WhiteKing) k++;
7864                 }
7865                 if (appData.debugMode) {
7866                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7867                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7868                 }
7869                 if(k <= 1) {
7870                         result = GameIsDrawn;
7871                         sprintf(buf, "%s but bare king", resultDetails);
7872                         resultDetails = buf;
7873                 }
7874             }
7875         }
7876
7877
7878         if(serverMoves != NULL && !loadFlag) { char c = '=';
7879             if(result==WhiteWins) c = '+';
7880             if(result==BlackWins) c = '-';
7881             if(resultDetails != NULL)
7882                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7883         }
7884         if (resultDetails != NULL) {
7885             gameInfo.result = result;
7886             gameInfo.resultDetails = StrSave(resultDetails);
7887
7888             /* display last move only if game was not loaded from file */
7889             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7890                 DisplayMove(currentMove - 1);
7891     
7892             if (forwardMostMove != 0) {
7893                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7894                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7895                                                                 ) {
7896                     if (*appData.saveGameFile != NULLCHAR) {
7897                         SaveGameToFile(appData.saveGameFile, TRUE);
7898                     } else if (appData.autoSaveGames) {
7899                         AutoSaveGame();
7900                     }
7901                     if (*appData.savePositionFile != NULLCHAR) {
7902                         SavePositionToFile(appData.savePositionFile);
7903                     }
7904                 }
7905             }
7906
7907             /* Tell program how game ended in case it is learning */
7908             /* [HGM] Moved this to after saving the PGN, just in case */
7909             /* engine died and we got here through time loss. In that */
7910             /* case we will get a fatal error writing the pipe, which */
7911             /* would otherwise lose us the PGN.                       */
7912             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7913             /* output during GameEnds should never be fatal anymore   */
7914             if (gameMode == MachinePlaysWhite ||
7915                 gameMode == MachinePlaysBlack ||
7916                 gameMode == TwoMachinesPlay ||
7917                 gameMode == IcsPlayingWhite ||
7918                 gameMode == IcsPlayingBlack ||
7919                 gameMode == BeginningOfGame) {
7920                 char buf[MSG_SIZ];
7921                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7922                         resultDetails);
7923                 if (first.pr != NoProc) {
7924                     SendToProgram(buf, &first);
7925                 }
7926                 if (second.pr != NoProc &&
7927                     gameMode == TwoMachinesPlay) {
7928                     SendToProgram(buf, &second);
7929                 }
7930             }
7931         }
7932
7933         if (appData.icsActive) {
7934             if (appData.quietPlay &&
7935                 (gameMode == IcsPlayingWhite ||
7936                  gameMode == IcsPlayingBlack)) {
7937                 SendToICS(ics_prefix);
7938                 SendToICS("set shout 1\n");
7939             }
7940             nextGameMode = IcsIdle;
7941             ics_user_moved = FALSE;
7942             /* clean up premove.  It's ugly when the game has ended and the
7943              * premove highlights are still on the board.
7944              */
7945             if (gotPremove) {
7946               gotPremove = FALSE;
7947               ClearPremoveHighlights();
7948               DrawPosition(FALSE, boards[currentMove]);
7949             }
7950             if (whosays == GE_ICS) {
7951                 switch (result) {
7952                 case WhiteWins:
7953                     if (gameMode == IcsPlayingWhite)
7954                         PlayIcsWinSound();
7955                     else if(gameMode == IcsPlayingBlack)
7956                         PlayIcsLossSound();
7957                     break;
7958                 case BlackWins:
7959                     if (gameMode == IcsPlayingBlack)
7960                         PlayIcsWinSound();
7961                     else if(gameMode == IcsPlayingWhite)
7962                         PlayIcsLossSound();
7963                     break;
7964                 case GameIsDrawn:
7965                     PlayIcsDrawSound();
7966                     break;
7967                 default:
7968                     PlayIcsUnfinishedSound();
7969                 }
7970             }
7971         } else if (gameMode == EditGame ||
7972                    gameMode == PlayFromGameFile || 
7973                    gameMode == AnalyzeMode || 
7974                    gameMode == AnalyzeFile) {
7975             nextGameMode = gameMode;
7976         } else {
7977             nextGameMode = EndOfGame;
7978         }
7979         pausing = FALSE;
7980         ModeHighlight();
7981     } else {
7982         nextGameMode = gameMode;
7983     }
7984
7985     if (appData.noChessProgram) {
7986         gameMode = nextGameMode;
7987         ModeHighlight();
7988         endingGame = 0; /* [HGM] crash */
7989         return;
7990     }
7991
7992     if (first.reuse) {
7993         /* Put first chess program into idle state */
7994         if (first.pr != NoProc &&
7995             (gameMode == MachinePlaysWhite ||
7996              gameMode == MachinePlaysBlack ||
7997              gameMode == TwoMachinesPlay ||
7998              gameMode == IcsPlayingWhite ||
7999              gameMode == IcsPlayingBlack ||
8000              gameMode == BeginningOfGame)) {
8001             SendToProgram("force\n", &first);
8002             if (first.usePing) {
8003               char buf[MSG_SIZ];
8004               sprintf(buf, "ping %d\n", ++first.lastPing);
8005               SendToProgram(buf, &first);
8006             }
8007         }
8008     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8009         /* Kill off first chess program */
8010         if (first.isr != NULL)
8011           RemoveInputSource(first.isr);
8012         first.isr = NULL;
8013     
8014         if (first.pr != NoProc) {
8015             ExitAnalyzeMode();
8016             DoSleep( appData.delayBeforeQuit );
8017             SendToProgram("quit\n", &first);
8018             DoSleep( appData.delayAfterQuit );
8019             DestroyChildProcess(first.pr, first.useSigterm);
8020         }
8021         first.pr = NoProc;
8022     }
8023     if (second.reuse) {
8024         /* Put second chess program into idle state */
8025         if (second.pr != NoProc &&
8026             gameMode == TwoMachinesPlay) {
8027             SendToProgram("force\n", &second);
8028             if (second.usePing) {
8029               char buf[MSG_SIZ];
8030               sprintf(buf, "ping %d\n", ++second.lastPing);
8031               SendToProgram(buf, &second);
8032             }
8033         }
8034     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8035         /* Kill off second chess program */
8036         if (second.isr != NULL)
8037           RemoveInputSource(second.isr);
8038         second.isr = NULL;
8039     
8040         if (second.pr != NoProc) {
8041             DoSleep( appData.delayBeforeQuit );
8042             SendToProgram("quit\n", &second);
8043             DoSleep( appData.delayAfterQuit );
8044             DestroyChildProcess(second.pr, second.useSigterm);
8045         }
8046         second.pr = NoProc;
8047     }
8048
8049     if (matchMode && gameMode == TwoMachinesPlay) {
8050         switch (result) {
8051         case WhiteWins:
8052           if (first.twoMachinesColor[0] == 'w') {
8053             first.matchWins++;
8054           } else {
8055             second.matchWins++;
8056           }
8057           break;
8058         case BlackWins:
8059           if (first.twoMachinesColor[0] == 'b') {
8060             first.matchWins++;
8061           } else {
8062             second.matchWins++;
8063           }
8064           break;
8065         default:
8066           break;
8067         }
8068         if (matchGame < appData.matchGames) {
8069             char *tmp;
8070             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8071                 tmp = first.twoMachinesColor;
8072                 first.twoMachinesColor = second.twoMachinesColor;
8073                 second.twoMachinesColor = tmp;
8074             }
8075             gameMode = nextGameMode;
8076             matchGame++;
8077             if(appData.matchPause>10000 || appData.matchPause<10)
8078                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8079             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8080             endingGame = 0; /* [HGM] crash */
8081             return;
8082         } else {
8083             char buf[MSG_SIZ];
8084             gameMode = nextGameMode;
8085             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8086                     first.tidy, second.tidy,
8087                     first.matchWins, second.matchWins,
8088                     appData.matchGames - (first.matchWins + second.matchWins));
8089             DisplayFatalError(buf, 0, 0);
8090         }
8091     }
8092     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8093         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8094       ExitAnalyzeMode();
8095     gameMode = nextGameMode;
8096     ModeHighlight();
8097     endingGame = 0;  /* [HGM] crash */
8098 }
8099
8100 /* Assumes program was just initialized (initString sent).
8101    Leaves program in force mode. */
8102 void
8103 FeedMovesToProgram(cps, upto) 
8104      ChessProgramState *cps;
8105      int upto;
8106 {
8107     int i;
8108     
8109     if (appData.debugMode)
8110       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8111               startedFromSetupPosition ? "position and " : "",
8112               backwardMostMove, upto, cps->which);
8113     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8114         // [HGM] variantswitch: make engine aware of new variant
8115         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8116                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8117         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8118         SendToProgram(buf, cps);
8119         currentlyInitializedVariant = gameInfo.variant;
8120     }
8121     SendToProgram("force\n", cps);
8122     if (startedFromSetupPosition) {
8123         SendBoard(cps, backwardMostMove);
8124     if (appData.debugMode) {
8125         fprintf(debugFP, "feedMoves\n");
8126     }
8127     }
8128     for (i = backwardMostMove; i < upto; i++) {
8129         SendMoveToProgram(i, cps);
8130     }
8131 }
8132
8133
8134 void
8135 ResurrectChessProgram()
8136 {
8137      /* The chess program may have exited.
8138         If so, restart it and feed it all the moves made so far. */
8139
8140     if (appData.noChessProgram || first.pr != NoProc) return;
8141     
8142     StartChessProgram(&first);
8143     InitChessProgram(&first, FALSE);
8144     FeedMovesToProgram(&first, currentMove);
8145
8146     if (!first.sendTime) {
8147         /* can't tell gnuchess what its clock should read,
8148            so we bow to its notion. */
8149         ResetClocks();
8150         timeRemaining[0][currentMove] = whiteTimeRemaining;
8151         timeRemaining[1][currentMove] = blackTimeRemaining;
8152     }
8153
8154     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8155                 appData.icsEngineAnalyze) && first.analysisSupport) {
8156       SendToProgram("analyze\n", &first);
8157       first.analyzing = TRUE;
8158     }
8159 }
8160
8161 /*
8162  * Button procedures
8163  */
8164 void
8165 Reset(redraw, init)
8166      int redraw, init;
8167 {
8168     int i;
8169
8170     if (appData.debugMode) {
8171         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8172                 redraw, init, gameMode);
8173     }
8174     pausing = pauseExamInvalid = FALSE;
8175     startedFromSetupPosition = blackPlaysFirst = FALSE;
8176     firstMove = TRUE;
8177     whiteFlag = blackFlag = FALSE;
8178     userOfferedDraw = FALSE;
8179     hintRequested = bookRequested = FALSE;
8180     first.maybeThinking = FALSE;
8181     second.maybeThinking = FALSE;
8182     first.bookSuspend = FALSE; // [HGM] book
8183     second.bookSuspend = FALSE;
8184     thinkOutput[0] = NULLCHAR;
8185     lastHint[0] = NULLCHAR;
8186     ClearGameInfo(&gameInfo);
8187     gameInfo.variant = StringToVariant(appData.variant);
8188     ics_user_moved = ics_clock_paused = FALSE;
8189     ics_getting_history = H_FALSE;
8190     ics_gamenum = -1;
8191     white_holding[0] = black_holding[0] = NULLCHAR;
8192     ClearProgramStats();
8193     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8194     
8195     ResetFrontEnd();
8196     ClearHighlights();
8197     flipView = appData.flipView;
8198     ClearPremoveHighlights();
8199     gotPremove = FALSE;
8200     alarmSounded = FALSE;
8201
8202     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8203     if(appData.serverMovesName != NULL) {
8204         /* [HGM] prepare to make moves file for broadcasting */
8205         clock_t t = clock();
8206         if(serverMoves != NULL) fclose(serverMoves);
8207         serverMoves = fopen(appData.serverMovesName, "r");
8208         if(serverMoves != NULL) {
8209             fclose(serverMoves);
8210             /* delay 15 sec before overwriting, so all clients can see end */
8211             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8212         }
8213         serverMoves = fopen(appData.serverMovesName, "w");
8214     }
8215
8216     ExitAnalyzeMode();
8217     gameMode = BeginningOfGame;
8218     ModeHighlight();
8219     if(appData.icsActive) gameInfo.variant = VariantNormal;
8220     InitPosition(redraw);
8221     for (i = 0; i < MAX_MOVES; i++) {
8222         if (commentList[i] != NULL) {
8223             free(commentList[i]);
8224             commentList[i] = NULL;
8225         }
8226     }
8227     ResetClocks();
8228     timeRemaining[0][0] = whiteTimeRemaining;
8229     timeRemaining[1][0] = blackTimeRemaining;
8230     if (first.pr == NULL) {
8231         StartChessProgram(&first);
8232     }
8233     if (init) {
8234             InitChessProgram(&first, startedFromSetupPosition);
8235     }
8236     DisplayTitle("");
8237     DisplayMessage("", "");
8238     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8239     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8240 }
8241
8242 void
8243 AutoPlayGameLoop()
8244 {
8245     for (;;) {
8246         if (!AutoPlayOneMove())
8247           return;
8248         if (matchMode || appData.timeDelay == 0)
8249           continue;
8250         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8251           return;
8252         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8253         break;
8254     }
8255 }
8256
8257
8258 int
8259 AutoPlayOneMove()
8260 {
8261     int fromX, fromY, toX, toY;
8262
8263     if (appData.debugMode) {
8264       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8265     }
8266
8267     if (gameMode != PlayFromGameFile)
8268       return FALSE;
8269
8270     if (currentMove >= forwardMostMove) {
8271       gameMode = EditGame;
8272       ModeHighlight();
8273
8274       /* [AS] Clear current move marker at the end of a game */
8275       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8276
8277       return FALSE;
8278     }
8279     
8280     toX = moveList[currentMove][2] - AAA;
8281     toY = moveList[currentMove][3] - ONE;
8282
8283     if (moveList[currentMove][1] == '@') {
8284         if (appData.highlightLastMove) {
8285             SetHighlights(-1, -1, toX, toY);
8286         }
8287     } else {
8288         fromX = moveList[currentMove][0] - AAA;
8289         fromY = moveList[currentMove][1] - ONE;
8290
8291         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8292
8293         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8294
8295         if (appData.highlightLastMove) {
8296             SetHighlights(fromX, fromY, toX, toY);
8297         }
8298     }
8299     DisplayMove(currentMove);
8300     SendMoveToProgram(currentMove++, &first);
8301     DisplayBothClocks();
8302     DrawPosition(FALSE, boards[currentMove]);
8303     // [HGM] PV info: always display, routine tests if empty
8304     DisplayComment(currentMove - 1, commentList[currentMove]);
8305     return TRUE;
8306 }
8307
8308
8309 int
8310 LoadGameOneMove(readAhead)
8311      ChessMove readAhead;
8312 {
8313     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8314     char promoChar = NULLCHAR;
8315     ChessMove moveType;
8316     char move[MSG_SIZ];
8317     char *p, *q;
8318     
8319     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8320         gameMode != AnalyzeMode && gameMode != Training) {
8321         gameFileFP = NULL;
8322         return FALSE;
8323     }
8324     
8325     yyboardindex = forwardMostMove;
8326     if (readAhead != (ChessMove)0) {
8327       moveType = readAhead;
8328     } else {
8329       if (gameFileFP == NULL)
8330           return FALSE;
8331       moveType = (ChessMove) yylex();
8332     }
8333     
8334     done = FALSE;
8335     switch (moveType) {
8336       case Comment:
8337         if (appData.debugMode) 
8338           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8339         p = yy_text;
8340         if (*p == '{' || *p == '[' || *p == '(') {
8341             p[strlen(p) - 1] = NULLCHAR;
8342             p++;
8343         }
8344
8345         /* append the comment but don't display it */
8346         while (*p == '\n') p++;
8347         AppendComment(currentMove, p);
8348         return TRUE;
8349
8350       case WhiteCapturesEnPassant:
8351       case BlackCapturesEnPassant:
8352       case WhitePromotionChancellor:
8353       case BlackPromotionChancellor:
8354       case WhitePromotionArchbishop:
8355       case BlackPromotionArchbishop:
8356       case WhitePromotionCentaur:
8357       case BlackPromotionCentaur:
8358       case WhitePromotionQueen:
8359       case BlackPromotionQueen:
8360       case WhitePromotionRook:
8361       case BlackPromotionRook:
8362       case WhitePromotionBishop:
8363       case BlackPromotionBishop:
8364       case WhitePromotionKnight:
8365       case BlackPromotionKnight:
8366       case WhitePromotionKing:
8367       case BlackPromotionKing:
8368       case NormalMove:
8369       case WhiteKingSideCastle:
8370       case WhiteQueenSideCastle:
8371       case BlackKingSideCastle:
8372       case BlackQueenSideCastle:
8373       case WhiteKingSideCastleWild:
8374       case WhiteQueenSideCastleWild:
8375       case BlackKingSideCastleWild:
8376       case BlackQueenSideCastleWild:
8377       /* PUSH Fabien */
8378       case WhiteHSideCastleFR:
8379       case WhiteASideCastleFR:
8380       case BlackHSideCastleFR:
8381       case BlackASideCastleFR:
8382       /* POP Fabien */
8383         if (appData.debugMode)
8384           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8385         fromX = currentMoveString[0] - AAA;
8386         fromY = currentMoveString[1] - ONE;
8387         toX = currentMoveString[2] - AAA;
8388         toY = currentMoveString[3] - ONE;
8389         promoChar = currentMoveString[4];
8390         break;
8391
8392       case WhiteDrop:
8393       case BlackDrop:
8394         if (appData.debugMode)
8395           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8396         fromX = moveType == WhiteDrop ?
8397           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8398         (int) CharToPiece(ToLower(currentMoveString[0]));
8399         fromY = DROP_RANK;
8400         toX = currentMoveString[2] - AAA;
8401         toY = currentMoveString[3] - ONE;
8402         break;
8403
8404       case WhiteWins:
8405       case BlackWins:
8406       case GameIsDrawn:
8407       case GameUnfinished:
8408         if (appData.debugMode)
8409           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8410         p = strchr(yy_text, '{');
8411         if (p == NULL) p = strchr(yy_text, '(');
8412         if (p == NULL) {
8413             p = yy_text;
8414             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8415         } else {
8416             q = strchr(p, *p == '{' ? '}' : ')');
8417             if (q != NULL) *q = NULLCHAR;
8418             p++;
8419         }
8420         GameEnds(moveType, p, GE_FILE);
8421         done = TRUE;
8422         if (cmailMsgLoaded) {
8423             ClearHighlights();
8424             flipView = WhiteOnMove(currentMove);
8425             if (moveType == GameUnfinished) flipView = !flipView;
8426             if (appData.debugMode)
8427               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8428         }
8429         break;
8430
8431       case (ChessMove) 0:       /* end of file */
8432         if (appData.debugMode)
8433           fprintf(debugFP, "Parser hit end of file\n");
8434         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8435                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8436           case MT_NONE:
8437           case MT_CHECK:
8438             break;
8439           case MT_CHECKMATE:
8440           case MT_STAINMATE:
8441             if (WhiteOnMove(currentMove)) {
8442                 GameEnds(BlackWins, "Black mates", GE_FILE);
8443             } else {
8444                 GameEnds(WhiteWins, "White mates", GE_FILE);
8445             }
8446             break;
8447           case MT_STALEMATE:
8448             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8449             break;
8450         }
8451         done = TRUE;
8452         break;
8453
8454       case MoveNumberOne:
8455         if (lastLoadGameStart == GNUChessGame) {
8456             /* GNUChessGames have numbers, but they aren't move numbers */
8457             if (appData.debugMode)
8458               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8459                       yy_text, (int) moveType);
8460             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8461         }
8462         /* else fall thru */
8463
8464       case XBoardGame:
8465       case GNUChessGame:
8466       case PGNTag:
8467         /* Reached start of next game in file */
8468         if (appData.debugMode)
8469           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8470         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8471                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8472           case MT_NONE:
8473           case MT_CHECK:
8474             break;
8475           case MT_CHECKMATE:
8476           case MT_STAINMATE:
8477             if (WhiteOnMove(currentMove)) {
8478                 GameEnds(BlackWins, "Black mates", GE_FILE);
8479             } else {
8480                 GameEnds(WhiteWins, "White mates", GE_FILE);
8481             }
8482             break;
8483           case MT_STALEMATE:
8484             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8485             break;
8486         }
8487         done = TRUE;
8488         break;
8489
8490       case PositionDiagram:     /* should not happen; ignore */
8491       case ElapsedTime:         /* ignore */
8492       case NAG:                 /* ignore */
8493         if (appData.debugMode)
8494           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8495                   yy_text, (int) moveType);
8496         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8497
8498       case IllegalMove:
8499         if (appData.testLegality) {
8500             if (appData.debugMode)
8501               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8502             sprintf(move, _("Illegal move: %d.%s%s"),
8503                     (forwardMostMove / 2) + 1,
8504                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8505             DisplayError(move, 0);
8506             done = TRUE;
8507         } else {
8508             if (appData.debugMode)
8509               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8510                       yy_text, currentMoveString);
8511             fromX = currentMoveString[0] - AAA;
8512             fromY = currentMoveString[1] - ONE;
8513             toX = currentMoveString[2] - AAA;
8514             toY = currentMoveString[3] - ONE;
8515             promoChar = currentMoveString[4];
8516         }
8517         break;
8518
8519       case AmbiguousMove:
8520         if (appData.debugMode)
8521           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8522         sprintf(move, _("Ambiguous move: %d.%s%s"),
8523                 (forwardMostMove / 2) + 1,
8524                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8525         DisplayError(move, 0);
8526         done = TRUE;
8527         break;
8528
8529       default:
8530       case ImpossibleMove:
8531         if (appData.debugMode)
8532           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8533         sprintf(move, _("Illegal move: %d.%s%s"),
8534                 (forwardMostMove / 2) + 1,
8535                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8536         DisplayError(move, 0);
8537         done = TRUE;
8538         break;
8539     }
8540
8541     if (done) {
8542         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8543             DrawPosition(FALSE, boards[currentMove]);
8544             DisplayBothClocks();
8545             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8546               DisplayComment(currentMove - 1, commentList[currentMove]);
8547         }
8548         (void) StopLoadGameTimer();
8549         gameFileFP = NULL;
8550         cmailOldMove = forwardMostMove;
8551         return FALSE;
8552     } else {
8553         /* currentMoveString is set as a side-effect of yylex */
8554         strcat(currentMoveString, "\n");
8555         strcpy(moveList[forwardMostMove], currentMoveString);
8556         
8557         thinkOutput[0] = NULLCHAR;
8558         MakeMove(fromX, fromY, toX, toY, promoChar);
8559         currentMove = forwardMostMove;
8560         return TRUE;
8561     }
8562 }
8563
8564 /* Load the nth game from the given file */
8565 int
8566 LoadGameFromFile(filename, n, title, useList)
8567      char *filename;
8568      int n;
8569      char *title;
8570      /*Boolean*/ int useList;
8571 {
8572     FILE *f;
8573     char buf[MSG_SIZ];
8574
8575     if (strcmp(filename, "-") == 0) {
8576         f = stdin;
8577         title = "stdin";
8578     } else {
8579         f = fopen(filename, "rb");
8580         if (f == NULL) {
8581           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8582             DisplayError(buf, errno);
8583             return FALSE;
8584         }
8585     }
8586     if (fseek(f, 0, 0) == -1) {
8587         /* f is not seekable; probably a pipe */
8588         useList = FALSE;
8589     }
8590     if (useList && n == 0) {
8591         int error = GameListBuild(f);
8592         if (error) {
8593             DisplayError(_("Cannot build game list"), error);
8594         } else if (!ListEmpty(&gameList) &&
8595                    ((ListGame *) gameList.tailPred)->number > 1) {
8596             GameListPopUp(f, title);
8597             return TRUE;
8598         }
8599         GameListDestroy();
8600         n = 1;
8601     }
8602     if (n == 0) n = 1;
8603     return LoadGame(f, n, title, FALSE);
8604 }
8605
8606
8607 void
8608 MakeRegisteredMove()
8609 {
8610     int fromX, fromY, toX, toY;
8611     char promoChar;
8612     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8613         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8614           case CMAIL_MOVE:
8615           case CMAIL_DRAW:
8616             if (appData.debugMode)
8617               fprintf(debugFP, "Restoring %s for game %d\n",
8618                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8619     
8620             thinkOutput[0] = NULLCHAR;
8621             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8622             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8623             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8624             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8625             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8626             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8627             MakeMove(fromX, fromY, toX, toY, promoChar);
8628             ShowMove(fromX, fromY, toX, toY);
8629               
8630             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8631                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8632               case MT_NONE:
8633               case MT_CHECK:
8634                 break;
8635                 
8636               case MT_CHECKMATE:
8637               case MT_STAINMATE:
8638                 if (WhiteOnMove(currentMove)) {
8639                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8640                 } else {
8641                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8642                 }
8643                 break;
8644                 
8645               case MT_STALEMATE:
8646                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8647                 break;
8648             }
8649
8650             break;
8651             
8652           case CMAIL_RESIGN:
8653             if (WhiteOnMove(currentMove)) {
8654                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8655             } else {
8656                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8657             }
8658             break;
8659             
8660           case CMAIL_ACCEPT:
8661             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8662             break;
8663               
8664           default:
8665             break;
8666         }
8667     }
8668
8669     return;
8670 }
8671
8672 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8673 int
8674 CmailLoadGame(f, gameNumber, title, useList)
8675      FILE *f;
8676      int gameNumber;
8677      char *title;
8678      int useList;
8679 {
8680     int retVal;
8681
8682     if (gameNumber > nCmailGames) {
8683         DisplayError(_("No more games in this message"), 0);
8684         return FALSE;
8685     }
8686     if (f == lastLoadGameFP) {
8687         int offset = gameNumber - lastLoadGameNumber;
8688         if (offset == 0) {
8689             cmailMsg[0] = NULLCHAR;
8690             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8691                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8692                 nCmailMovesRegistered--;
8693             }
8694             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8695             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8696                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8697             }
8698         } else {
8699             if (! RegisterMove()) return FALSE;
8700         }
8701     }
8702
8703     retVal = LoadGame(f, gameNumber, title, useList);
8704
8705     /* Make move registered during previous look at this game, if any */
8706     MakeRegisteredMove();
8707
8708     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8709         commentList[currentMove]
8710           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8711         DisplayComment(currentMove - 1, commentList[currentMove]);
8712     }
8713
8714     return retVal;
8715 }
8716
8717 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8718 int
8719 ReloadGame(offset)
8720      int offset;
8721 {
8722     int gameNumber = lastLoadGameNumber + offset;
8723     if (lastLoadGameFP == NULL) {
8724         DisplayError(_("No game has been loaded yet"), 0);
8725         return FALSE;
8726     }
8727     if (gameNumber <= 0) {
8728         DisplayError(_("Can't back up any further"), 0);
8729         return FALSE;
8730     }
8731     if (cmailMsgLoaded) {
8732         return CmailLoadGame(lastLoadGameFP, gameNumber,
8733                              lastLoadGameTitle, lastLoadGameUseList);
8734     } else {
8735         return LoadGame(lastLoadGameFP, gameNumber,
8736                         lastLoadGameTitle, lastLoadGameUseList);
8737     }
8738 }
8739
8740
8741
8742 /* Load the nth game from open file f */
8743 int
8744 LoadGame(f, gameNumber, title, useList)
8745      FILE *f;
8746      int gameNumber;
8747      char *title;
8748      int useList;
8749 {
8750     ChessMove cm;
8751     char buf[MSG_SIZ];
8752     int gn = gameNumber;
8753     ListGame *lg = NULL;
8754     int numPGNTags = 0;
8755     int err;
8756     GameMode oldGameMode;
8757     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8758
8759     if (appData.debugMode) 
8760         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8761
8762     if (gameMode == Training )
8763         SetTrainingModeOff();
8764
8765     oldGameMode = gameMode;
8766     if (gameMode != BeginningOfGame) {
8767       Reset(FALSE, TRUE);
8768     }
8769
8770     gameFileFP = f;
8771     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8772         fclose(lastLoadGameFP);
8773     }
8774
8775     if (useList) {
8776         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8777         
8778         if (lg) {
8779             fseek(f, lg->offset, 0);
8780             GameListHighlight(gameNumber);
8781             gn = 1;
8782         }
8783         else {
8784             DisplayError(_("Game number out of range"), 0);
8785             return FALSE;
8786         }
8787     } else {
8788         GameListDestroy();
8789         if (fseek(f, 0, 0) == -1) {
8790             if (f == lastLoadGameFP ?
8791                 gameNumber == lastLoadGameNumber + 1 :
8792                 gameNumber == 1) {
8793                 gn = 1;
8794             } else {
8795                 DisplayError(_("Can't seek on game file"), 0);
8796                 return FALSE;
8797             }
8798         }
8799     }
8800     lastLoadGameFP = f;
8801     lastLoadGameNumber = gameNumber;
8802     strcpy(lastLoadGameTitle, title);
8803     lastLoadGameUseList = useList;
8804
8805     yynewfile(f);
8806
8807     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8808       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8809                 lg->gameInfo.black);
8810             DisplayTitle(buf);
8811     } else if (*title != NULLCHAR) {
8812         if (gameNumber > 1) {
8813             sprintf(buf, "%s %d", title, gameNumber);
8814             DisplayTitle(buf);
8815         } else {
8816             DisplayTitle(title);
8817         }
8818     }
8819
8820     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8821         gameMode = PlayFromGameFile;
8822         ModeHighlight();
8823     }
8824
8825     currentMove = forwardMostMove = backwardMostMove = 0;
8826     CopyBoard(boards[0], initialPosition);
8827     StopClocks();
8828
8829     /*
8830      * Skip the first gn-1 games in the file.
8831      * Also skip over anything that precedes an identifiable 
8832      * start of game marker, to avoid being confused by 
8833      * garbage at the start of the file.  Currently 
8834      * recognized start of game markers are the move number "1",
8835      * the pattern "gnuchess .* game", the pattern
8836      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8837      * A game that starts with one of the latter two patterns
8838      * will also have a move number 1, possibly
8839      * following a position diagram.
8840      * 5-4-02: Let's try being more lenient and allowing a game to
8841      * start with an unnumbered move.  Does that break anything?
8842      */
8843     cm = lastLoadGameStart = (ChessMove) 0;
8844     while (gn > 0) {
8845         yyboardindex = forwardMostMove;
8846         cm = (ChessMove) yylex();
8847         switch (cm) {
8848           case (ChessMove) 0:
8849             if (cmailMsgLoaded) {
8850                 nCmailGames = CMAIL_MAX_GAMES - gn;
8851             } else {
8852                 Reset(TRUE, TRUE);
8853                 DisplayError(_("Game not found in file"), 0);
8854             }
8855             return FALSE;
8856
8857           case GNUChessGame:
8858           case XBoardGame:
8859             gn--;
8860             lastLoadGameStart = cm;
8861             break;
8862             
8863           case MoveNumberOne:
8864             switch (lastLoadGameStart) {
8865               case GNUChessGame:
8866               case XBoardGame:
8867               case PGNTag:
8868                 break;
8869               case MoveNumberOne:
8870               case (ChessMove) 0:
8871                 gn--;           /* count this game */
8872                 lastLoadGameStart = cm;
8873                 break;
8874               default:
8875                 /* impossible */
8876                 break;
8877             }
8878             break;
8879
8880           case PGNTag:
8881             switch (lastLoadGameStart) {
8882               case GNUChessGame:
8883               case PGNTag:
8884               case MoveNumberOne:
8885               case (ChessMove) 0:
8886                 gn--;           /* count this game */
8887                 lastLoadGameStart = cm;
8888                 break;
8889               case XBoardGame:
8890                 lastLoadGameStart = cm; /* game counted already */
8891                 break;
8892               default:
8893                 /* impossible */
8894                 break;
8895             }
8896             if (gn > 0) {
8897                 do {
8898                     yyboardindex = forwardMostMove;
8899                     cm = (ChessMove) yylex();
8900                 } while (cm == PGNTag || cm == Comment);
8901             }
8902             break;
8903
8904           case WhiteWins:
8905           case BlackWins:
8906           case GameIsDrawn:
8907             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8908                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8909                     != CMAIL_OLD_RESULT) {
8910                     nCmailResults ++ ;
8911                     cmailResult[  CMAIL_MAX_GAMES
8912                                 - gn - 1] = CMAIL_OLD_RESULT;
8913                 }
8914             }
8915             break;
8916
8917           case NormalMove:
8918             /* Only a NormalMove can be at the start of a game
8919              * without a position diagram. */
8920             if (lastLoadGameStart == (ChessMove) 0) {
8921               gn--;
8922               lastLoadGameStart = MoveNumberOne;
8923             }
8924             break;
8925
8926           default:
8927             break;
8928         }
8929     }
8930     
8931     if (appData.debugMode)
8932       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8933
8934     if (cm == XBoardGame) {
8935         /* Skip any header junk before position diagram and/or move 1 */
8936         for (;;) {
8937             yyboardindex = forwardMostMove;
8938             cm = (ChessMove) yylex();
8939
8940             if (cm == (ChessMove) 0 ||
8941                 cm == GNUChessGame || cm == XBoardGame) {
8942                 /* Empty game; pretend end-of-file and handle later */
8943                 cm = (ChessMove) 0;
8944                 break;
8945             }
8946
8947             if (cm == MoveNumberOne || cm == PositionDiagram ||
8948                 cm == PGNTag || cm == Comment)
8949               break;
8950         }
8951     } else if (cm == GNUChessGame) {
8952         if (gameInfo.event != NULL) {
8953             free(gameInfo.event);
8954         }
8955         gameInfo.event = StrSave(yy_text);
8956     }   
8957
8958     startedFromSetupPosition = FALSE;
8959     while (cm == PGNTag) {
8960         if (appData.debugMode) 
8961           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8962         err = ParsePGNTag(yy_text, &gameInfo);
8963         if (!err) numPGNTags++;
8964
8965         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8966         if(gameInfo.variant != oldVariant) {
8967             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8968             InitPosition(TRUE);
8969             oldVariant = gameInfo.variant;
8970             if (appData.debugMode) 
8971               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8972         }
8973
8974
8975         if (gameInfo.fen != NULL) {
8976           Board initial_position;
8977           startedFromSetupPosition = TRUE;
8978           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8979             Reset(TRUE, TRUE);
8980             DisplayError(_("Bad FEN position in file"), 0);
8981             return FALSE;
8982           }
8983           CopyBoard(boards[0], initial_position);
8984           if (blackPlaysFirst) {
8985             currentMove = forwardMostMove = backwardMostMove = 1;
8986             CopyBoard(boards[1], initial_position);
8987             strcpy(moveList[0], "");
8988             strcpy(parseList[0], "");
8989             timeRemaining[0][1] = whiteTimeRemaining;
8990             timeRemaining[1][1] = blackTimeRemaining;
8991             if (commentList[0] != NULL) {
8992               commentList[1] = commentList[0];
8993               commentList[0] = NULL;
8994             }
8995           } else {
8996             currentMove = forwardMostMove = backwardMostMove = 0;
8997           }
8998           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8999           {   int i;
9000               initialRulePlies = FENrulePlies;
9001               epStatus[forwardMostMove] = FENepStatus;
9002               for( i=0; i< nrCastlingRights; i++ )
9003                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9004           }
9005           yyboardindex = forwardMostMove;
9006           free(gameInfo.fen);
9007           gameInfo.fen = NULL;
9008         }
9009
9010         yyboardindex = forwardMostMove;
9011         cm = (ChessMove) yylex();
9012
9013         /* Handle comments interspersed among the tags */
9014         while (cm == Comment) {
9015             char *p;
9016             if (appData.debugMode) 
9017               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9018             p = yy_text;
9019             if (*p == '{' || *p == '[' || *p == '(') {
9020                 p[strlen(p) - 1] = NULLCHAR;
9021                 p++;
9022             }
9023             while (*p == '\n') p++;
9024             AppendComment(currentMove, p);
9025             yyboardindex = forwardMostMove;
9026             cm = (ChessMove) yylex();
9027         }
9028     }
9029
9030     /* don't rely on existence of Event tag since if game was
9031      * pasted from clipboard the Event tag may not exist
9032      */
9033     if (numPGNTags > 0){
9034         char *tags;
9035         if (gameInfo.variant == VariantNormal) {
9036           gameInfo.variant = StringToVariant(gameInfo.event);
9037         }
9038         if (!matchMode) {
9039           if( appData.autoDisplayTags ) {
9040             tags = PGNTags(&gameInfo);
9041             TagsPopUp(tags, CmailMsg());
9042             free(tags);
9043           }
9044         }
9045     } else {
9046         /* Make something up, but don't display it now */
9047         SetGameInfo();
9048         TagsPopDown();
9049     }
9050
9051     if (cm == PositionDiagram) {
9052         int i, j;
9053         char *p;
9054         Board initial_position;
9055
9056         if (appData.debugMode)
9057           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9058
9059         if (!startedFromSetupPosition) {
9060             p = yy_text;
9061             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9062               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9063                 switch (*p) {
9064                   case '[':
9065                   case '-':
9066                   case ' ':
9067                   case '\t':
9068                   case '\n':
9069                   case '\r':
9070                     break;
9071                   default:
9072                     initial_position[i][j++] = CharToPiece(*p);
9073                     break;
9074                 }
9075             while (*p == ' ' || *p == '\t' ||
9076                    *p == '\n' || *p == '\r') p++;
9077         
9078             if (strncmp(p, "black", strlen("black"))==0)
9079               blackPlaysFirst = TRUE;
9080             else
9081               blackPlaysFirst = FALSE;
9082             startedFromSetupPosition = TRUE;
9083         
9084             CopyBoard(boards[0], initial_position);
9085             if (blackPlaysFirst) {
9086                 currentMove = forwardMostMove = backwardMostMove = 1;
9087                 CopyBoard(boards[1], initial_position);
9088                 strcpy(moveList[0], "");
9089                 strcpy(parseList[0], "");
9090                 timeRemaining[0][1] = whiteTimeRemaining;
9091                 timeRemaining[1][1] = blackTimeRemaining;
9092                 if (commentList[0] != NULL) {
9093                     commentList[1] = commentList[0];
9094                     commentList[0] = NULL;
9095                 }
9096             } else {
9097                 currentMove = forwardMostMove = backwardMostMove = 0;
9098             }
9099         }
9100         yyboardindex = forwardMostMove;
9101         cm = (ChessMove) yylex();
9102     }
9103
9104     if (first.pr == NoProc) {
9105         StartChessProgram(&first);
9106     }
9107     InitChessProgram(&first, FALSE);
9108     SendToProgram("force\n", &first);
9109     if (startedFromSetupPosition) {
9110         SendBoard(&first, forwardMostMove);
9111     if (appData.debugMode) {
9112         fprintf(debugFP, "Load Game\n");
9113     }
9114         DisplayBothClocks();
9115     }      
9116
9117     /* [HGM] server: flag to write setup moves in broadcast file as one */
9118     loadFlag = appData.suppressLoadMoves;
9119
9120     while (cm == Comment) {
9121         char *p;
9122         if (appData.debugMode) 
9123           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9124         p = yy_text;
9125         if (*p == '{' || *p == '[' || *p == '(') {
9126             p[strlen(p) - 1] = NULLCHAR;
9127             p++;
9128         }
9129         while (*p == '\n') p++;
9130         AppendComment(currentMove, p);
9131         yyboardindex = forwardMostMove;
9132         cm = (ChessMove) yylex();
9133     }
9134
9135     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9136         cm == WhiteWins || cm == BlackWins ||
9137         cm == GameIsDrawn || cm == GameUnfinished) {
9138         DisplayMessage("", _("No moves in game"));
9139         if (cmailMsgLoaded) {
9140             if (appData.debugMode)
9141               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9142             ClearHighlights();
9143             flipView = FALSE;
9144         }
9145         DrawPosition(FALSE, boards[currentMove]);
9146         DisplayBothClocks();
9147         gameMode = EditGame;
9148         ModeHighlight();
9149         gameFileFP = NULL;
9150         cmailOldMove = 0;
9151         return TRUE;
9152     }
9153
9154     // [HGM] PV info: routine tests if comment empty
9155     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9156         DisplayComment(currentMove - 1, commentList[currentMove]);
9157     }
9158     if (!matchMode && appData.timeDelay != 0) 
9159       DrawPosition(FALSE, boards[currentMove]);
9160
9161     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9162       programStats.ok_to_send = 1;
9163     }
9164
9165     /* if the first token after the PGN tags is a move
9166      * and not move number 1, retrieve it from the parser 
9167      */
9168     if (cm != MoveNumberOne)
9169         LoadGameOneMove(cm);
9170
9171     /* load the remaining moves from the file */
9172     while (LoadGameOneMove((ChessMove)0)) {
9173       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9174       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9175     }
9176
9177     /* rewind to the start of the game */
9178     currentMove = backwardMostMove;
9179
9180     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9181
9182     if (oldGameMode == AnalyzeFile ||
9183         oldGameMode == AnalyzeMode) {
9184       AnalyzeFileEvent();
9185     }
9186
9187     if (matchMode || appData.timeDelay == 0) {
9188       ToEndEvent();
9189       gameMode = EditGame;
9190       ModeHighlight();
9191     } else if (appData.timeDelay > 0) {
9192       AutoPlayGameLoop();
9193     }
9194
9195     if (appData.debugMode) 
9196         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9197
9198     loadFlag = 0; /* [HGM] true game starts */
9199     return TRUE;
9200 }
9201
9202 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9203 int
9204 ReloadPosition(offset)
9205      int offset;
9206 {
9207     int positionNumber = lastLoadPositionNumber + offset;
9208     if (lastLoadPositionFP == NULL) {
9209         DisplayError(_("No position has been loaded yet"), 0);
9210         return FALSE;
9211     }
9212     if (positionNumber <= 0) {
9213         DisplayError(_("Can't back up any further"), 0);
9214         return FALSE;
9215     }
9216     return LoadPosition(lastLoadPositionFP, positionNumber,
9217                         lastLoadPositionTitle);
9218 }
9219
9220 /* Load the nth position from the given file */
9221 int
9222 LoadPositionFromFile(filename, n, title)
9223      char *filename;
9224      int n;
9225      char *title;
9226 {
9227     FILE *f;
9228     char buf[MSG_SIZ];
9229
9230     if (strcmp(filename, "-") == 0) {
9231         return LoadPosition(stdin, n, "stdin");
9232     } else {
9233         f = fopen(filename, "rb");
9234         if (f == NULL) {
9235             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9236             DisplayError(buf, errno);
9237             return FALSE;
9238         } else {
9239             return LoadPosition(f, n, title);
9240         }
9241     }
9242 }
9243
9244 /* Load the nth position from the given open file, and close it */
9245 int
9246 LoadPosition(f, positionNumber, title)
9247      FILE *f;
9248      int positionNumber;
9249      char *title;
9250 {
9251     char *p, line[MSG_SIZ];
9252     Board initial_position;
9253     int i, j, fenMode, pn;
9254     
9255     if (gameMode == Training )
9256         SetTrainingModeOff();
9257
9258     if (gameMode != BeginningOfGame) {
9259         Reset(FALSE, TRUE);
9260     }
9261     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9262         fclose(lastLoadPositionFP);
9263     }
9264     if (positionNumber == 0) positionNumber = 1;
9265     lastLoadPositionFP = f;
9266     lastLoadPositionNumber = positionNumber;
9267     strcpy(lastLoadPositionTitle, title);
9268     if (first.pr == NoProc) {
9269       StartChessProgram(&first);
9270       InitChessProgram(&first, FALSE);
9271     }    
9272     pn = positionNumber;
9273     if (positionNumber < 0) {
9274         /* Negative position number means to seek to that byte offset */
9275         if (fseek(f, -positionNumber, 0) == -1) {
9276             DisplayError(_("Can't seek on position file"), 0);
9277             return FALSE;
9278         };
9279         pn = 1;
9280     } else {
9281         if (fseek(f, 0, 0) == -1) {
9282             if (f == lastLoadPositionFP ?
9283                 positionNumber == lastLoadPositionNumber + 1 :
9284                 positionNumber == 1) {
9285                 pn = 1;
9286             } else {
9287                 DisplayError(_("Can't seek on position file"), 0);
9288                 return FALSE;
9289             }
9290         }
9291     }
9292     /* See if this file is FEN or old-style xboard */
9293     if (fgets(line, MSG_SIZ, f) == NULL) {
9294         DisplayError(_("Position not found in file"), 0);
9295         return FALSE;
9296     }
9297 #if 0
9298     switch (line[0]) {
9299       case '#':  case 'x':
9300       default:
9301         fenMode = FALSE;
9302         break;
9303       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9304       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9305       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9306       case '7':  case '8':  case '9':
9307       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9308       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9309       case 'C':  case 'W':             case 'c':  case 'w': 
9310         fenMode = TRUE;
9311         break;
9312     }
9313 #else
9314     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9315     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9316 #endif
9317
9318     if (pn >= 2) {
9319         if (fenMode || line[0] == '#') pn--;
9320         while (pn > 0) {
9321             /* skip positions before number pn */
9322             if (fgets(line, MSG_SIZ, f) == NULL) {
9323                 Reset(TRUE, TRUE);
9324                 DisplayError(_("Position not found in file"), 0);
9325                 return FALSE;
9326             }
9327             if (fenMode || line[0] == '#') pn--;
9328         }
9329     }
9330
9331     if (fenMode) {
9332         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9333             DisplayError(_("Bad FEN position in file"), 0);
9334             return FALSE;
9335         }
9336     } else {
9337         (void) fgets(line, MSG_SIZ, f);
9338         (void) fgets(line, MSG_SIZ, f);
9339     
9340         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9341             (void) fgets(line, MSG_SIZ, f);
9342             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9343                 if (*p == ' ')
9344                   continue;
9345                 initial_position[i][j++] = CharToPiece(*p);
9346             }
9347         }
9348     
9349         blackPlaysFirst = FALSE;
9350         if (!feof(f)) {
9351             (void) fgets(line, MSG_SIZ, f);
9352             if (strncmp(line, "black", strlen("black"))==0)
9353               blackPlaysFirst = TRUE;
9354         }
9355     }
9356     startedFromSetupPosition = TRUE;
9357     
9358     SendToProgram("force\n", &first);
9359     CopyBoard(boards[0], initial_position);
9360     if (blackPlaysFirst) {
9361         currentMove = forwardMostMove = backwardMostMove = 1;
9362         strcpy(moveList[0], "");
9363         strcpy(parseList[0], "");
9364         CopyBoard(boards[1], initial_position);
9365         DisplayMessage("", _("Black to play"));
9366     } else {
9367         currentMove = forwardMostMove = backwardMostMove = 0;
9368         DisplayMessage("", _("White to play"));
9369     }
9370           /* [HGM] copy FEN attributes as well */
9371           {   int i;
9372               initialRulePlies = FENrulePlies;
9373               epStatus[forwardMostMove] = FENepStatus;
9374               for( i=0; i< nrCastlingRights; i++ )
9375                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9376           }
9377     SendBoard(&first, forwardMostMove);
9378     if (appData.debugMode) {
9379 int i, j;
9380   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9381   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9382         fprintf(debugFP, "Load Position\n");
9383     }
9384
9385     if (positionNumber > 1) {
9386         sprintf(line, "%s %d", title, positionNumber);
9387         DisplayTitle(line);
9388     } else {
9389         DisplayTitle(title);
9390     }
9391     gameMode = EditGame;
9392     ModeHighlight();
9393     ResetClocks();
9394     timeRemaining[0][1] = whiteTimeRemaining;
9395     timeRemaining[1][1] = blackTimeRemaining;
9396     DrawPosition(FALSE, boards[currentMove]);
9397    
9398     return TRUE;
9399 }
9400
9401
9402 void
9403 CopyPlayerNameIntoFileName(dest, src)
9404      char **dest, *src;
9405 {
9406     while (*src != NULLCHAR && *src != ',') {
9407         if (*src == ' ') {
9408             *(*dest)++ = '_';
9409             src++;
9410         } else {
9411             *(*dest)++ = *src++;
9412         }
9413     }
9414 }
9415
9416 char *DefaultFileName(ext)
9417      char *ext;
9418 {
9419     static char def[MSG_SIZ];
9420     char *p;
9421
9422     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9423         p = def;
9424         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9425         *p++ = '-';
9426         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9427         *p++ = '.';
9428         strcpy(p, ext);
9429     } else {
9430         def[0] = NULLCHAR;
9431     }
9432     return def;
9433 }
9434
9435 /* Save the current game to the given file */
9436 int
9437 SaveGameToFile(filename, append)
9438      char *filename;
9439      int append;
9440 {
9441     FILE *f;
9442     char buf[MSG_SIZ];
9443
9444     if (strcmp(filename, "-") == 0) {
9445         return SaveGame(stdout, 0, NULL);
9446     } else {
9447         f = fopen(filename, append ? "a" : "w");
9448         if (f == NULL) {
9449             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9450             DisplayError(buf, errno);
9451             return FALSE;
9452         } else {
9453             return SaveGame(f, 0, NULL);
9454         }
9455     }
9456 }
9457
9458 char *
9459 SavePart(str)
9460      char *str;
9461 {
9462     static char buf[MSG_SIZ];
9463     char *p;
9464     
9465     p = strchr(str, ' ');
9466     if (p == NULL) return str;
9467     strncpy(buf, str, p - str);
9468     buf[p - str] = NULLCHAR;
9469     return buf;
9470 }
9471
9472 #define PGN_MAX_LINE 75
9473
9474 #define PGN_SIDE_WHITE  0
9475 #define PGN_SIDE_BLACK  1
9476
9477 /* [AS] */
9478 static int FindFirstMoveOutOfBook( int side )
9479 {
9480     int result = -1;
9481
9482     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9483         int index = backwardMostMove;
9484         int has_book_hit = 0;
9485
9486         if( (index % 2) != side ) {
9487             index++;
9488         }
9489
9490         while( index < forwardMostMove ) {
9491             /* Check to see if engine is in book */
9492             int depth = pvInfoList[index].depth;
9493             int score = pvInfoList[index].score;
9494             int in_book = 0;
9495
9496             if( depth <= 2 ) {
9497                 in_book = 1;
9498             }
9499             else if( score == 0 && depth == 63 ) {
9500                 in_book = 1; /* Zappa */
9501             }
9502             else if( score == 2 && depth == 99 ) {
9503                 in_book = 1; /* Abrok */
9504             }
9505
9506             has_book_hit += in_book;
9507
9508             if( ! in_book ) {
9509                 result = index;
9510
9511                 break;
9512             }
9513
9514             index += 2;
9515         }
9516     }
9517
9518     return result;
9519 }
9520
9521 /* [AS] */
9522 void GetOutOfBookInfo( char * buf )
9523 {
9524     int oob[2];
9525     int i;
9526     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9527
9528     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9529     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9530
9531     *buf = '\0';
9532
9533     if( oob[0] >= 0 || oob[1] >= 0 ) {
9534         for( i=0; i<2; i++ ) {
9535             int idx = oob[i];
9536
9537             if( idx >= 0 ) {
9538                 if( i > 0 && oob[0] >= 0 ) {
9539                     strcat( buf, "   " );
9540                 }
9541
9542                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9543                 sprintf( buf+strlen(buf), "%s%.2f", 
9544                     pvInfoList[idx].score >= 0 ? "+" : "",
9545                     pvInfoList[idx].score / 100.0 );
9546             }
9547         }
9548     }
9549 }
9550
9551 /* Save game in PGN style and close the file */
9552 int
9553 SaveGamePGN(f)
9554      FILE *f;
9555 {
9556     int i, offset, linelen, newblock;
9557     time_t tm;
9558 //    char *movetext;
9559     char numtext[32];
9560     int movelen, numlen, blank;
9561     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9562
9563     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9564     
9565     tm = time((time_t *) NULL);
9566     
9567     PrintPGNTags(f, &gameInfo);
9568     
9569     if (backwardMostMove > 0 || startedFromSetupPosition) {
9570         char *fen = PositionToFEN(backwardMostMove, NULL);
9571         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9572         fprintf(f, "\n{--------------\n");
9573         PrintPosition(f, backwardMostMove);
9574         fprintf(f, "--------------}\n");
9575         free(fen);
9576     }
9577     else {
9578         /* [AS] Out of book annotation */
9579         if( appData.saveOutOfBookInfo ) {
9580             char buf[64];
9581
9582             GetOutOfBookInfo( buf );
9583
9584             if( buf[0] != '\0' ) {
9585                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9586             }
9587         }
9588
9589         fprintf(f, "\n");
9590     }
9591
9592     i = backwardMostMove;
9593     linelen = 0;
9594     newblock = TRUE;
9595
9596     while (i < forwardMostMove) {
9597         /* Print comments preceding this move */
9598         if (commentList[i] != NULL) {
9599             if (linelen > 0) fprintf(f, "\n");
9600             fprintf(f, "{\n%s}\n", commentList[i]);
9601             linelen = 0;
9602             newblock = TRUE;
9603         }
9604
9605         /* Format move number */
9606         if ((i % 2) == 0) {
9607             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9608         } else {
9609             if (newblock) {
9610                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9611             } else {
9612                 numtext[0] = NULLCHAR;
9613             }
9614         }
9615         numlen = strlen(numtext);
9616         newblock = FALSE;
9617
9618         /* Print move number */
9619         blank = linelen > 0 && numlen > 0;
9620         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9621             fprintf(f, "\n");
9622             linelen = 0;
9623             blank = 0;
9624         }
9625         if (blank) {
9626             fprintf(f, " ");
9627             linelen++;
9628         }
9629         fprintf(f, numtext);
9630         linelen += numlen;
9631
9632         /* Get move */
9633         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9634         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9635 #if 0
9636         // SavePart already does this!
9637         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9638                 int p = movelen - 1;
9639                 if(move_buffer[p] == ' ') p--;
9640                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9641                     while(p && move_buffer[--p] != '(');
9642                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9643                 }
9644         }
9645 #endif
9646         /* Print move */
9647         blank = linelen > 0 && movelen > 0;
9648         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9649             fprintf(f, "\n");
9650             linelen = 0;
9651             blank = 0;
9652         }
9653         if (blank) {
9654             fprintf(f, " ");
9655             linelen++;
9656         }
9657         fprintf(f, move_buffer);
9658         linelen += movelen;
9659
9660         /* [AS] Add PV info if present */
9661         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9662             /* [HGM] add time */
9663             char buf[MSG_SIZ]; int seconds = 0;
9664
9665 #if 1
9666             if(i >= backwardMostMove) {
9667                 if(WhiteOnMove(i))
9668                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9669                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9670                 else
9671                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9672                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9673             }
9674             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9675 #else
9676             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9677 #endif
9678
9679             if( seconds <= 0) buf[0] = 0; else
9680             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9681                 seconds = (seconds + 4)/10; // round to full seconds
9682                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9683                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9684             }
9685
9686             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9687                 pvInfoList[i].score >= 0 ? "+" : "",
9688                 pvInfoList[i].score / 100.0,
9689                 pvInfoList[i].depth,
9690                 buf );
9691
9692             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9693
9694             /* Print score/depth */
9695             blank = linelen > 0 && movelen > 0;
9696             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9697                 fprintf(f, "\n");
9698                 linelen = 0;
9699                 blank = 0;
9700             }
9701             if (blank) {
9702                 fprintf(f, " ");
9703                 linelen++;
9704             }
9705             fprintf(f, move_buffer);
9706             linelen += movelen;
9707         }
9708
9709         i++;
9710     }
9711     
9712     /* Start a new line */
9713     if (linelen > 0) fprintf(f, "\n");
9714
9715     /* Print comments after last move */
9716     if (commentList[i] != NULL) {
9717         fprintf(f, "{\n%s}\n", commentList[i]);
9718     }
9719
9720     /* Print result */
9721     if (gameInfo.resultDetails != NULL &&
9722         gameInfo.resultDetails[0] != NULLCHAR) {
9723         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9724                 PGNResult(gameInfo.result));
9725     } else {
9726         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9727     }
9728
9729     fclose(f);
9730     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9731     return TRUE;
9732 }
9733
9734 /* Save game in old style and close the file */
9735 int
9736 SaveGameOldStyle(f)
9737      FILE *f;
9738 {
9739     int i, offset;
9740     time_t tm;
9741     
9742     tm = time((time_t *) NULL);
9743     
9744     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9745     PrintOpponents(f);
9746     
9747     if (backwardMostMove > 0 || startedFromSetupPosition) {
9748         fprintf(f, "\n[--------------\n");
9749         PrintPosition(f, backwardMostMove);
9750         fprintf(f, "--------------]\n");
9751     } else {
9752         fprintf(f, "\n");
9753     }
9754
9755     i = backwardMostMove;
9756     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9757
9758     while (i < forwardMostMove) {
9759         if (commentList[i] != NULL) {
9760             fprintf(f, "[%s]\n", commentList[i]);
9761         }
9762
9763         if ((i % 2) == 1) {
9764             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9765             i++;
9766         } else {
9767             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9768             i++;
9769             if (commentList[i] != NULL) {
9770                 fprintf(f, "\n");
9771                 continue;
9772             }
9773             if (i >= forwardMostMove) {
9774                 fprintf(f, "\n");
9775                 break;
9776             }
9777             fprintf(f, "%s\n", parseList[i]);
9778             i++;
9779         }
9780     }
9781     
9782     if (commentList[i] != NULL) {
9783         fprintf(f, "[%s]\n", commentList[i]);
9784     }
9785
9786     /* This isn't really the old style, but it's close enough */
9787     if (gameInfo.resultDetails != NULL &&
9788         gameInfo.resultDetails[0] != NULLCHAR) {
9789         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9790                 gameInfo.resultDetails);
9791     } else {
9792         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9793     }
9794
9795     fclose(f);
9796     return TRUE;
9797 }
9798
9799 /* Save the current game to open file f and close the file */
9800 int
9801 SaveGame(f, dummy, dummy2)
9802      FILE *f;
9803      int dummy;
9804      char *dummy2;
9805 {
9806     if (gameMode == EditPosition) EditPositionDone();
9807     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9808     if (appData.oldSaveStyle)
9809       return SaveGameOldStyle(f);
9810     else
9811       return SaveGamePGN(f);
9812 }
9813
9814 /* Save the current position to the given file */
9815 int
9816 SavePositionToFile(filename)
9817      char *filename;
9818 {
9819     FILE *f;
9820     char buf[MSG_SIZ];
9821
9822     if (strcmp(filename, "-") == 0) {
9823         return SavePosition(stdout, 0, NULL);
9824     } else {
9825         f = fopen(filename, "a");
9826         if (f == NULL) {
9827             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9828             DisplayError(buf, errno);
9829             return FALSE;
9830         } else {
9831             SavePosition(f, 0, NULL);
9832             return TRUE;
9833         }
9834     }
9835 }
9836
9837 /* Save the current position to the given open file and close the file */
9838 int
9839 SavePosition(f, dummy, dummy2)
9840      FILE *f;
9841      int dummy;
9842      char *dummy2;
9843 {
9844     time_t tm;
9845     char *fen;
9846     
9847     if (appData.oldSaveStyle) {
9848         tm = time((time_t *) NULL);
9849     
9850         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9851         PrintOpponents(f);
9852         fprintf(f, "[--------------\n");
9853         PrintPosition(f, currentMove);
9854         fprintf(f, "--------------]\n");
9855     } else {
9856         fen = PositionToFEN(currentMove, NULL);
9857         fprintf(f, "%s\n", fen);
9858         free(fen);
9859     }
9860     fclose(f);
9861     return TRUE;
9862 }
9863
9864 void
9865 ReloadCmailMsgEvent(unregister)
9866      int unregister;
9867 {
9868 #if !WIN32
9869     static char *inFilename = NULL;
9870     static char *outFilename;
9871     int i;
9872     struct stat inbuf, outbuf;
9873     int status;
9874     
9875     /* Any registered moves are unregistered if unregister is set, */
9876     /* i.e. invoked by the signal handler */
9877     if (unregister) {
9878         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9879             cmailMoveRegistered[i] = FALSE;
9880             if (cmailCommentList[i] != NULL) {
9881                 free(cmailCommentList[i]);
9882                 cmailCommentList[i] = NULL;
9883             }
9884         }
9885         nCmailMovesRegistered = 0;
9886     }
9887
9888     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9889         cmailResult[i] = CMAIL_NOT_RESULT;
9890     }
9891     nCmailResults = 0;
9892
9893     if (inFilename == NULL) {
9894         /* Because the filenames are static they only get malloced once  */
9895         /* and they never get freed                                      */
9896         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9897         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9898
9899         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9900         sprintf(outFilename, "%s.out", appData.cmailGameName);
9901     }
9902     
9903     status = stat(outFilename, &outbuf);
9904     if (status < 0) {
9905         cmailMailedMove = FALSE;
9906     } else {
9907         status = stat(inFilename, &inbuf);
9908         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9909     }
9910     
9911     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9912        counts the games, notes how each one terminated, etc.
9913        
9914        It would be nice to remove this kludge and instead gather all
9915        the information while building the game list.  (And to keep it
9916        in the game list nodes instead of having a bunch of fixed-size
9917        parallel arrays.)  Note this will require getting each game's
9918        termination from the PGN tags, as the game list builder does
9919        not process the game moves.  --mann
9920        */
9921     cmailMsgLoaded = TRUE;
9922     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9923     
9924     /* Load first game in the file or popup game menu */
9925     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9926
9927 #endif /* !WIN32 */
9928     return;
9929 }
9930
9931 int
9932 RegisterMove()
9933 {
9934     FILE *f;
9935     char string[MSG_SIZ];
9936
9937     if (   cmailMailedMove
9938         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9939         return TRUE;            /* Allow free viewing  */
9940     }
9941
9942     /* Unregister move to ensure that we don't leave RegisterMove        */
9943     /* with the move registered when the conditions for registering no   */
9944     /* longer hold                                                       */
9945     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9946         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9947         nCmailMovesRegistered --;
9948
9949         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9950           {
9951               free(cmailCommentList[lastLoadGameNumber - 1]);
9952               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9953           }
9954     }
9955
9956     if (cmailOldMove == -1) {
9957         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9958         return FALSE;
9959     }
9960
9961     if (currentMove > cmailOldMove + 1) {
9962         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9963         return FALSE;
9964     }
9965
9966     if (currentMove < cmailOldMove) {
9967         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9968         return FALSE;
9969     }
9970
9971     if (forwardMostMove > currentMove) {
9972         /* Silently truncate extra moves */
9973         TruncateGame();
9974     }
9975
9976     if (   (currentMove == cmailOldMove + 1)
9977         || (   (currentMove == cmailOldMove)
9978             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9979                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9980         if (gameInfo.result != GameUnfinished) {
9981             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9982         }
9983
9984         if (commentList[currentMove] != NULL) {
9985             cmailCommentList[lastLoadGameNumber - 1]
9986               = StrSave(commentList[currentMove]);
9987         }
9988         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9989
9990         if (appData.debugMode)
9991           fprintf(debugFP, "Saving %s for game %d\n",
9992                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9993
9994         sprintf(string,
9995                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9996         
9997         f = fopen(string, "w");
9998         if (appData.oldSaveStyle) {
9999             SaveGameOldStyle(f); /* also closes the file */
10000             
10001             sprintf(string, "%s.pos.out", appData.cmailGameName);
10002             f = fopen(string, "w");
10003             SavePosition(f, 0, NULL); /* also closes the file */
10004         } else {
10005             fprintf(f, "{--------------\n");
10006             PrintPosition(f, currentMove);
10007             fprintf(f, "--------------}\n\n");
10008             
10009             SaveGame(f, 0, NULL); /* also closes the file*/
10010         }
10011         
10012         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10013         nCmailMovesRegistered ++;
10014     } else if (nCmailGames == 1) {
10015         DisplayError(_("You have not made a move yet"), 0);
10016         return FALSE;
10017     }
10018
10019     return TRUE;
10020 }
10021
10022 void
10023 MailMoveEvent()
10024 {
10025 #if !WIN32
10026     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10027     FILE *commandOutput;
10028     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10029     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10030     int nBuffers;
10031     int i;
10032     int archived;
10033     char *arcDir;
10034
10035     if (! cmailMsgLoaded) {
10036         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10037         return;
10038     }
10039
10040     if (nCmailGames == nCmailResults) {
10041         DisplayError(_("No unfinished games"), 0);
10042         return;
10043     }
10044
10045 #if CMAIL_PROHIBIT_REMAIL
10046     if (cmailMailedMove) {
10047         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);
10048         DisplayError(msg, 0);
10049         return;
10050     }
10051 #endif
10052
10053     if (! (cmailMailedMove || RegisterMove())) return;
10054     
10055     if (   cmailMailedMove
10056         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10057         sprintf(string, partCommandString,
10058                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10059         commandOutput = popen(string, "r");
10060
10061         if (commandOutput == NULL) {
10062             DisplayError(_("Failed to invoke cmail"), 0);
10063         } else {
10064             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10065                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10066             }
10067             if (nBuffers > 1) {
10068                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10069                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10070                 nBytes = MSG_SIZ - 1;
10071             } else {
10072                 (void) memcpy(msg, buffer, nBytes);
10073             }
10074             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10075
10076             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10077                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10078
10079                 archived = TRUE;
10080                 for (i = 0; i < nCmailGames; i ++) {
10081                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10082                         archived = FALSE;
10083                     }
10084                 }
10085                 if (   archived
10086                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10087                         != NULL)) {
10088                     sprintf(buffer, "%s/%s.%s.archive",
10089                             arcDir,
10090                             appData.cmailGameName,
10091                             gameInfo.date);
10092                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10093                     cmailMsgLoaded = FALSE;
10094                 }
10095             }
10096
10097             DisplayInformation(msg);
10098             pclose(commandOutput);
10099         }
10100     } else {
10101         if ((*cmailMsg) != '\0') {
10102             DisplayInformation(cmailMsg);
10103         }
10104     }
10105
10106     return;
10107 #endif /* !WIN32 */
10108 }
10109
10110 char *
10111 CmailMsg()
10112 {
10113 #if WIN32
10114     return NULL;
10115 #else
10116     int  prependComma = 0;
10117     char number[5];
10118     char string[MSG_SIZ];       /* Space for game-list */
10119     int  i;
10120     
10121     if (!cmailMsgLoaded) return "";
10122
10123     if (cmailMailedMove) {
10124         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10125     } else {
10126         /* Create a list of games left */
10127         sprintf(string, "[");
10128         for (i = 0; i < nCmailGames; i ++) {
10129             if (! (   cmailMoveRegistered[i]
10130                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10131                 if (prependComma) {
10132                     sprintf(number, ",%d", i + 1);
10133                 } else {
10134                     sprintf(number, "%d", i + 1);
10135                     prependComma = 1;
10136                 }
10137                 
10138                 strcat(string, number);
10139             }
10140         }
10141         strcat(string, "]");
10142
10143         if (nCmailMovesRegistered + nCmailResults == 0) {
10144             switch (nCmailGames) {
10145               case 1:
10146                 sprintf(cmailMsg,
10147                         _("Still need to make move for game\n"));
10148                 break;
10149                 
10150               case 2:
10151                 sprintf(cmailMsg,
10152                         _("Still need to make moves for both games\n"));
10153                 break;
10154                 
10155               default:
10156                 sprintf(cmailMsg,
10157                         _("Still need to make moves for all %d games\n"),
10158                         nCmailGames);
10159                 break;
10160             }
10161         } else {
10162             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10163               case 1:
10164                 sprintf(cmailMsg,
10165                         _("Still need to make a move for game %s\n"),
10166                         string);
10167                 break;
10168                 
10169               case 0:
10170                 if (nCmailResults == nCmailGames) {
10171                     sprintf(cmailMsg, _("No unfinished games\n"));
10172                 } else {
10173                     sprintf(cmailMsg, _("Ready to send mail\n"));
10174                 }
10175                 break;
10176                 
10177               default:
10178                 sprintf(cmailMsg,
10179                         _("Still need to make moves for games %s\n"),
10180                         string);
10181             }
10182         }
10183     }
10184     return cmailMsg;
10185 #endif /* WIN32 */
10186 }
10187
10188 void
10189 ResetGameEvent()
10190 {
10191     if (gameMode == Training)
10192       SetTrainingModeOff();
10193
10194     Reset(TRUE, TRUE);
10195     cmailMsgLoaded = FALSE;
10196     if (appData.icsActive) {
10197       SendToICS(ics_prefix);
10198       SendToICS("refresh\n");
10199     }
10200 }
10201
10202 void
10203 ExitEvent(status)
10204      int status;
10205 {
10206     exiting++;
10207     if (exiting > 2) {
10208       /* Give up on clean exit */
10209       exit(status);
10210     }
10211     if (exiting > 1) {
10212       /* Keep trying for clean exit */
10213       return;
10214     }
10215
10216     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10217
10218     if (telnetISR != NULL) {
10219       RemoveInputSource(telnetISR);
10220     }
10221     if (icsPR != NoProc) {
10222       DestroyChildProcess(icsPR, TRUE);
10223     }
10224 #if 0
10225     /* Save game if resource set and not already saved by GameEnds() */
10226     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10227                              && forwardMostMove > 0) {
10228       if (*appData.saveGameFile != NULLCHAR) {
10229         SaveGameToFile(appData.saveGameFile, TRUE);
10230       } else if (appData.autoSaveGames) {
10231         AutoSaveGame();
10232       }
10233       if (*appData.savePositionFile != NULLCHAR) {
10234         SavePositionToFile(appData.savePositionFile);
10235       }
10236     }
10237     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10238 #else
10239     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10240     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10241 #endif
10242     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10243     /* make sure this other one finishes before killing it!                  */
10244     if(endingGame) { int count = 0;
10245         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10246         while(endingGame && count++ < 10) DoSleep(1);
10247         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10248     }
10249
10250     /* Kill off chess programs */
10251     if (first.pr != NoProc) {
10252         ExitAnalyzeMode();
10253         
10254         DoSleep( appData.delayBeforeQuit );
10255         SendToProgram("quit\n", &first);
10256         DoSleep( appData.delayAfterQuit );
10257         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10258     }
10259     if (second.pr != NoProc) {
10260         DoSleep( appData.delayBeforeQuit );
10261         SendToProgram("quit\n", &second);
10262         DoSleep( appData.delayAfterQuit );
10263         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10264     }
10265     if (first.isr != NULL) {
10266         RemoveInputSource(first.isr);
10267     }
10268     if (second.isr != NULL) {
10269         RemoveInputSource(second.isr);
10270     }
10271
10272     ShutDownFrontEnd();
10273     exit(status);
10274 }
10275
10276 void
10277 PauseEvent()
10278 {
10279     if (appData.debugMode)
10280         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10281     if (pausing) {
10282         pausing = FALSE;
10283         ModeHighlight();
10284         if (gameMode == MachinePlaysWhite ||
10285             gameMode == MachinePlaysBlack) {
10286             StartClocks();
10287         } else {
10288             DisplayBothClocks();
10289         }
10290         if (gameMode == PlayFromGameFile) {
10291             if (appData.timeDelay >= 0) 
10292                 AutoPlayGameLoop();
10293         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10294             Reset(FALSE, TRUE);
10295             SendToICS(ics_prefix);
10296             SendToICS("refresh\n");
10297         } else if (currentMove < forwardMostMove) {
10298             ForwardInner(forwardMostMove);
10299         }
10300         pauseExamInvalid = FALSE;
10301     } else {
10302         switch (gameMode) {
10303           default:
10304             return;
10305           case IcsExamining:
10306             pauseExamForwardMostMove = forwardMostMove;
10307             pauseExamInvalid = FALSE;
10308             /* fall through */
10309           case IcsObserving:
10310           case IcsPlayingWhite:
10311           case IcsPlayingBlack:
10312             pausing = TRUE;
10313             ModeHighlight();
10314             return;
10315           case PlayFromGameFile:
10316             (void) StopLoadGameTimer();
10317             pausing = TRUE;
10318             ModeHighlight();
10319             break;
10320           case BeginningOfGame:
10321             if (appData.icsActive) return;
10322             /* else fall through */
10323           case MachinePlaysWhite:
10324           case MachinePlaysBlack:
10325           case TwoMachinesPlay:
10326             if (forwardMostMove == 0)
10327               return;           /* don't pause if no one has moved */
10328             if ((gameMode == MachinePlaysWhite &&
10329                  !WhiteOnMove(forwardMostMove)) ||
10330                 (gameMode == MachinePlaysBlack &&
10331                  WhiteOnMove(forwardMostMove))) {
10332                 StopClocks();
10333             }
10334             pausing = TRUE;
10335             ModeHighlight();
10336             break;
10337         }
10338     }
10339 }
10340
10341 void
10342 EditCommentEvent()
10343 {
10344     char title[MSG_SIZ];
10345
10346     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10347         strcpy(title, _("Edit comment"));
10348     } else {
10349         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10350                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10351                 parseList[currentMove - 1]);
10352     }
10353
10354     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10355 }
10356
10357
10358 void
10359 EditTagsEvent()
10360 {
10361     char *tags = PGNTags(&gameInfo);
10362     EditTagsPopUp(tags);
10363     free(tags);
10364 }
10365
10366 void
10367 AnalyzeModeEvent()
10368 {
10369     if (appData.noChessProgram || gameMode == AnalyzeMode)
10370       return;
10371
10372     if (gameMode != AnalyzeFile) {
10373         if (!appData.icsEngineAnalyze) {
10374                EditGameEvent();
10375                if (gameMode != EditGame) return;
10376         }
10377         ResurrectChessProgram();
10378         SendToProgram("analyze\n", &first);
10379         first.analyzing = TRUE;
10380         /*first.maybeThinking = TRUE;*/
10381         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10382         AnalysisPopUp(_("Analysis"),
10383                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10384     }
10385     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10386     pausing = FALSE;
10387     ModeHighlight();
10388     SetGameInfo();
10389
10390     StartAnalysisClock();
10391     GetTimeMark(&lastNodeCountTime);
10392     lastNodeCount = 0;
10393 }
10394
10395 void
10396 AnalyzeFileEvent()
10397 {
10398     if (appData.noChessProgram || gameMode == AnalyzeFile)
10399       return;
10400
10401     if (gameMode != AnalyzeMode) {
10402         EditGameEvent();
10403         if (gameMode != EditGame) return;
10404         ResurrectChessProgram();
10405         SendToProgram("analyze\n", &first);
10406         first.analyzing = TRUE;
10407         /*first.maybeThinking = TRUE;*/
10408         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10409         AnalysisPopUp(_("Analysis"),
10410                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10411     }
10412     gameMode = AnalyzeFile;
10413     pausing = FALSE;
10414     ModeHighlight();
10415     SetGameInfo();
10416
10417     StartAnalysisClock();
10418     GetTimeMark(&lastNodeCountTime);
10419     lastNodeCount = 0;
10420 }
10421
10422 void
10423 MachineWhiteEvent()
10424 {
10425     char buf[MSG_SIZ];
10426     char *bookHit = NULL;
10427
10428     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10429       return;
10430
10431
10432     if (gameMode == PlayFromGameFile || 
10433         gameMode == TwoMachinesPlay  || 
10434         gameMode == Training         || 
10435         gameMode == AnalyzeMode      || 
10436         gameMode == EndOfGame)
10437         EditGameEvent();
10438
10439     if (gameMode == EditPosition) 
10440         EditPositionDone();
10441
10442     if (!WhiteOnMove(currentMove)) {
10443         DisplayError(_("It is not White's turn"), 0);
10444         return;
10445     }
10446   
10447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10448       ExitAnalyzeMode();
10449
10450     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10451         gameMode == AnalyzeFile)
10452         TruncateGame();
10453
10454     ResurrectChessProgram();    /* in case it isn't running */
10455     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10456         gameMode = MachinePlaysWhite;
10457         ResetClocks();
10458     } else
10459     gameMode = MachinePlaysWhite;
10460     pausing = FALSE;
10461     ModeHighlight();
10462     SetGameInfo();
10463     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10464     DisplayTitle(buf);
10465     if (first.sendName) {
10466       sprintf(buf, "name %s\n", gameInfo.black);
10467       SendToProgram(buf, &first);
10468     }
10469     if (first.sendTime) {
10470       if (first.useColors) {
10471         SendToProgram("black\n", &first); /*gnu kludge*/
10472       }
10473       SendTimeRemaining(&first, TRUE);
10474     }
10475     if (first.useColors) {
10476       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10477     }
10478     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10479     SetMachineThinkingEnables();
10480     first.maybeThinking = TRUE;
10481     StartClocks();
10482
10483     if (appData.autoFlipView && !flipView) {
10484       flipView = !flipView;
10485       DrawPosition(FALSE, NULL);
10486       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10487     }
10488
10489     if(bookHit) { // [HGM] book: simulate book reply
10490         static char bookMove[MSG_SIZ]; // a bit generous?
10491
10492         programStats.nodes = programStats.depth = programStats.time = 
10493         programStats.score = programStats.got_only_move = 0;
10494         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10495
10496         strcpy(bookMove, "move ");
10497         strcat(bookMove, bookHit);
10498         HandleMachineMove(bookMove, &first);
10499     }
10500 }
10501
10502 void
10503 MachineBlackEvent()
10504 {
10505     char buf[MSG_SIZ];
10506    char *bookHit = NULL;
10507
10508     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10509         return;
10510
10511
10512     if (gameMode == PlayFromGameFile || 
10513         gameMode == TwoMachinesPlay  || 
10514         gameMode == Training         || 
10515         gameMode == AnalyzeMode      || 
10516         gameMode == EndOfGame)
10517         EditGameEvent();
10518
10519     if (gameMode == EditPosition) 
10520         EditPositionDone();
10521
10522     if (WhiteOnMove(currentMove)) {
10523         DisplayError(_("It is not Black's turn"), 0);
10524         return;
10525     }
10526     
10527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10528       ExitAnalyzeMode();
10529
10530     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10531         gameMode == AnalyzeFile)
10532         TruncateGame();
10533
10534     ResurrectChessProgram();    /* in case it isn't running */
10535     gameMode = MachinePlaysBlack;
10536     pausing = FALSE;
10537     ModeHighlight();
10538     SetGameInfo();
10539     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10540     DisplayTitle(buf);
10541     if (first.sendName) {
10542       sprintf(buf, "name %s\n", gameInfo.white);
10543       SendToProgram(buf, &first);
10544     }
10545     if (first.sendTime) {
10546       if (first.useColors) {
10547         SendToProgram("white\n", &first); /*gnu kludge*/
10548       }
10549       SendTimeRemaining(&first, FALSE);
10550     }
10551     if (first.useColors) {
10552       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10553     }
10554     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10555     SetMachineThinkingEnables();
10556     first.maybeThinking = TRUE;
10557     StartClocks();
10558
10559     if (appData.autoFlipView && flipView) {
10560       flipView = !flipView;
10561       DrawPosition(FALSE, NULL);
10562       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10563     }
10564     if(bookHit) { // [HGM] book: simulate book reply
10565         static char bookMove[MSG_SIZ]; // a bit generous?
10566
10567         programStats.nodes = programStats.depth = programStats.time = 
10568         programStats.score = programStats.got_only_move = 0;
10569         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10570
10571         strcpy(bookMove, "move ");
10572         strcat(bookMove, bookHit);
10573         HandleMachineMove(bookMove, &first);
10574     }
10575 }
10576
10577
10578 void
10579 DisplayTwoMachinesTitle()
10580 {
10581     char buf[MSG_SIZ];
10582     if (appData.matchGames > 0) {
10583         if (first.twoMachinesColor[0] == 'w') {
10584             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10585                     gameInfo.white, gameInfo.black,
10586                     first.matchWins, second.matchWins,
10587                     matchGame - 1 - (first.matchWins + second.matchWins));
10588         } else {
10589             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10590                     gameInfo.white, gameInfo.black,
10591                     second.matchWins, first.matchWins,
10592                     matchGame - 1 - (first.matchWins + second.matchWins));
10593         }
10594     } else {
10595         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10596     }
10597     DisplayTitle(buf);
10598 }
10599
10600 void
10601 TwoMachinesEvent P((void))
10602 {
10603     int i;
10604     char buf[MSG_SIZ];
10605     ChessProgramState *onmove;
10606     char *bookHit = NULL;
10607     
10608     if (appData.noChessProgram) return;
10609
10610     switch (gameMode) {
10611       case TwoMachinesPlay:
10612         return;
10613       case MachinePlaysWhite:
10614       case MachinePlaysBlack:
10615         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10616             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10617             return;
10618         }
10619         /* fall through */
10620       case BeginningOfGame:
10621       case PlayFromGameFile:
10622       case EndOfGame:
10623         EditGameEvent();
10624         if (gameMode != EditGame) return;
10625         break;
10626       case EditPosition:
10627         EditPositionDone();
10628         break;
10629       case AnalyzeMode:
10630       case AnalyzeFile:
10631         ExitAnalyzeMode();
10632         break;
10633       case EditGame:
10634       default:
10635         break;
10636     }
10637
10638     forwardMostMove = currentMove;
10639     ResurrectChessProgram();    /* in case first program isn't running */
10640
10641     if (second.pr == NULL) {
10642         StartChessProgram(&second);
10643         if (second.protocolVersion == 1) {
10644           TwoMachinesEventIfReady();
10645         } else {
10646           /* kludge: allow timeout for initial "feature" command */
10647           FreezeUI();
10648           DisplayMessage("", _("Starting second chess program"));
10649           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10650         }
10651         return;
10652     }
10653     DisplayMessage("", "");
10654     InitChessProgram(&second, FALSE);
10655     SendToProgram("force\n", &second);
10656     if (startedFromSetupPosition) {
10657         SendBoard(&second, backwardMostMove);
10658     if (appData.debugMode) {
10659         fprintf(debugFP, "Two Machines\n");
10660     }
10661     }
10662     for (i = backwardMostMove; i < forwardMostMove; i++) {
10663         SendMoveToProgram(i, &second);
10664     }
10665
10666     gameMode = TwoMachinesPlay;
10667     pausing = FALSE;
10668     ModeHighlight();
10669     SetGameInfo();
10670     DisplayTwoMachinesTitle();
10671     firstMove = TRUE;
10672     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10673         onmove = &first;
10674     } else {
10675         onmove = &second;
10676     }
10677
10678     SendToProgram(first.computerString, &first);
10679     if (first.sendName) {
10680       sprintf(buf, "name %s\n", second.tidy);
10681       SendToProgram(buf, &first);
10682     }
10683     SendToProgram(second.computerString, &second);
10684     if (second.sendName) {
10685       sprintf(buf, "name %s\n", first.tidy);
10686       SendToProgram(buf, &second);
10687     }
10688
10689     ResetClocks();
10690     if (!first.sendTime || !second.sendTime) {
10691         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10692         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10693     }
10694     if (onmove->sendTime) {
10695       if (onmove->useColors) {
10696         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10697       }
10698       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10699     }
10700     if (onmove->useColors) {
10701       SendToProgram(onmove->twoMachinesColor, onmove);
10702     }
10703     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10704 //    SendToProgram("go\n", onmove);
10705     onmove->maybeThinking = TRUE;
10706     SetMachineThinkingEnables();
10707
10708     StartClocks();
10709
10710     if(bookHit) { // [HGM] book: simulate book reply
10711         static char bookMove[MSG_SIZ]; // a bit generous?
10712
10713         programStats.nodes = programStats.depth = programStats.time = 
10714         programStats.score = programStats.got_only_move = 0;
10715         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10716
10717         strcpy(bookMove, "move ");
10718         strcat(bookMove, bookHit);
10719         HandleMachineMove(bookMove, &first);
10720     }
10721 }
10722
10723 void
10724 TrainingEvent()
10725 {
10726     if (gameMode == Training) {
10727       SetTrainingModeOff();
10728       gameMode = PlayFromGameFile;
10729       DisplayMessage("", _("Training mode off"));
10730     } else {
10731       gameMode = Training;
10732       animateTraining = appData.animate;
10733
10734       /* make sure we are not already at the end of the game */
10735       if (currentMove < forwardMostMove) {
10736         SetTrainingModeOn();
10737         DisplayMessage("", _("Training mode on"));
10738       } else {
10739         gameMode = PlayFromGameFile;
10740         DisplayError(_("Already at end of game"), 0);
10741       }
10742     }
10743     ModeHighlight();
10744 }
10745
10746 void
10747 IcsClientEvent()
10748 {
10749     if (!appData.icsActive) return;
10750     switch (gameMode) {
10751       case IcsPlayingWhite:
10752       case IcsPlayingBlack:
10753       case IcsObserving:
10754       case IcsIdle:
10755       case BeginningOfGame:
10756       case IcsExamining:
10757         return;
10758
10759       case EditGame:
10760         break;
10761
10762       case EditPosition:
10763         EditPositionDone();
10764         break;
10765
10766       case AnalyzeMode:
10767       case AnalyzeFile:
10768         ExitAnalyzeMode();
10769         break;
10770         
10771       default:
10772         EditGameEvent();
10773         break;
10774     }
10775
10776     gameMode = IcsIdle;
10777     ModeHighlight();
10778     return;
10779 }
10780
10781
10782 void
10783 EditGameEvent()
10784 {
10785     int i;
10786
10787     switch (gameMode) {
10788       case Training:
10789         SetTrainingModeOff();
10790         break;
10791       case MachinePlaysWhite:
10792       case MachinePlaysBlack:
10793       case BeginningOfGame:
10794         SendToProgram("force\n", &first);
10795         SetUserThinkingEnables();
10796         break;
10797       case PlayFromGameFile:
10798         (void) StopLoadGameTimer();
10799         if (gameFileFP != NULL) {
10800             gameFileFP = NULL;
10801         }
10802         break;
10803       case EditPosition:
10804         EditPositionDone();
10805         break;
10806       case AnalyzeMode:
10807       case AnalyzeFile:
10808         ExitAnalyzeMode();
10809         SendToProgram("force\n", &first);
10810         break;
10811       case TwoMachinesPlay:
10812         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10813         ResurrectChessProgram();
10814         SetUserThinkingEnables();
10815         break;
10816       case EndOfGame:
10817         ResurrectChessProgram();
10818         break;
10819       case IcsPlayingBlack:
10820       case IcsPlayingWhite:
10821         DisplayError(_("Warning: You are still playing a game"), 0);
10822         break;
10823       case IcsObserving:
10824         DisplayError(_("Warning: You are still observing a game"), 0);
10825         break;
10826       case IcsExamining:
10827         DisplayError(_("Warning: You are still examining a game"), 0);
10828         break;
10829       case IcsIdle:
10830         break;
10831       case EditGame:
10832       default:
10833         return;
10834     }
10835     
10836     pausing = FALSE;
10837     StopClocks();
10838     first.offeredDraw = second.offeredDraw = 0;
10839
10840     if (gameMode == PlayFromGameFile) {
10841         whiteTimeRemaining = timeRemaining[0][currentMove];
10842         blackTimeRemaining = timeRemaining[1][currentMove];
10843         DisplayTitle("");
10844     }
10845
10846     if (gameMode == MachinePlaysWhite ||
10847         gameMode == MachinePlaysBlack ||
10848         gameMode == TwoMachinesPlay ||
10849         gameMode == EndOfGame) {
10850         i = forwardMostMove;
10851         while (i > currentMove) {
10852             SendToProgram("undo\n", &first);
10853             i--;
10854         }
10855         whiteTimeRemaining = timeRemaining[0][currentMove];
10856         blackTimeRemaining = timeRemaining[1][currentMove];
10857         DisplayBothClocks();
10858         if (whiteFlag || blackFlag) {
10859             whiteFlag = blackFlag = 0;
10860         }
10861         DisplayTitle("");
10862     }           
10863     
10864     gameMode = EditGame;
10865     ModeHighlight();
10866     SetGameInfo();
10867 }
10868
10869
10870 void
10871 EditPositionEvent()
10872 {
10873     if (gameMode == EditPosition) {
10874         EditGameEvent();
10875         return;
10876     }
10877     
10878     EditGameEvent();
10879     if (gameMode != EditGame) return;
10880     
10881     gameMode = EditPosition;
10882     ModeHighlight();
10883     SetGameInfo();
10884     if (currentMove > 0)
10885       CopyBoard(boards[0], boards[currentMove]);
10886     
10887     blackPlaysFirst = !WhiteOnMove(currentMove);
10888     ResetClocks();
10889     currentMove = forwardMostMove = backwardMostMove = 0;
10890     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10891     DisplayMove(-1);
10892 }
10893
10894 void
10895 ExitAnalyzeMode()
10896 {
10897     /* [DM] icsEngineAnalyze - possible call from other functions */
10898     if (appData.icsEngineAnalyze) {
10899         appData.icsEngineAnalyze = FALSE;
10900
10901         DisplayMessage("",_("Close ICS engine analyze..."));
10902     }
10903     if (first.analysisSupport && first.analyzing) {
10904       SendToProgram("exit\n", &first);
10905       first.analyzing = FALSE;
10906     }
10907     AnalysisPopDown();
10908     thinkOutput[0] = NULLCHAR;
10909 }
10910
10911 void
10912 EditPositionDone()
10913 {
10914     startedFromSetupPosition = TRUE;
10915     InitChessProgram(&first, FALSE);
10916     SendToProgram("force\n", &first);
10917     if (blackPlaysFirst) {
10918         strcpy(moveList[0], "");
10919         strcpy(parseList[0], "");
10920         currentMove = forwardMostMove = backwardMostMove = 1;
10921         CopyBoard(boards[1], boards[0]);
10922         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10923         { int i;
10924           epStatus[1] = epStatus[0];
10925           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10926         }
10927     } else {
10928         currentMove = forwardMostMove = backwardMostMove = 0;
10929     }
10930     SendBoard(&first, forwardMostMove);
10931     if (appData.debugMode) {
10932         fprintf(debugFP, "EditPosDone\n");
10933     }
10934     DisplayTitle("");
10935     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10936     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10937     gameMode = EditGame;
10938     ModeHighlight();
10939     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10940     ClearHighlights(); /* [AS] */
10941 }
10942
10943 /* Pause for `ms' milliseconds */
10944 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10945 void
10946 TimeDelay(ms)
10947      long ms;
10948 {
10949     TimeMark m1, m2;
10950
10951     GetTimeMark(&m1);
10952     do {
10953         GetTimeMark(&m2);
10954     } while (SubtractTimeMarks(&m2, &m1) < ms);
10955 }
10956
10957 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10958 void
10959 SendMultiLineToICS(buf)
10960      char *buf;
10961 {
10962     char temp[MSG_SIZ+1], *p;
10963     int len;
10964
10965     len = strlen(buf);
10966     if (len > MSG_SIZ)
10967       len = MSG_SIZ;
10968   
10969     strncpy(temp, buf, len);
10970     temp[len] = 0;
10971
10972     p = temp;
10973     while (*p) {
10974         if (*p == '\n' || *p == '\r')
10975           *p = ' ';
10976         ++p;
10977     }
10978
10979     strcat(temp, "\n");
10980     SendToICS(temp);
10981     SendToPlayer(temp, strlen(temp));
10982 }
10983
10984 void
10985 SetWhiteToPlayEvent()
10986 {
10987     if (gameMode == EditPosition) {
10988         blackPlaysFirst = FALSE;
10989         DisplayBothClocks();    /* works because currentMove is 0 */
10990     } else if (gameMode == IcsExamining) {
10991         SendToICS(ics_prefix);
10992         SendToICS("tomove white\n");
10993     }
10994 }
10995
10996 void
10997 SetBlackToPlayEvent()
10998 {
10999     if (gameMode == EditPosition) {
11000         blackPlaysFirst = TRUE;
11001         currentMove = 1;        /* kludge */
11002         DisplayBothClocks();
11003         currentMove = 0;
11004     } else if (gameMode == IcsExamining) {
11005         SendToICS(ics_prefix);
11006         SendToICS("tomove black\n");
11007     }
11008 }
11009
11010 void
11011 EditPositionMenuEvent(selection, x, y)
11012      ChessSquare selection;
11013      int x, y;
11014 {
11015     char buf[MSG_SIZ];
11016     ChessSquare piece = boards[0][y][x];
11017
11018     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11019
11020     switch (selection) {
11021       case ClearBoard:
11022         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11023             SendToICS(ics_prefix);
11024             SendToICS("bsetup clear\n");
11025         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11026             SendToICS(ics_prefix);
11027             SendToICS("clearboard\n");
11028         } else {
11029             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11030                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11031                 for (y = 0; y < BOARD_HEIGHT; y++) {
11032                     if (gameMode == IcsExamining) {
11033                         if (boards[currentMove][y][x] != EmptySquare) {
11034                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11035                                     AAA + x, ONE + y);
11036                             SendToICS(buf);
11037                         }
11038                     } else {
11039                         boards[0][y][x] = p;
11040                     }
11041                 }
11042             }
11043         }
11044         if (gameMode == EditPosition) {
11045             DrawPosition(FALSE, boards[0]);
11046         }
11047         break;
11048
11049       case WhitePlay:
11050         SetWhiteToPlayEvent();
11051         break;
11052
11053       case BlackPlay:
11054         SetBlackToPlayEvent();
11055         break;
11056
11057       case EmptySquare:
11058         if (gameMode == IcsExamining) {
11059             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11060             SendToICS(buf);
11061         } else {
11062             boards[0][y][x] = EmptySquare;
11063             DrawPosition(FALSE, boards[0]);
11064         }
11065         break;
11066
11067       case PromotePiece:
11068         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11069            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11070             selection = (ChessSquare) (PROMOTED piece);
11071         } else if(piece == EmptySquare) selection = WhiteSilver;
11072         else selection = (ChessSquare)((int)piece - 1);
11073         goto defaultlabel;
11074
11075       case DemotePiece:
11076         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11077            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11078             selection = (ChessSquare) (DEMOTED piece);
11079         } else if(piece == EmptySquare) selection = BlackSilver;
11080         else selection = (ChessSquare)((int)piece + 1);       
11081         goto defaultlabel;
11082
11083       case WhiteQueen:
11084       case BlackQueen:
11085         if(gameInfo.variant == VariantShatranj ||
11086            gameInfo.variant == VariantXiangqi  ||
11087            gameInfo.variant == VariantCourier    )
11088             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11089         goto defaultlabel;
11090
11091       case WhiteKing:
11092       case BlackKing:
11093         if(gameInfo.variant == VariantXiangqi)
11094             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11095         if(gameInfo.variant == VariantKnightmate)
11096             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11097       default:
11098         defaultlabel:
11099         if (gameMode == IcsExamining) {
11100             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11101                     PieceToChar(selection), AAA + x, ONE + y);
11102             SendToICS(buf);
11103         } else {
11104             boards[0][y][x] = selection;
11105             DrawPosition(FALSE, boards[0]);
11106         }
11107         break;
11108     }
11109 }
11110
11111
11112 void
11113 DropMenuEvent(selection, x, y)
11114      ChessSquare selection;
11115      int x, y;
11116 {
11117     ChessMove moveType;
11118
11119     switch (gameMode) {
11120       case IcsPlayingWhite:
11121       case MachinePlaysBlack:
11122         if (!WhiteOnMove(currentMove)) {
11123             DisplayMoveError(_("It is Black's turn"));
11124             return;
11125         }
11126         moveType = WhiteDrop;
11127         break;
11128       case IcsPlayingBlack:
11129       case MachinePlaysWhite:
11130         if (WhiteOnMove(currentMove)) {
11131             DisplayMoveError(_("It is White's turn"));
11132             return;
11133         }
11134         moveType = BlackDrop;
11135         break;
11136       case EditGame:
11137         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11138         break;
11139       default:
11140         return;
11141     }
11142
11143     if (moveType == BlackDrop && selection < BlackPawn) {
11144       selection = (ChessSquare) ((int) selection
11145                                  + (int) BlackPawn - (int) WhitePawn);
11146     }
11147     if (boards[currentMove][y][x] != EmptySquare) {
11148         DisplayMoveError(_("That square is occupied"));
11149         return;
11150     }
11151
11152     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11153 }
11154
11155 void
11156 AcceptEvent()
11157 {
11158     /* Accept a pending offer of any kind from opponent */
11159     
11160     if (appData.icsActive) {
11161         SendToICS(ics_prefix);
11162         SendToICS("accept\n");
11163     } else if (cmailMsgLoaded) {
11164         if (currentMove == cmailOldMove &&
11165             commentList[cmailOldMove] != NULL &&
11166             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11167                    "Black offers a draw" : "White offers a draw")) {
11168             TruncateGame();
11169             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11170             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11171         } else {
11172             DisplayError(_("There is no pending offer on this move"), 0);
11173             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11174         }
11175     } else {
11176         /* Not used for offers from chess program */
11177     }
11178 }
11179
11180 void
11181 DeclineEvent()
11182 {
11183     /* Decline a pending offer of any kind from opponent */
11184     
11185     if (appData.icsActive) {
11186         SendToICS(ics_prefix);
11187         SendToICS("decline\n");
11188     } else if (cmailMsgLoaded) {
11189         if (currentMove == cmailOldMove &&
11190             commentList[cmailOldMove] != NULL &&
11191             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11192                    "Black offers a draw" : "White offers a draw")) {
11193 #ifdef NOTDEF
11194             AppendComment(cmailOldMove, "Draw declined");
11195             DisplayComment(cmailOldMove - 1, "Draw declined");
11196 #endif /*NOTDEF*/
11197         } else {
11198             DisplayError(_("There is no pending offer on this move"), 0);
11199         }
11200     } else {
11201         /* Not used for offers from chess program */
11202     }
11203 }
11204
11205 void
11206 RematchEvent()
11207 {
11208     /* Issue ICS rematch command */
11209     if (appData.icsActive) {
11210         SendToICS(ics_prefix);
11211         SendToICS("rematch\n");
11212     }
11213 }
11214
11215 void
11216 CallFlagEvent()
11217 {
11218     /* Call your opponent's flag (claim a win on time) */
11219     if (appData.icsActive) {
11220         SendToICS(ics_prefix);
11221         SendToICS("flag\n");
11222     } else {
11223         switch (gameMode) {
11224           default:
11225             return;
11226           case MachinePlaysWhite:
11227             if (whiteFlag) {
11228                 if (blackFlag)
11229                   GameEnds(GameIsDrawn, "Both players ran out of time",
11230                            GE_PLAYER);
11231                 else
11232                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11233             } else {
11234                 DisplayError(_("Your opponent is not out of time"), 0);
11235             }
11236             break;
11237           case MachinePlaysBlack:
11238             if (blackFlag) {
11239                 if (whiteFlag)
11240                   GameEnds(GameIsDrawn, "Both players ran out of time",
11241                            GE_PLAYER);
11242                 else
11243                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11244             } else {
11245                 DisplayError(_("Your opponent is not out of time"), 0);
11246             }
11247             break;
11248         }
11249     }
11250 }
11251
11252 void
11253 DrawEvent()
11254 {
11255     /* Offer draw or accept pending draw offer from opponent */
11256     
11257     if (appData.icsActive) {
11258         /* Note: tournament rules require draw offers to be
11259            made after you make your move but before you punch
11260            your clock.  Currently ICS doesn't let you do that;
11261            instead, you immediately punch your clock after making
11262            a move, but you can offer a draw at any time. */
11263         
11264         SendToICS(ics_prefix);
11265         SendToICS("draw\n");
11266     } else if (cmailMsgLoaded) {
11267         if (currentMove == cmailOldMove &&
11268             commentList[cmailOldMove] != NULL &&
11269             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11270                    "Black offers a draw" : "White offers a draw")) {
11271             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11272             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11273         } else if (currentMove == cmailOldMove + 1) {
11274             char *offer = WhiteOnMove(cmailOldMove) ?
11275               "White offers a draw" : "Black offers a draw";
11276             AppendComment(currentMove, offer);
11277             DisplayComment(currentMove - 1, offer);
11278             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11279         } else {
11280             DisplayError(_("You must make your move before offering a draw"), 0);
11281             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11282         }
11283     } else if (first.offeredDraw) {
11284         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11285     } else {
11286         if (first.sendDrawOffers) {
11287             SendToProgram("draw\n", &first);
11288             userOfferedDraw = TRUE;
11289         }
11290     }
11291 }
11292
11293 void
11294 AdjournEvent()
11295 {
11296     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11297     
11298     if (appData.icsActive) {
11299         SendToICS(ics_prefix);
11300         SendToICS("adjourn\n");
11301     } else {
11302         /* Currently GNU Chess doesn't offer or accept Adjourns */
11303     }
11304 }
11305
11306
11307 void
11308 AbortEvent()
11309 {
11310     /* Offer Abort or accept pending Abort offer from opponent */
11311     
11312     if (appData.icsActive) {
11313         SendToICS(ics_prefix);
11314         SendToICS("abort\n");
11315     } else {
11316         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11317     }
11318 }
11319
11320 void
11321 ResignEvent()
11322 {
11323     /* Resign.  You can do this even if it's not your turn. */
11324     
11325     if (appData.icsActive) {
11326         SendToICS(ics_prefix);
11327         SendToICS("resign\n");
11328     } else {
11329         switch (gameMode) {
11330           case MachinePlaysWhite:
11331             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11332             break;
11333           case MachinePlaysBlack:
11334             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11335             break;
11336           case EditGame:
11337             if (cmailMsgLoaded) {
11338                 TruncateGame();
11339                 if (WhiteOnMove(cmailOldMove)) {
11340                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11341                 } else {
11342                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11343                 }
11344                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11345             }
11346             break;
11347           default:
11348             break;
11349         }
11350     }
11351 }
11352
11353
11354 void
11355 StopObservingEvent()
11356 {
11357     /* Stop observing current games */
11358     SendToICS(ics_prefix);
11359     SendToICS("unobserve\n");
11360 }
11361
11362 void
11363 StopExaminingEvent()
11364 {
11365     /* Stop observing current game */
11366     SendToICS(ics_prefix);
11367     SendToICS("unexamine\n");
11368 }
11369
11370 void
11371 ForwardInner(target)
11372      int target;
11373 {
11374     int limit;
11375
11376     if (appData.debugMode)
11377         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11378                 target, currentMove, forwardMostMove);
11379
11380     if (gameMode == EditPosition)
11381       return;
11382
11383     if (gameMode == PlayFromGameFile && !pausing)
11384       PauseEvent();
11385     
11386     if (gameMode == IcsExamining && pausing)
11387       limit = pauseExamForwardMostMove;
11388     else
11389       limit = forwardMostMove;
11390     
11391     if (target > limit) target = limit;
11392
11393     if (target > 0 && moveList[target - 1][0]) {
11394         int fromX, fromY, toX, toY;
11395         toX = moveList[target - 1][2] - AAA;
11396         toY = moveList[target - 1][3] - ONE;
11397         if (moveList[target - 1][1] == '@') {
11398             if (appData.highlightLastMove) {
11399                 SetHighlights(-1, -1, toX, toY);
11400             }
11401         } else {
11402             fromX = moveList[target - 1][0] - AAA;
11403             fromY = moveList[target - 1][1] - ONE;
11404             if (target == currentMove + 1) {
11405                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11406             }
11407             if (appData.highlightLastMove) {
11408                 SetHighlights(fromX, fromY, toX, toY);
11409             }
11410         }
11411     }
11412     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11413         gameMode == Training || gameMode == PlayFromGameFile || 
11414         gameMode == AnalyzeFile) {
11415         while (currentMove < target) {
11416             SendMoveToProgram(currentMove++, &first);
11417         }
11418     } else {
11419         currentMove = target;
11420     }
11421     
11422     if (gameMode == EditGame || gameMode == EndOfGame) {
11423         whiteTimeRemaining = timeRemaining[0][currentMove];
11424         blackTimeRemaining = timeRemaining[1][currentMove];
11425     }
11426     DisplayBothClocks();
11427     DisplayMove(currentMove - 1);
11428     DrawPosition(FALSE, boards[currentMove]);
11429     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11430     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11431         DisplayComment(currentMove - 1, commentList[currentMove]);
11432     }
11433 }
11434
11435
11436 void
11437 ForwardEvent()
11438 {
11439     if (gameMode == IcsExamining && !pausing) {
11440         SendToICS(ics_prefix);
11441         SendToICS("forward\n");
11442     } else {
11443         ForwardInner(currentMove + 1);
11444     }
11445 }
11446
11447 void
11448 ToEndEvent()
11449 {
11450     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11451         /* to optimze, we temporarily turn off analysis mode while we feed
11452          * the remaining moves to the engine. Otherwise we get analysis output
11453          * after each move.
11454          */ 
11455         if (first.analysisSupport) {
11456           SendToProgram("exit\nforce\n", &first);
11457           first.analyzing = FALSE;
11458         }
11459     }
11460         
11461     if (gameMode == IcsExamining && !pausing) {
11462         SendToICS(ics_prefix);
11463         SendToICS("forward 999999\n");
11464     } else {
11465         ForwardInner(forwardMostMove);
11466     }
11467
11468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11469         /* we have fed all the moves, so reactivate analysis mode */
11470         SendToProgram("analyze\n", &first);
11471         first.analyzing = TRUE;
11472         /*first.maybeThinking = TRUE;*/
11473         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11474     }
11475 }
11476
11477 void
11478 BackwardInner(target)
11479      int target;
11480 {
11481     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11482
11483     if (appData.debugMode)
11484         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11485                 target, currentMove, forwardMostMove);
11486
11487     if (gameMode == EditPosition) return;
11488     if (currentMove <= backwardMostMove) {
11489         ClearHighlights();
11490         DrawPosition(full_redraw, boards[currentMove]);
11491         return;
11492     }
11493     if (gameMode == PlayFromGameFile && !pausing)
11494       PauseEvent();
11495     
11496     if (moveList[target][0]) {
11497         int fromX, fromY, toX, toY;
11498         toX = moveList[target][2] - AAA;
11499         toY = moveList[target][3] - ONE;
11500         if (moveList[target][1] == '@') {
11501             if (appData.highlightLastMove) {
11502                 SetHighlights(-1, -1, toX, toY);
11503             }
11504         } else {
11505             fromX = moveList[target][0] - AAA;
11506             fromY = moveList[target][1] - ONE;
11507             if (target == currentMove - 1) {
11508                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11509             }
11510             if (appData.highlightLastMove) {
11511                 SetHighlights(fromX, fromY, toX, toY);
11512             }
11513         }
11514     }
11515     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11516         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11517         while (currentMove > target) {
11518             SendToProgram("undo\n", &first);
11519             currentMove--;
11520         }
11521     } else {
11522         currentMove = target;
11523     }
11524     
11525     if (gameMode == EditGame || gameMode == EndOfGame) {
11526         whiteTimeRemaining = timeRemaining[0][currentMove];
11527         blackTimeRemaining = timeRemaining[1][currentMove];
11528     }
11529     DisplayBothClocks();
11530     DisplayMove(currentMove - 1);
11531     DrawPosition(full_redraw, boards[currentMove]);
11532     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11533     // [HGM] PV info: routine tests if comment empty
11534     DisplayComment(currentMove - 1, commentList[currentMove]);
11535 }
11536
11537 void
11538 BackwardEvent()
11539 {
11540     if (gameMode == IcsExamining && !pausing) {
11541         SendToICS(ics_prefix);
11542         SendToICS("backward\n");
11543     } else {
11544         BackwardInner(currentMove - 1);
11545     }
11546 }
11547
11548 void
11549 ToStartEvent()
11550 {
11551     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11552         /* to optimze, we temporarily turn off analysis mode while we undo
11553          * all the moves. Otherwise we get analysis output after each undo.
11554          */ 
11555         if (first.analysisSupport) {
11556           SendToProgram("exit\nforce\n", &first);
11557           first.analyzing = FALSE;
11558         }
11559     }
11560
11561     if (gameMode == IcsExamining && !pausing) {
11562         SendToICS(ics_prefix);
11563         SendToICS("backward 999999\n");
11564     } else {
11565         BackwardInner(backwardMostMove);
11566     }
11567
11568     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11569         /* we have fed all the moves, so reactivate analysis mode */
11570         SendToProgram("analyze\n", &first);
11571         first.analyzing = TRUE;
11572         /*first.maybeThinking = TRUE;*/
11573         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11574     }
11575 }
11576
11577 void
11578 ToNrEvent(int to)
11579 {
11580   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11581   if (to >= forwardMostMove) to = forwardMostMove;
11582   if (to <= backwardMostMove) to = backwardMostMove;
11583   if (to < currentMove) {
11584     BackwardInner(to);
11585   } else {
11586     ForwardInner(to);
11587   }
11588 }
11589
11590 void
11591 RevertEvent()
11592 {
11593     if (gameMode != IcsExamining) {
11594         DisplayError(_("You are not examining a game"), 0);
11595         return;
11596     }
11597     if (pausing) {
11598         DisplayError(_("You can't revert while pausing"), 0);
11599         return;
11600     }
11601     SendToICS(ics_prefix);
11602     SendToICS("revert\n");
11603 }
11604
11605 void
11606 RetractMoveEvent()
11607 {
11608     switch (gameMode) {
11609       case MachinePlaysWhite:
11610       case MachinePlaysBlack:
11611         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11612             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11613             return;
11614         }
11615         if (forwardMostMove < 2) return;
11616         currentMove = forwardMostMove = forwardMostMove - 2;
11617         whiteTimeRemaining = timeRemaining[0][currentMove];
11618         blackTimeRemaining = timeRemaining[1][currentMove];
11619         DisplayBothClocks();
11620         DisplayMove(currentMove - 1);
11621         ClearHighlights();/*!! could figure this out*/
11622         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11623         SendToProgram("remove\n", &first);
11624         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11625         break;
11626
11627       case BeginningOfGame:
11628       default:
11629         break;
11630
11631       case IcsPlayingWhite:
11632       case IcsPlayingBlack:
11633         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11634             SendToICS(ics_prefix);
11635             SendToICS("takeback 2\n");
11636         } else {
11637             SendToICS(ics_prefix);
11638             SendToICS("takeback 1\n");
11639         }
11640         break;
11641     }
11642 }
11643
11644 void
11645 MoveNowEvent()
11646 {
11647     ChessProgramState *cps;
11648
11649     switch (gameMode) {
11650       case MachinePlaysWhite:
11651         if (!WhiteOnMove(forwardMostMove)) {
11652             DisplayError(_("It is your turn"), 0);
11653             return;
11654         }
11655         cps = &first;
11656         break;
11657       case MachinePlaysBlack:
11658         if (WhiteOnMove(forwardMostMove)) {
11659             DisplayError(_("It is your turn"), 0);
11660             return;
11661         }
11662         cps = &first;
11663         break;
11664       case TwoMachinesPlay:
11665         if (WhiteOnMove(forwardMostMove) ==
11666             (first.twoMachinesColor[0] == 'w')) {
11667             cps = &first;
11668         } else {
11669             cps = &second;
11670         }
11671         break;
11672       case BeginningOfGame:
11673       default:
11674         return;
11675     }
11676     SendToProgram("?\n", cps);
11677 }
11678
11679 void
11680 TruncateGameEvent()
11681 {
11682     EditGameEvent();
11683     if (gameMode != EditGame) return;
11684     TruncateGame();
11685 }
11686
11687 void
11688 TruncateGame()
11689 {
11690     if (forwardMostMove > currentMove) {
11691         if (gameInfo.resultDetails != NULL) {
11692             free(gameInfo.resultDetails);
11693             gameInfo.resultDetails = NULL;
11694             gameInfo.result = GameUnfinished;
11695         }
11696         forwardMostMove = currentMove;
11697         HistorySet(parseList, backwardMostMove, forwardMostMove,
11698                    currentMove-1);
11699     }
11700 }
11701
11702 void
11703 HintEvent()
11704 {
11705     if (appData.noChessProgram) return;
11706     switch (gameMode) {
11707       case MachinePlaysWhite:
11708         if (WhiteOnMove(forwardMostMove)) {
11709             DisplayError(_("Wait until your turn"), 0);
11710             return;
11711         }
11712         break;
11713       case BeginningOfGame:
11714       case MachinePlaysBlack:
11715         if (!WhiteOnMove(forwardMostMove)) {
11716             DisplayError(_("Wait until your turn"), 0);
11717             return;
11718         }
11719         break;
11720       default:
11721         DisplayError(_("No hint available"), 0);
11722         return;
11723     }
11724     SendToProgram("hint\n", &first);
11725     hintRequested = TRUE;
11726 }
11727
11728 void
11729 BookEvent()
11730 {
11731     if (appData.noChessProgram) return;
11732     switch (gameMode) {
11733       case MachinePlaysWhite:
11734         if (WhiteOnMove(forwardMostMove)) {
11735             DisplayError(_("Wait until your turn"), 0);
11736             return;
11737         }
11738         break;
11739       case BeginningOfGame:
11740       case MachinePlaysBlack:
11741         if (!WhiteOnMove(forwardMostMove)) {
11742             DisplayError(_("Wait until your turn"), 0);
11743             return;
11744         }
11745         break;
11746       case EditPosition:
11747         EditPositionDone();
11748         break;
11749       case TwoMachinesPlay:
11750         return;
11751       default:
11752         break;
11753     }
11754     SendToProgram("bk\n", &first);
11755     bookOutput[0] = NULLCHAR;
11756     bookRequested = TRUE;
11757 }
11758
11759 void
11760 AboutGameEvent()
11761 {
11762     char *tags = PGNTags(&gameInfo);
11763     TagsPopUp(tags, CmailMsg());
11764     free(tags);
11765 }
11766
11767 /* end button procedures */
11768
11769 void
11770 PrintPosition(fp, move)
11771      FILE *fp;
11772      int move;
11773 {
11774     int i, j;
11775     
11776     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11777         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11778             char c = PieceToChar(boards[move][i][j]);
11779             fputc(c == 'x' ? '.' : c, fp);
11780             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11781         }
11782     }
11783     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11784       fprintf(fp, "white to play\n");
11785     else
11786       fprintf(fp, "black to play\n");
11787 }
11788
11789 void
11790 PrintOpponents(fp)
11791      FILE *fp;
11792 {
11793     if (gameInfo.white != NULL) {
11794         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11795     } else {
11796         fprintf(fp, "\n");
11797     }
11798 }
11799
11800 /* Find last component of program's own name, using some heuristics */
11801 void
11802 TidyProgramName(prog, host, buf)
11803      char *prog, *host, buf[MSG_SIZ];
11804 {
11805     char *p, *q;
11806     int local = (strcmp(host, "localhost") == 0);
11807     while (!local && (p = strchr(prog, ';')) != NULL) {
11808         p++;
11809         while (*p == ' ') p++;
11810         prog = p;
11811     }
11812     if (*prog == '"' || *prog == '\'') {
11813         q = strchr(prog + 1, *prog);
11814     } else {
11815         q = strchr(prog, ' ');
11816     }
11817     if (q == NULL) q = prog + strlen(prog);
11818     p = q;
11819     while (p >= prog && *p != '/' && *p != '\\') p--;
11820     p++;
11821     if(p == prog && *p == '"') p++;
11822     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11823     memcpy(buf, p, q - p);
11824     buf[q - p] = NULLCHAR;
11825     if (!local) {
11826         strcat(buf, "@");
11827         strcat(buf, host);
11828     }
11829 }
11830
11831 char *
11832 TimeControlTagValue()
11833 {
11834     char buf[MSG_SIZ];
11835     if (!appData.clockMode) {
11836         strcpy(buf, "-");
11837     } else if (movesPerSession > 0) {
11838         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11839     } else if (timeIncrement == 0) {
11840         sprintf(buf, "%ld", timeControl/1000);
11841     } else {
11842         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11843     }
11844     return StrSave(buf);
11845 }
11846
11847 void
11848 SetGameInfo()
11849 {
11850     /* This routine is used only for certain modes */
11851     VariantClass v = gameInfo.variant;
11852     ClearGameInfo(&gameInfo);
11853     gameInfo.variant = v;
11854
11855     switch (gameMode) {
11856       case MachinePlaysWhite:
11857         gameInfo.event = StrSave( appData.pgnEventHeader );
11858         gameInfo.site = StrSave(HostName());
11859         gameInfo.date = PGNDate();
11860         gameInfo.round = StrSave("-");
11861         gameInfo.white = StrSave(first.tidy);
11862         gameInfo.black = StrSave(UserName());
11863         gameInfo.timeControl = TimeControlTagValue();
11864         break;
11865
11866       case MachinePlaysBlack:
11867         gameInfo.event = StrSave( appData.pgnEventHeader );
11868         gameInfo.site = StrSave(HostName());
11869         gameInfo.date = PGNDate();
11870         gameInfo.round = StrSave("-");
11871         gameInfo.white = StrSave(UserName());
11872         gameInfo.black = StrSave(first.tidy);
11873         gameInfo.timeControl = TimeControlTagValue();
11874         break;
11875
11876       case TwoMachinesPlay:
11877         gameInfo.event = StrSave( appData.pgnEventHeader );
11878         gameInfo.site = StrSave(HostName());
11879         gameInfo.date = PGNDate();
11880         if (matchGame > 0) {
11881             char buf[MSG_SIZ];
11882             sprintf(buf, "%d", matchGame);
11883             gameInfo.round = StrSave(buf);
11884         } else {
11885             gameInfo.round = StrSave("-");
11886         }
11887         if (first.twoMachinesColor[0] == 'w') {
11888             gameInfo.white = StrSave(first.tidy);
11889             gameInfo.black = StrSave(second.tidy);
11890         } else {
11891             gameInfo.white = StrSave(second.tidy);
11892             gameInfo.black = StrSave(first.tidy);
11893         }
11894         gameInfo.timeControl = TimeControlTagValue();
11895         break;
11896
11897       case EditGame:
11898         gameInfo.event = StrSave("Edited game");
11899         gameInfo.site = StrSave(HostName());
11900         gameInfo.date = PGNDate();
11901         gameInfo.round = StrSave("-");
11902         gameInfo.white = StrSave("-");
11903         gameInfo.black = StrSave("-");
11904         break;
11905
11906       case EditPosition:
11907         gameInfo.event = StrSave("Edited position");
11908         gameInfo.site = StrSave(HostName());
11909         gameInfo.date = PGNDate();
11910         gameInfo.round = StrSave("-");
11911         gameInfo.white = StrSave("-");
11912         gameInfo.black = StrSave("-");
11913         break;
11914
11915       case IcsPlayingWhite:
11916       case IcsPlayingBlack:
11917       case IcsObserving:
11918       case IcsExamining:
11919         break;
11920
11921       case PlayFromGameFile:
11922         gameInfo.event = StrSave("Game from non-PGN file");
11923         gameInfo.site = StrSave(HostName());
11924         gameInfo.date = PGNDate();
11925         gameInfo.round = StrSave("-");
11926         gameInfo.white = StrSave("?");
11927         gameInfo.black = StrSave("?");
11928         break;
11929
11930       default:
11931         break;
11932     }
11933 }
11934
11935 void
11936 ReplaceComment(index, text)
11937      int index;
11938      char *text;
11939 {
11940     int len;
11941
11942     while (*text == '\n') text++;
11943     len = strlen(text);
11944     while (len > 0 && text[len - 1] == '\n') len--;
11945
11946     if (commentList[index] != NULL)
11947       free(commentList[index]);
11948
11949     if (len == 0) {
11950         commentList[index] = NULL;
11951         return;
11952     }
11953     commentList[index] = (char *) malloc(len + 2);
11954     strncpy(commentList[index], text, len);
11955     commentList[index][len] = '\n';
11956     commentList[index][len + 1] = NULLCHAR;
11957 }
11958
11959 void
11960 CrushCRs(text)
11961      char *text;
11962 {
11963   char *p = text;
11964   char *q = text;
11965   char ch;
11966
11967   do {
11968     ch = *p++;
11969     if (ch == '\r') continue;
11970     *q++ = ch;
11971   } while (ch != '\0');
11972 }
11973
11974 void
11975 AppendComment(index, text)
11976      int index;
11977      char *text;
11978 {
11979     int oldlen, len;
11980     char *old;
11981
11982     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11983
11984     CrushCRs(text);
11985     while (*text == '\n') text++;
11986     len = strlen(text);
11987     while (len > 0 && text[len - 1] == '\n') len--;
11988
11989     if (len == 0) return;
11990
11991     if (commentList[index] != NULL) {
11992         old = commentList[index];
11993         oldlen = strlen(old);
11994         commentList[index] = (char *) malloc(oldlen + len + 2);
11995         strcpy(commentList[index], old);
11996         free(old);
11997         strncpy(&commentList[index][oldlen], text, len);
11998         commentList[index][oldlen + len] = '\n';
11999         commentList[index][oldlen + len + 1] = NULLCHAR;
12000     } else {
12001         commentList[index] = (char *) malloc(len + 2);
12002         strncpy(commentList[index], text, len);
12003         commentList[index][len] = '\n';
12004         commentList[index][len + 1] = NULLCHAR;
12005     }
12006 }
12007
12008 static char * FindStr( char * text, char * sub_text )
12009 {
12010     char * result = strstr( text, sub_text );
12011
12012     if( result != NULL ) {
12013         result += strlen( sub_text );
12014     }
12015
12016     return result;
12017 }
12018
12019 /* [AS] Try to extract PV info from PGN comment */
12020 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12021 char *GetInfoFromComment( int index, char * text )
12022 {
12023     char * sep = text;
12024
12025     if( text != NULL && index > 0 ) {
12026         int score = 0;
12027         int depth = 0;
12028         int time = -1, sec = 0, deci;
12029         char * s_eval = FindStr( text, "[%eval " );
12030         char * s_emt = FindStr( text, "[%emt " );
12031
12032         if( s_eval != NULL || s_emt != NULL ) {
12033             /* New style */
12034             char delim;
12035
12036             if( s_eval != NULL ) {
12037                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12038                     return text;
12039                 }
12040
12041                 if( delim != ']' ) {
12042                     return text;
12043                 }
12044             }
12045
12046             if( s_emt != NULL ) {
12047             }
12048         }
12049         else {
12050             /* We expect something like: [+|-]nnn.nn/dd */
12051             int score_lo = 0;
12052
12053             sep = strchr( text, '/' );
12054             if( sep == NULL || sep < (text+4) ) {
12055                 return text;
12056             }
12057
12058             time = -1; sec = -1; deci = -1;
12059             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12060                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12061                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12062                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12063                 return text;
12064             }
12065
12066             if( score_lo < 0 || score_lo >= 100 ) {
12067                 return text;
12068             }
12069
12070             if(sec >= 0) time = 600*time + 10*sec; else
12071             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12072
12073             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12074
12075             /* [HGM] PV time: now locate end of PV info */
12076             while( *++sep >= '0' && *sep <= '9'); // strip depth
12077             if(time >= 0)
12078             while( *++sep >= '0' && *sep <= '9'); // strip time
12079             if(sec >= 0)
12080             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12081             if(deci >= 0)
12082             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12083             while(*sep == ' ') sep++;
12084         }
12085
12086         if( depth <= 0 ) {
12087             return text;
12088         }
12089
12090         if( time < 0 ) {
12091             time = -1;
12092         }
12093
12094         pvInfoList[index-1].depth = depth;
12095         pvInfoList[index-1].score = score;
12096         pvInfoList[index-1].time  = 10*time; // centi-sec
12097     }
12098     return sep;
12099 }
12100
12101 void
12102 SendToProgram(message, cps)
12103      char *message;
12104      ChessProgramState *cps;
12105 {
12106     int count, outCount, error;
12107     char buf[MSG_SIZ];
12108
12109     if (cps->pr == NULL) return;
12110     Attention(cps);
12111     
12112     if (appData.debugMode) {
12113         TimeMark now;
12114         GetTimeMark(&now);
12115         fprintf(debugFP, "%ld >%-6s: %s", 
12116                 SubtractTimeMarks(&now, &programStartTime),
12117                 cps->which, message);
12118     }
12119     
12120     count = strlen(message);
12121     outCount = OutputToProcess(cps->pr, message, count, &error);
12122     if (outCount < count && !exiting 
12123                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12124         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12125         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12126             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12127                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12128                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12129             } else {
12130                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12131             }
12132             gameInfo.resultDetails = buf;
12133         }
12134         DisplayFatalError(buf, error, 1);
12135     }
12136 }
12137
12138 void
12139 ReceiveFromProgram(isr, closure, message, count, error)
12140      InputSourceRef isr;
12141      VOIDSTAR closure;
12142      char *message;
12143      int count;
12144      int error;
12145 {
12146     char *end_str;
12147     char buf[MSG_SIZ];
12148     ChessProgramState *cps = (ChessProgramState *)closure;
12149
12150     if (isr != cps->isr) return; /* Killed intentionally */
12151     if (count <= 0) {
12152         if (count == 0) {
12153             sprintf(buf,
12154                     _("Error: %s chess program (%s) exited unexpectedly"),
12155                     cps->which, cps->program);
12156         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12157                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12158                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12159                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12160                 } else {
12161                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12162                 }
12163                 gameInfo.resultDetails = buf;
12164             }
12165             RemoveInputSource(cps->isr);
12166             DisplayFatalError(buf, 0, 1);
12167         } else {
12168             sprintf(buf,
12169                     _("Error reading from %s chess program (%s)"),
12170                     cps->which, cps->program);
12171             RemoveInputSource(cps->isr);
12172
12173             /* [AS] Program is misbehaving badly... kill it */
12174             if( count == -2 ) {
12175                 DestroyChildProcess( cps->pr, 9 );
12176                 cps->pr = NoProc;
12177             }
12178
12179             DisplayFatalError(buf, error, 1);
12180         }
12181         return;
12182     }
12183     
12184     if ((end_str = strchr(message, '\r')) != NULL)
12185       *end_str = NULLCHAR;
12186     if ((end_str = strchr(message, '\n')) != NULL)
12187       *end_str = NULLCHAR;
12188     
12189     if (appData.debugMode) {
12190         TimeMark now; int print = 1;
12191         char *quote = ""; char c; int i;
12192
12193         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12194                 char start = message[0];
12195                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12196                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12197                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12198                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12199                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12200                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12201                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12202                         { quote = "# "; print = (appData.engineComments == 2); }
12203                 message[0] = start; // restore original message
12204         }
12205         if(print) {
12206                 GetTimeMark(&now);
12207                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12208                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12209                         quote,
12210                         message);
12211         }
12212     }
12213
12214     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12215     if (appData.icsEngineAnalyze) {
12216         if (strstr(message, "whisper") != NULL ||
12217              strstr(message, "kibitz") != NULL || 
12218             strstr(message, "tellics") != NULL) return;
12219     }
12220
12221     HandleMachineMove(message, cps);
12222 }
12223
12224
12225 void
12226 SendTimeControl(cps, mps, tc, inc, sd, st)
12227      ChessProgramState *cps;
12228      int mps, inc, sd, st;
12229      long tc;
12230 {
12231     char buf[MSG_SIZ];
12232     int seconds;
12233
12234     if( timeControl_2 > 0 ) {
12235         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12236             tc = timeControl_2;
12237         }
12238     }
12239     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12240     inc /= cps->timeOdds;
12241     st  /= cps->timeOdds;
12242
12243     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12244
12245     if (st > 0) {
12246       /* Set exact time per move, normally using st command */
12247       if (cps->stKludge) {
12248         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12249         seconds = st % 60;
12250         if (seconds == 0) {
12251           sprintf(buf, "level 1 %d\n", st/60);
12252         } else {
12253           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12254         }
12255       } else {
12256         sprintf(buf, "st %d\n", st);
12257       }
12258     } else {
12259       /* Set conventional or incremental time control, using level command */
12260       if (seconds == 0) {
12261         /* Note old gnuchess bug -- minutes:seconds used to not work.
12262            Fixed in later versions, but still avoid :seconds
12263            when seconds is 0. */
12264         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12265       } else {
12266         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12267                 seconds, inc/1000);
12268       }
12269     }
12270     SendToProgram(buf, cps);
12271
12272     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12273     /* Orthogonally, limit search to given depth */
12274     if (sd > 0) {
12275       if (cps->sdKludge) {
12276         sprintf(buf, "depth\n%d\n", sd);
12277       } else {
12278         sprintf(buf, "sd %d\n", sd);
12279       }
12280       SendToProgram(buf, cps);
12281     }
12282
12283     if(cps->nps > 0) { /* [HGM] nps */
12284         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12285         else {
12286                 sprintf(buf, "nps %d\n", cps->nps);
12287               SendToProgram(buf, cps);
12288         }
12289     }
12290 }
12291
12292 ChessProgramState *WhitePlayer()
12293 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12294 {
12295     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12296        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12297         return &second;
12298     return &first;
12299 }
12300
12301 void
12302 SendTimeRemaining(cps, machineWhite)
12303      ChessProgramState *cps;
12304      int /*boolean*/ machineWhite;
12305 {
12306     char message[MSG_SIZ];
12307     long time, otime;
12308
12309     /* Note: this routine must be called when the clocks are stopped
12310        or when they have *just* been set or switched; otherwise
12311        it will be off by the time since the current tick started.
12312     */
12313     if (machineWhite) {
12314         time = whiteTimeRemaining / 10;
12315         otime = blackTimeRemaining / 10;
12316     } else {
12317         time = blackTimeRemaining / 10;
12318         otime = whiteTimeRemaining / 10;
12319     }
12320     /* [HGM] translate opponent's time by time-odds factor */
12321     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12322     if (appData.debugMode) {
12323         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12324     }
12325
12326     if (time <= 0) time = 1;
12327     if (otime <= 0) otime = 1;
12328     
12329     sprintf(message, "time %ld\n", time);
12330     SendToProgram(message, cps);
12331
12332     sprintf(message, "otim %ld\n", otime);
12333     SendToProgram(message, cps);
12334 }
12335
12336 int
12337 BoolFeature(p, name, loc, cps)
12338      char **p;
12339      char *name;
12340      int *loc;
12341      ChessProgramState *cps;
12342 {
12343   char buf[MSG_SIZ];
12344   int len = strlen(name);
12345   int val;
12346   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12347     (*p) += len + 1;
12348     sscanf(*p, "%d", &val);
12349     *loc = (val != 0);
12350     while (**p && **p != ' ') (*p)++;
12351     sprintf(buf, "accepted %s\n", name);
12352     SendToProgram(buf, cps);
12353     return TRUE;
12354   }
12355   return FALSE;
12356 }
12357
12358 int
12359 IntFeature(p, name, loc, cps)
12360      char **p;
12361      char *name;
12362      int *loc;
12363      ChessProgramState *cps;
12364 {
12365   char buf[MSG_SIZ];
12366   int len = strlen(name);
12367   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12368     (*p) += len + 1;
12369     sscanf(*p, "%d", loc);
12370     while (**p && **p != ' ') (*p)++;
12371     sprintf(buf, "accepted %s\n", name);
12372     SendToProgram(buf, cps);
12373     return TRUE;
12374   }
12375   return FALSE;
12376 }
12377
12378 int
12379 StringFeature(p, name, loc, cps)
12380      char **p;
12381      char *name;
12382      char loc[];
12383      ChessProgramState *cps;
12384 {
12385   char buf[MSG_SIZ];
12386   int len = strlen(name);
12387   if (strncmp((*p), name, len) == 0
12388       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12389     (*p) += len + 2;
12390     sscanf(*p, "%[^\"]", loc);
12391     while (**p && **p != '\"') (*p)++;
12392     if (**p == '\"') (*p)++;
12393     sprintf(buf, "accepted %s\n", name);
12394     SendToProgram(buf, cps);
12395     return TRUE;
12396   }
12397   return FALSE;
12398 }
12399
12400 int 
12401 ParseOption(Option *opt, ChessProgramState *cps)
12402 // [HGM] options: process the string that defines an engine option, and determine
12403 // name, type, default value, and allowed value range
12404 {
12405         char *p, *q, buf[MSG_SIZ];
12406         int n, min = (-1)<<31, max = 1<<31, def;
12407
12408         if(p = strstr(opt->name, " -spin ")) {
12409             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12410             if(max < min) max = min; // enforce consistency
12411             if(def < min) def = min;
12412             if(def > max) def = max;
12413             opt->value = def;
12414             opt->min = min;
12415             opt->max = max;
12416             opt->type = Spin;
12417         } else if((p = strstr(opt->name, " -slider "))) {
12418             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12419             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12420             if(max < min) max = min; // enforce consistency
12421             if(def < min) def = min;
12422             if(def > max) def = max;
12423             opt->value = def;
12424             opt->min = min;
12425             opt->max = max;
12426             opt->type = Spin; // Slider;
12427         } else if((p = strstr(opt->name, " -string "))) {
12428             opt->textValue = p+9;
12429             opt->type = TextBox;
12430         } else if((p = strstr(opt->name, " -file "))) {
12431             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12432             opt->textValue = p+7;
12433             opt->type = TextBox; // FileName;
12434         } else if((p = strstr(opt->name, " -path "))) {
12435             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12436             opt->textValue = p+7;
12437             opt->type = TextBox; // PathName;
12438         } else if(p = strstr(opt->name, " -check ")) {
12439             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12440             opt->value = (def != 0);
12441             opt->type = CheckBox;
12442         } else if(p = strstr(opt->name, " -combo ")) {
12443             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12444             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12445             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12446             opt->value = n = 0;
12447             while(q = StrStr(q, " /// ")) {
12448                 n++; *q = 0;    // count choices, and null-terminate each of them
12449                 q += 5;
12450                 if(*q == '*') { // remember default, which is marked with * prefix
12451                     q++;
12452                     opt->value = n;
12453                 }
12454                 cps->comboList[cps->comboCnt++] = q;
12455             }
12456             cps->comboList[cps->comboCnt++] = NULL;
12457             opt->max = n + 1;
12458             opt->type = ComboBox;
12459         } else if(p = strstr(opt->name, " -button")) {
12460             opt->type = Button;
12461         } else if(p = strstr(opt->name, " -save")) {
12462             opt->type = SaveButton;
12463         } else return FALSE;
12464         *p = 0; // terminate option name
12465         // now look if the command-line options define a setting for this engine option.
12466         if(cps->optionSettings && cps->optionSettings[0])
12467             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12468         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12469                 sprintf(buf, "option %s", p);
12470                 if(p = strstr(buf, ",")) *p = 0;
12471                 strcat(buf, "\n");
12472                 SendToProgram(buf, cps);
12473         }
12474         return TRUE;
12475 }
12476
12477 void
12478 FeatureDone(cps, val)
12479      ChessProgramState* cps;
12480      int val;
12481 {
12482   DelayedEventCallback cb = GetDelayedEvent();
12483   if ((cb == InitBackEnd3 && cps == &first) ||
12484       (cb == TwoMachinesEventIfReady && cps == &second)) {
12485     CancelDelayedEvent();
12486     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12487   }
12488   cps->initDone = val;
12489 }
12490
12491 /* Parse feature command from engine */
12492 void
12493 ParseFeatures(args, cps)
12494      char* args;
12495      ChessProgramState *cps;  
12496 {
12497   char *p = args;
12498   char *q;
12499   int val;
12500   char buf[MSG_SIZ];
12501
12502   for (;;) {
12503     while (*p == ' ') p++;
12504     if (*p == NULLCHAR) return;
12505
12506     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12507     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12508     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12509     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12510     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12511     if (BoolFeature(&p, "reuse", &val, cps)) {
12512       /* Engine can disable reuse, but can't enable it if user said no */
12513       if (!val) cps->reuse = FALSE;
12514       continue;
12515     }
12516     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12517     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12518       if (gameMode == TwoMachinesPlay) {
12519         DisplayTwoMachinesTitle();
12520       } else {
12521         DisplayTitle("");
12522       }
12523       continue;
12524     }
12525     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12526     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12527     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12528     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12529     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12530     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12531     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12532     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12533     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12534     if (IntFeature(&p, "done", &val, cps)) {
12535       FeatureDone(cps, val);
12536       continue;
12537     }
12538     /* Added by Tord: */
12539     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12540     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12541     /* End of additions by Tord */
12542
12543     /* [HGM] added features: */
12544     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12545     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12546     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12547     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12548     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12549     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12550     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12551         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12552         if(cps->nrOptions >= MAX_OPTIONS) {
12553             cps->nrOptions--;
12554             sprintf(buf, "%s engine has too many options\n", cps->which);
12555             DisplayError(buf, 0);
12556         }
12557         continue;
12558     }
12559     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12560     /* End of additions by HGM */
12561
12562     /* unknown feature: complain and skip */
12563     q = p;
12564     while (*q && *q != '=') q++;
12565     sprintf(buf, "rejected %.*s\n", q-p, p);
12566     SendToProgram(buf, cps);
12567     p = q;
12568     if (*p == '=') {
12569       p++;
12570       if (*p == '\"') {
12571         p++;
12572         while (*p && *p != '\"') p++;
12573         if (*p == '\"') p++;
12574       } else {
12575         while (*p && *p != ' ') p++;
12576       }
12577     }
12578   }
12579
12580 }
12581
12582 void
12583 PeriodicUpdatesEvent(newState)
12584      int newState;
12585 {
12586     if (newState == appData.periodicUpdates)
12587       return;
12588
12589     appData.periodicUpdates=newState;
12590
12591     /* Display type changes, so update it now */
12592     DisplayAnalysis();
12593
12594     /* Get the ball rolling again... */
12595     if (newState) {
12596         AnalysisPeriodicEvent(1);
12597         StartAnalysisClock();
12598     }
12599 }
12600
12601 void
12602 PonderNextMoveEvent(newState)
12603      int newState;
12604 {
12605     if (newState == appData.ponderNextMove) return;
12606     if (gameMode == EditPosition) EditPositionDone();
12607     if (newState) {
12608         SendToProgram("hard\n", &first);
12609         if (gameMode == TwoMachinesPlay) {
12610             SendToProgram("hard\n", &second);
12611         }
12612     } else {
12613         SendToProgram("easy\n", &first);
12614         thinkOutput[0] = NULLCHAR;
12615         if (gameMode == TwoMachinesPlay) {
12616             SendToProgram("easy\n", &second);
12617         }
12618     }
12619     appData.ponderNextMove = newState;
12620 }
12621
12622 void
12623 NewSettingEvent(option, command, value)
12624      char *command;
12625      int option, value;
12626 {
12627     char buf[MSG_SIZ];
12628
12629     if (gameMode == EditPosition) EditPositionDone();
12630     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12631     SendToProgram(buf, &first);
12632     if (gameMode == TwoMachinesPlay) {
12633         SendToProgram(buf, &second);
12634     }
12635 }
12636
12637 void
12638 ShowThinkingEvent()
12639 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12640 {
12641     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12642     int newState = appData.showThinking
12643         // [HGM] thinking: other features now need thinking output as well
12644         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12645     
12646     if (oldState == newState) return;
12647     oldState = newState;
12648     if (gameMode == EditPosition) EditPositionDone();
12649     if (oldState) {
12650         SendToProgram("post\n", &first);
12651         if (gameMode == TwoMachinesPlay) {
12652             SendToProgram("post\n", &second);
12653         }
12654     } else {
12655         SendToProgram("nopost\n", &first);
12656         thinkOutput[0] = NULLCHAR;
12657         if (gameMode == TwoMachinesPlay) {
12658             SendToProgram("nopost\n", &second);
12659         }
12660     }
12661 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12662 }
12663
12664 void
12665 AskQuestionEvent(title, question, replyPrefix, which)
12666      char *title; char *question; char *replyPrefix; char *which;
12667 {
12668   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12669   if (pr == NoProc) return;
12670   AskQuestion(title, question, replyPrefix, pr);
12671 }
12672
12673 void
12674 DisplayMove(moveNumber)
12675      int moveNumber;
12676 {
12677     char message[MSG_SIZ];
12678     char res[MSG_SIZ];
12679     char cpThinkOutput[MSG_SIZ];
12680
12681     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12682     
12683     if (moveNumber == forwardMostMove - 1 || 
12684         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12685
12686         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12687
12688         if (strchr(cpThinkOutput, '\n')) {
12689             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12690         }
12691     } else {
12692         *cpThinkOutput = NULLCHAR;
12693     }
12694
12695     /* [AS] Hide thinking from human user */
12696     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12697         *cpThinkOutput = NULLCHAR;
12698         if( thinkOutput[0] != NULLCHAR ) {
12699             int i;
12700
12701             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12702                 cpThinkOutput[i] = '.';
12703             }
12704             cpThinkOutput[i] = NULLCHAR;
12705             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12706         }
12707     }
12708
12709     if (moveNumber == forwardMostMove - 1 &&
12710         gameInfo.resultDetails != NULL) {
12711         if (gameInfo.resultDetails[0] == NULLCHAR) {
12712             sprintf(res, " %s", PGNResult(gameInfo.result));
12713         } else {
12714             sprintf(res, " {%s} %s",
12715                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12716         }
12717     } else {
12718         res[0] = NULLCHAR;
12719     }
12720
12721     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12722         DisplayMessage(res, cpThinkOutput);
12723     } else {
12724         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12725                 WhiteOnMove(moveNumber) ? " " : ".. ",
12726                 parseList[moveNumber], res);
12727         DisplayMessage(message, cpThinkOutput);
12728     }
12729 }
12730
12731 void
12732 DisplayAnalysisText(text)
12733      char *text;
12734 {
12735     char buf[MSG_SIZ];
12736
12737     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12738                || appData.icsEngineAnalyze) {
12739         sprintf(buf, "Analysis (%s)", first.tidy);
12740         AnalysisPopUp(buf, text);
12741     }
12742 }
12743
12744 static int
12745 only_one_move(str)
12746      char *str;
12747 {
12748     while (*str && isspace(*str)) ++str;
12749     while (*str && !isspace(*str)) ++str;
12750     if (!*str) return 1;
12751     while (*str && isspace(*str)) ++str;
12752     if (!*str) return 1;
12753     return 0;
12754 }
12755
12756 void
12757 DisplayAnalysis()
12758 {
12759     char buf[MSG_SIZ];
12760     char lst[MSG_SIZ / 2];
12761     double nps;
12762     static char *xtra[] = { "", " (--)", " (++)" };
12763     int h, m, s, cs;
12764   
12765     if (programStats.time == 0) {
12766         programStats.time = 1;
12767     }
12768   
12769     if (programStats.got_only_move) {
12770         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12771     } else {
12772         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12773
12774         nps = (u64ToDouble(programStats.nodes) /
12775              ((double)programStats.time /100.0));
12776
12777         cs = programStats.time % 100;
12778         s = programStats.time / 100;
12779         h = (s / (60*60));
12780         s = s - h*60*60;
12781         m = (s/60);
12782         s = s - m*60;
12783
12784         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12785           if (programStats.move_name[0] != NULLCHAR) {
12786             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12787                     programStats.depth,
12788                     programStats.nr_moves-programStats.moves_left,
12789                     programStats.nr_moves, programStats.move_name,
12790                     ((float)programStats.score)/100.0, lst,
12791                     only_one_move(lst)?
12792                     xtra[programStats.got_fail] : "",
12793                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12794           } else {
12795             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12796                     programStats.depth,
12797                     programStats.nr_moves-programStats.moves_left,
12798                     programStats.nr_moves, ((float)programStats.score)/100.0,
12799                     lst,
12800                     only_one_move(lst)?
12801                     xtra[programStats.got_fail] : "",
12802                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12803           }
12804         } else {
12805             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12806                     programStats.depth,
12807                     ((float)programStats.score)/100.0,
12808                     lst,
12809                     only_one_move(lst)?
12810                     xtra[programStats.got_fail] : "",
12811                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12812         }
12813     }
12814     DisplayAnalysisText(buf);
12815 }
12816
12817 void
12818 DisplayComment(moveNumber, text)
12819      int moveNumber;
12820      char *text;
12821 {
12822     char title[MSG_SIZ];
12823     char buf[8000]; // comment can be long!
12824     int score, depth;
12825
12826     if( appData.autoDisplayComment ) {
12827         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12828             strcpy(title, "Comment");
12829         } else {
12830             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12831                     WhiteOnMove(moveNumber) ? " " : ".. ",
12832                     parseList[moveNumber]);
12833         }
12834         // [HGM] PV info: display PV info together with (or as) comment
12835         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12836             if(text == NULL) text = "";                                           
12837             score = pvInfoList[moveNumber].score;
12838             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12839                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12840             text = buf;
12841         }
12842     } else title[0] = 0;
12843
12844     if (text != NULL)
12845         CommentPopUp(title, text);
12846 }
12847
12848 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12849  * might be busy thinking or pondering.  It can be omitted if your
12850  * gnuchess is configured to stop thinking immediately on any user
12851  * input.  However, that gnuchess feature depends on the FIONREAD
12852  * ioctl, which does not work properly on some flavors of Unix.
12853  */
12854 void
12855 Attention(cps)
12856      ChessProgramState *cps;
12857 {
12858 #if ATTENTION
12859     if (!cps->useSigint) return;
12860     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12861     switch (gameMode) {
12862       case MachinePlaysWhite:
12863       case MachinePlaysBlack:
12864       case TwoMachinesPlay:
12865       case IcsPlayingWhite:
12866       case IcsPlayingBlack:
12867       case AnalyzeMode:
12868       case AnalyzeFile:
12869         /* Skip if we know it isn't thinking */
12870         if (!cps->maybeThinking) return;
12871         if (appData.debugMode)
12872           fprintf(debugFP, "Interrupting %s\n", cps->which);
12873         InterruptChildProcess(cps->pr);
12874         cps->maybeThinking = FALSE;
12875         break;
12876       default:
12877         break;
12878     }
12879 #endif /*ATTENTION*/
12880 }
12881
12882 int
12883 CheckFlags()
12884 {
12885     if (whiteTimeRemaining <= 0) {
12886         if (!whiteFlag) {
12887             whiteFlag = TRUE;
12888             if (appData.icsActive) {
12889                 if (appData.autoCallFlag &&
12890                     gameMode == IcsPlayingBlack && !blackFlag) {
12891                   SendToICS(ics_prefix);
12892                   SendToICS("flag\n");
12893                 }
12894             } else {
12895                 if (blackFlag) {
12896                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12897                 } else {
12898                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12899                     if (appData.autoCallFlag) {
12900                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12901                         return TRUE;
12902                     }
12903                 }
12904             }
12905         }
12906     }
12907     if (blackTimeRemaining <= 0) {
12908         if (!blackFlag) {
12909             blackFlag = TRUE;
12910             if (appData.icsActive) {
12911                 if (appData.autoCallFlag &&
12912                     gameMode == IcsPlayingWhite && !whiteFlag) {
12913                   SendToICS(ics_prefix);
12914                   SendToICS("flag\n");
12915                 }
12916             } else {
12917                 if (whiteFlag) {
12918                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12919                 } else {
12920                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12921                     if (appData.autoCallFlag) {
12922                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12923                         return TRUE;
12924                     }
12925                 }
12926             }
12927         }
12928     }
12929     return FALSE;
12930 }
12931
12932 void
12933 CheckTimeControl()
12934 {
12935     if (!appData.clockMode || appData.icsActive ||
12936         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12937
12938     /*
12939      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12940      */
12941     if ( !WhiteOnMove(forwardMostMove) )
12942         /* White made time control */
12943         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12944         /* [HGM] time odds: correct new time quota for time odds! */
12945                                             / WhitePlayer()->timeOdds;
12946       else
12947         /* Black made time control */
12948         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12949                                             / WhitePlayer()->other->timeOdds;
12950 }
12951
12952 void
12953 DisplayBothClocks()
12954 {
12955     int wom = gameMode == EditPosition ?
12956       !blackPlaysFirst : WhiteOnMove(currentMove);
12957     DisplayWhiteClock(whiteTimeRemaining, wom);
12958     DisplayBlackClock(blackTimeRemaining, !wom);
12959 }
12960
12961
12962 /* Timekeeping seems to be a portability nightmare.  I think everyone
12963    has ftime(), but I'm really not sure, so I'm including some ifdefs
12964    to use other calls if you don't.  Clocks will be less accurate if
12965    you have neither ftime nor gettimeofday.
12966 */
12967
12968 /* VS 2008 requires the #include outside of the function */
12969 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12970 #include <sys/timeb.h>
12971 #endif
12972
12973 /* Get the current time as a TimeMark */
12974 void
12975 GetTimeMark(tm)
12976      TimeMark *tm;
12977 {
12978 #if HAVE_GETTIMEOFDAY
12979
12980     struct timeval timeVal;
12981     struct timezone timeZone;
12982
12983     gettimeofday(&timeVal, &timeZone);
12984     tm->sec = (long) timeVal.tv_sec; 
12985     tm->ms = (int) (timeVal.tv_usec / 1000L);
12986
12987 #else /*!HAVE_GETTIMEOFDAY*/
12988 #if HAVE_FTIME
12989
12990 // include <sys/timeb.h> / moved to just above start of function
12991     struct timeb timeB;
12992
12993     ftime(&timeB);
12994     tm->sec = (long) timeB.time;
12995     tm->ms = (int) timeB.millitm;
12996
12997 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12998     tm->sec = (long) time(NULL);
12999     tm->ms = 0;
13000 #endif
13001 #endif
13002 }
13003
13004 /* Return the difference in milliseconds between two
13005    time marks.  We assume the difference will fit in a long!
13006 */
13007 long
13008 SubtractTimeMarks(tm2, tm1)
13009      TimeMark *tm2, *tm1;
13010 {
13011     return 1000L*(tm2->sec - tm1->sec) +
13012            (long) (tm2->ms - tm1->ms);
13013 }
13014
13015
13016 /*
13017  * Code to manage the game clocks.
13018  *
13019  * In tournament play, black starts the clock and then white makes a move.
13020  * We give the human user a slight advantage if he is playing white---the
13021  * clocks don't run until he makes his first move, so it takes zero time.
13022  * Also, we don't account for network lag, so we could get out of sync
13023  * with GNU Chess's clock -- but then, referees are always right.  
13024  */
13025
13026 static TimeMark tickStartTM;
13027 static long intendedTickLength;
13028
13029 long
13030 NextTickLength(timeRemaining)
13031      long timeRemaining;
13032 {
13033     long nominalTickLength, nextTickLength;
13034
13035     if (timeRemaining > 0L && timeRemaining <= 10000L)
13036       nominalTickLength = 100L;
13037     else
13038       nominalTickLength = 1000L;
13039     nextTickLength = timeRemaining % nominalTickLength;
13040     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13041
13042     return nextTickLength;
13043 }
13044
13045 /* Adjust clock one minute up or down */
13046 void
13047 AdjustClock(Boolean which, int dir)
13048 {
13049     if(which) blackTimeRemaining += 60000*dir;
13050     else      whiteTimeRemaining += 60000*dir;
13051     DisplayBothClocks();
13052 }
13053
13054 /* Stop clocks and reset to a fresh time control */
13055 void
13056 ResetClocks() 
13057 {
13058     (void) StopClockTimer();
13059     if (appData.icsActive) {
13060         whiteTimeRemaining = blackTimeRemaining = 0;
13061     } else { /* [HGM] correct new time quote for time odds */
13062         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13063         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13064     }
13065     if (whiteFlag || blackFlag) {
13066         DisplayTitle("");
13067         whiteFlag = blackFlag = FALSE;
13068     }
13069     DisplayBothClocks();
13070 }
13071
13072 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13073
13074 /* Decrement running clock by amount of time that has passed */
13075 void
13076 DecrementClocks()
13077 {
13078     long timeRemaining;
13079     long lastTickLength, fudge;
13080     TimeMark now;
13081
13082     if (!appData.clockMode) return;
13083     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13084         
13085     GetTimeMark(&now);
13086
13087     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13088
13089     /* Fudge if we woke up a little too soon */
13090     fudge = intendedTickLength - lastTickLength;
13091     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13092
13093     if (WhiteOnMove(forwardMostMove)) {
13094         if(whiteNPS >= 0) lastTickLength = 0;
13095         timeRemaining = whiteTimeRemaining -= lastTickLength;
13096         DisplayWhiteClock(whiteTimeRemaining - fudge,
13097                           WhiteOnMove(currentMove));
13098     } else {
13099         if(blackNPS >= 0) lastTickLength = 0;
13100         timeRemaining = blackTimeRemaining -= lastTickLength;
13101         DisplayBlackClock(blackTimeRemaining - fudge,
13102                           !WhiteOnMove(currentMove));
13103     }
13104
13105     if (CheckFlags()) return;
13106         
13107     tickStartTM = now;
13108     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13109     StartClockTimer(intendedTickLength);
13110
13111     /* if the time remaining has fallen below the alarm threshold, sound the
13112      * alarm. if the alarm has sounded and (due to a takeback or time control
13113      * with increment) the time remaining has increased to a level above the
13114      * threshold, reset the alarm so it can sound again. 
13115      */
13116     
13117     if (appData.icsActive && appData.icsAlarm) {
13118
13119         /* make sure we are dealing with the user's clock */
13120         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13121                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13122            )) return;
13123
13124         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13125             alarmSounded = FALSE;
13126         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13127             PlayAlarmSound();
13128             alarmSounded = TRUE;
13129         }
13130     }
13131 }
13132
13133
13134 /* A player has just moved, so stop the previously running
13135    clock and (if in clock mode) start the other one.
13136    We redisplay both clocks in case we're in ICS mode, because
13137    ICS gives us an update to both clocks after every move.
13138    Note that this routine is called *after* forwardMostMove
13139    is updated, so the last fractional tick must be subtracted
13140    from the color that is *not* on move now.
13141 */
13142 void
13143 SwitchClocks()
13144 {
13145     long lastTickLength;
13146     TimeMark now;
13147     int flagged = FALSE;
13148
13149     GetTimeMark(&now);
13150
13151     if (StopClockTimer() && appData.clockMode) {
13152         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13153         if (WhiteOnMove(forwardMostMove)) {
13154             if(blackNPS >= 0) lastTickLength = 0;
13155             blackTimeRemaining -= lastTickLength;
13156            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13157 //         if(pvInfoList[forwardMostMove-1].time == -1)
13158                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13159                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13160         } else {
13161            if(whiteNPS >= 0) lastTickLength = 0;
13162            whiteTimeRemaining -= lastTickLength;
13163            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13164 //         if(pvInfoList[forwardMostMove-1].time == -1)
13165                  pvInfoList[forwardMostMove-1].time = 
13166                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13167         }
13168         flagged = CheckFlags();
13169     }
13170     CheckTimeControl();
13171
13172     if (flagged || !appData.clockMode) return;
13173
13174     switch (gameMode) {
13175       case MachinePlaysBlack:
13176       case MachinePlaysWhite:
13177       case BeginningOfGame:
13178         if (pausing) return;
13179         break;
13180
13181       case EditGame:
13182       case PlayFromGameFile:
13183       case IcsExamining:
13184         return;
13185
13186       default:
13187         break;
13188     }
13189
13190     tickStartTM = now;
13191     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13192       whiteTimeRemaining : blackTimeRemaining);
13193     StartClockTimer(intendedTickLength);
13194 }
13195         
13196
13197 /* Stop both clocks */
13198 void
13199 StopClocks()
13200 {       
13201     long lastTickLength;
13202     TimeMark now;
13203
13204     if (!StopClockTimer()) return;
13205     if (!appData.clockMode) return;
13206
13207     GetTimeMark(&now);
13208
13209     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13210     if (WhiteOnMove(forwardMostMove)) {
13211         if(whiteNPS >= 0) lastTickLength = 0;
13212         whiteTimeRemaining -= lastTickLength;
13213         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13214     } else {
13215         if(blackNPS >= 0) lastTickLength = 0;
13216         blackTimeRemaining -= lastTickLength;
13217         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13218     }
13219     CheckFlags();
13220 }
13221         
13222 /* Start clock of player on move.  Time may have been reset, so
13223    if clock is already running, stop and restart it. */
13224 void
13225 StartClocks()
13226 {
13227     (void) StopClockTimer(); /* in case it was running already */
13228     DisplayBothClocks();
13229     if (CheckFlags()) return;
13230
13231     if (!appData.clockMode) return;
13232     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13233
13234     GetTimeMark(&tickStartTM);
13235     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13236       whiteTimeRemaining : blackTimeRemaining);
13237
13238    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13239     whiteNPS = blackNPS = -1; 
13240     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13241        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13242         whiteNPS = first.nps;
13243     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13244        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13245         blackNPS = first.nps;
13246     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13247         whiteNPS = second.nps;
13248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13249         blackNPS = second.nps;
13250     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13251
13252     StartClockTimer(intendedTickLength);
13253 }
13254
13255 char *
13256 TimeString(ms)
13257      long ms;
13258 {
13259     long second, minute, hour, day;
13260     char *sign = "";
13261     static char buf[32];
13262     
13263     if (ms > 0 && ms <= 9900) {
13264       /* convert milliseconds to tenths, rounding up */
13265       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13266
13267       sprintf(buf, " %03.1f ", tenths/10.0);
13268       return buf;
13269     }
13270
13271     /* convert milliseconds to seconds, rounding up */
13272     /* use floating point to avoid strangeness of integer division
13273        with negative dividends on many machines */
13274     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13275
13276     if (second < 0) {
13277         sign = "-";
13278         second = -second;
13279     }
13280     
13281     day = second / (60 * 60 * 24);
13282     second = second % (60 * 60 * 24);
13283     hour = second / (60 * 60);
13284     second = second % (60 * 60);
13285     minute = second / 60;
13286     second = second % 60;
13287     
13288     if (day > 0)
13289       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13290               sign, day, hour, minute, second);
13291     else if (hour > 0)
13292       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13293     else
13294       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13295     
13296     return buf;
13297 }
13298
13299
13300 /*
13301  * This is necessary because some C libraries aren't ANSI C compliant yet.
13302  */
13303 char *
13304 StrStr(string, match)
13305      char *string, *match;
13306 {
13307     int i, length;
13308     
13309     length = strlen(match);
13310     
13311     for (i = strlen(string) - length; i >= 0; i--, string++)
13312       if (!strncmp(match, string, length))
13313         return string;
13314     
13315     return NULL;
13316 }
13317
13318 char *
13319 StrCaseStr(string, match)
13320      char *string, *match;
13321 {
13322     int i, j, length;
13323     
13324     length = strlen(match);
13325     
13326     for (i = strlen(string) - length; i >= 0; i--, string++) {
13327         for (j = 0; j < length; j++) {
13328             if (ToLower(match[j]) != ToLower(string[j]))
13329               break;
13330         }
13331         if (j == length) return string;
13332     }
13333
13334     return NULL;
13335 }
13336
13337 #ifndef _amigados
13338 int
13339 StrCaseCmp(s1, s2)
13340      char *s1, *s2;
13341 {
13342     char c1, c2;
13343     
13344     for (;;) {
13345         c1 = ToLower(*s1++);
13346         c2 = ToLower(*s2++);
13347         if (c1 > c2) return 1;
13348         if (c1 < c2) return -1;
13349         if (c1 == NULLCHAR) return 0;
13350     }
13351 }
13352
13353
13354 int
13355 ToLower(c)
13356      int c;
13357 {
13358     return isupper(c) ? tolower(c) : c;
13359 }
13360
13361
13362 int
13363 ToUpper(c)
13364      int c;
13365 {
13366     return islower(c) ? toupper(c) : c;
13367 }
13368 #endif /* !_amigados    */
13369
13370 char *
13371 StrSave(s)
13372      char *s;
13373 {
13374     char *ret;
13375
13376     if ((ret = (char *) malloc(strlen(s) + 1))) {
13377         strcpy(ret, s);
13378     }
13379     return ret;
13380 }
13381
13382 char *
13383 StrSavePtr(s, savePtr)
13384      char *s, **savePtr;
13385 {
13386     if (*savePtr) {
13387         free(*savePtr);
13388     }
13389     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13390         strcpy(*savePtr, s);
13391     }
13392     return(*savePtr);
13393 }
13394
13395 char *
13396 PGNDate()
13397 {
13398     time_t clock;
13399     struct tm *tm;
13400     char buf[MSG_SIZ];
13401
13402     clock = time((time_t *)NULL);
13403     tm = localtime(&clock);
13404     sprintf(buf, "%04d.%02d.%02d",
13405             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13406     return StrSave(buf);
13407 }
13408
13409
13410 char *
13411 PositionToFEN(move, overrideCastling)
13412      int move;
13413      char *overrideCastling;
13414 {
13415     int i, j, fromX, fromY, toX, toY;
13416     int whiteToPlay;
13417     char buf[128];
13418     char *p, *q;
13419     int emptycount;
13420     ChessSquare piece;
13421
13422     whiteToPlay = (gameMode == EditPosition) ?
13423       !blackPlaysFirst : (move % 2 == 0);
13424     p = buf;
13425
13426     /* Piece placement data */
13427     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13428         emptycount = 0;
13429         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13430             if (boards[move][i][j] == EmptySquare) {
13431                 emptycount++;
13432             } else { ChessSquare piece = boards[move][i][j];
13433                 if (emptycount > 0) {
13434                     if(emptycount<10) /* [HGM] can be >= 10 */
13435                         *p++ = '0' + emptycount;
13436                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13437                     emptycount = 0;
13438                 }
13439                 if(PieceToChar(piece) == '+') {
13440                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13441                     *p++ = '+';
13442                     piece = (ChessSquare)(DEMOTED piece);
13443                 } 
13444                 *p++ = PieceToChar(piece);
13445                 if(p[-1] == '~') {
13446                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13447                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13448                     *p++ = '~';
13449                 }
13450             }
13451         }
13452         if (emptycount > 0) {
13453             if(emptycount<10) /* [HGM] can be >= 10 */
13454                 *p++ = '0' + emptycount;
13455             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13456             emptycount = 0;
13457         }
13458         *p++ = '/';
13459     }
13460     *(p - 1) = ' ';
13461
13462     /* [HGM] print Crazyhouse or Shogi holdings */
13463     if( gameInfo.holdingsWidth ) {
13464         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13465         q = p;
13466         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13467             piece = boards[move][i][BOARD_WIDTH-1];
13468             if( piece != EmptySquare )
13469               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13470                   *p++ = PieceToChar(piece);
13471         }
13472         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13473             piece = boards[move][BOARD_HEIGHT-i-1][0];
13474             if( piece != EmptySquare )
13475               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13476                   *p++ = PieceToChar(piece);
13477         }
13478
13479         if( q == p ) *p++ = '-';
13480         *p++ = ']';
13481         *p++ = ' ';
13482     }
13483
13484     /* Active color */
13485     *p++ = whiteToPlay ? 'w' : 'b';
13486     *p++ = ' ';
13487
13488   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13489     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13490   } else {
13491   if(nrCastlingRights) {
13492      q = p;
13493      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13494        /* [HGM] write directly from rights */
13495            if(castlingRights[move][2] >= 0 &&
13496               castlingRights[move][0] >= 0   )
13497                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13498            if(castlingRights[move][2] >= 0 &&
13499               castlingRights[move][1] >= 0   )
13500                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13501            if(castlingRights[move][5] >= 0 &&
13502               castlingRights[move][3] >= 0   )
13503                 *p++ = castlingRights[move][3] + AAA;
13504            if(castlingRights[move][5] >= 0 &&
13505               castlingRights[move][4] >= 0   )
13506                 *p++ = castlingRights[move][4] + AAA;
13507      } else {
13508
13509         /* [HGM] write true castling rights */
13510         if( nrCastlingRights == 6 ) {
13511             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13512                castlingRights[move][2] >= 0  ) *p++ = 'K';
13513             if(castlingRights[move][1] == BOARD_LEFT &&
13514                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13515             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13516                castlingRights[move][5] >= 0  ) *p++ = 'k';
13517             if(castlingRights[move][4] == BOARD_LEFT &&
13518                castlingRights[move][5] >= 0  ) *p++ = 'q';
13519         }
13520      }
13521      if (q == p) *p++ = '-'; /* No castling rights */
13522      *p++ = ' ';
13523   }
13524
13525   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13526      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13527     /* En passant target square */
13528     if (move > backwardMostMove) {
13529         fromX = moveList[move - 1][0] - AAA;
13530         fromY = moveList[move - 1][1] - ONE;
13531         toX = moveList[move - 1][2] - AAA;
13532         toY = moveList[move - 1][3] - ONE;
13533         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13534             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13535             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13536             fromX == toX) {
13537             /* 2-square pawn move just happened */
13538             *p++ = toX + AAA;
13539             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13540         } else {
13541             *p++ = '-';
13542         }
13543     } else {
13544         *p++ = '-';
13545     }
13546     *p++ = ' ';
13547   }
13548   }
13549
13550     /* [HGM] find reversible plies */
13551     {   int i = 0, j=move;
13552
13553         if (appData.debugMode) { int k;
13554             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13555             for(k=backwardMostMove; k<=forwardMostMove; k++)
13556                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13557
13558         }
13559
13560         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13561         if( j == backwardMostMove ) i += initialRulePlies;
13562         sprintf(p, "%d ", i);
13563         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13564     }
13565     /* Fullmove number */
13566     sprintf(p, "%d", (move / 2) + 1);
13567     
13568     return StrSave(buf);
13569 }
13570
13571 Boolean
13572 ParseFEN(board, blackPlaysFirst, fen)
13573     Board board;
13574      int *blackPlaysFirst;
13575      char *fen;
13576 {
13577     int i, j;
13578     char *p;
13579     int emptycount;
13580     ChessSquare piece;
13581
13582     p = fen;
13583
13584     /* [HGM] by default clear Crazyhouse holdings, if present */
13585     if(gameInfo.holdingsWidth) {
13586        for(i=0; i<BOARD_HEIGHT; i++) {
13587            board[i][0]             = EmptySquare; /* black holdings */
13588            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13589            board[i][1]             = (ChessSquare) 0; /* black counts */
13590            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13591        }
13592     }
13593
13594     /* Piece placement data */
13595     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13596         j = 0;
13597         for (;;) {
13598             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13599                 if (*p == '/') p++;
13600                 emptycount = gameInfo.boardWidth - j;
13601                 while (emptycount--)
13602                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13603                 break;
13604 #if(BOARD_SIZE >= 10)
13605             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13606                 p++; emptycount=10;
13607                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13608                 while (emptycount--)
13609                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13610 #endif
13611             } else if (isdigit(*p)) {
13612                 emptycount = *p++ - '0';
13613                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13614                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13615                 while (emptycount--)
13616                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13617             } else if (*p == '+' || isalpha(*p)) {
13618                 if (j >= gameInfo.boardWidth) return FALSE;
13619                 if(*p=='+') {
13620                     piece = CharToPiece(*++p);
13621                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13622                     piece = (ChessSquare) (PROMOTED piece ); p++;
13623                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13624                 } else piece = CharToPiece(*p++);
13625
13626                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13627                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13628                     piece = (ChessSquare) (PROMOTED piece);
13629                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13630                     p++;
13631                 }
13632                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13633             } else {
13634                 return FALSE;
13635             }
13636         }
13637     }
13638     while (*p == '/' || *p == ' ') p++;
13639
13640     /* [HGM] look for Crazyhouse holdings here */
13641     while(*p==' ') p++;
13642     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13643         if(*p == '[') p++;
13644         if(*p == '-' ) *p++; /* empty holdings */ else {
13645             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13646             /* if we would allow FEN reading to set board size, we would   */
13647             /* have to add holdings and shift the board read so far here   */
13648             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13649                 *p++;
13650                 if((int) piece >= (int) BlackPawn ) {
13651                     i = (int)piece - (int)BlackPawn;
13652                     i = PieceToNumber((ChessSquare)i);
13653                     if( i >= gameInfo.holdingsSize ) return FALSE;
13654                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13655                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13656                 } else {
13657                     i = (int)piece - (int)WhitePawn;
13658                     i = PieceToNumber((ChessSquare)i);
13659                     if( i >= gameInfo.holdingsSize ) return FALSE;
13660                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13661                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13662                 }
13663             }
13664         }
13665         if(*p == ']') *p++;
13666     }
13667
13668     while(*p == ' ') p++;
13669
13670     /* Active color */
13671     switch (*p++) {
13672       case 'w':
13673         *blackPlaysFirst = FALSE;
13674         break;
13675       case 'b': 
13676         *blackPlaysFirst = TRUE;
13677         break;
13678       default:
13679         return FALSE;
13680     }
13681
13682     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13683     /* return the extra info in global variiables             */
13684
13685     /* set defaults in case FEN is incomplete */
13686     FENepStatus = EP_UNKNOWN;
13687     for(i=0; i<nrCastlingRights; i++ ) {
13688         FENcastlingRights[i] =
13689             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13690     }   /* assume possible unless obviously impossible */
13691     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13692     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13693     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13694     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13695     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13696     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13697     FENrulePlies = 0;
13698
13699     while(*p==' ') p++;
13700     if(nrCastlingRights) {
13701       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13702           /* castling indicator present, so default becomes no castlings */
13703           for(i=0; i<nrCastlingRights; i++ ) {
13704                  FENcastlingRights[i] = -1;
13705           }
13706       }
13707       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13708              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13709              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13710              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13711         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13712
13713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13714             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13715             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13716         }
13717         switch(c) {
13718           case'K':
13719               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13720               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13721               FENcastlingRights[2] = whiteKingFile;
13722               break;
13723           case'Q':
13724               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13725               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13726               FENcastlingRights[2] = whiteKingFile;
13727               break;
13728           case'k':
13729               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13730               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13731               FENcastlingRights[5] = blackKingFile;
13732               break;
13733           case'q':
13734               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13735               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13736               FENcastlingRights[5] = blackKingFile;
13737           case '-':
13738               break;
13739           default: /* FRC castlings */
13740               if(c >= 'a') { /* black rights */
13741                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13742                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13743                   if(i == BOARD_RGHT) break;
13744                   FENcastlingRights[5] = i;
13745                   c -= AAA;
13746                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13747                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13748                   if(c > i)
13749                       FENcastlingRights[3] = c;
13750                   else
13751                       FENcastlingRights[4] = c;
13752               } else { /* white rights */
13753                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13754                     if(board[0][i] == WhiteKing) break;
13755                   if(i == BOARD_RGHT) break;
13756                   FENcastlingRights[2] = i;
13757                   c -= AAA - 'a' + 'A';
13758                   if(board[0][c] >= WhiteKing) break;
13759                   if(c > i)
13760                       FENcastlingRights[0] = c;
13761                   else
13762                       FENcastlingRights[1] = c;
13763               }
13764         }
13765       }
13766     if (appData.debugMode) {
13767         fprintf(debugFP, "FEN castling rights:");
13768         for(i=0; i<nrCastlingRights; i++)
13769         fprintf(debugFP, " %d", FENcastlingRights[i]);
13770         fprintf(debugFP, "\n");
13771     }
13772
13773       while(*p==' ') p++;
13774     }
13775
13776     /* read e.p. field in games that know e.p. capture */
13777     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13778        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13779       if(*p=='-') {
13780         p++; FENepStatus = EP_NONE;
13781       } else {
13782          char c = *p++ - AAA;
13783
13784          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13785          if(*p >= '0' && *p <='9') *p++;
13786          FENepStatus = c;
13787       }
13788     }
13789
13790
13791     if(sscanf(p, "%d", &i) == 1) {
13792         FENrulePlies = i; /* 50-move ply counter */
13793         /* (The move number is still ignored)    */
13794     }
13795
13796     return TRUE;
13797 }
13798       
13799 void
13800 EditPositionPasteFEN(char *fen)
13801 {
13802   if (fen != NULL) {
13803     Board initial_position;
13804
13805     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13806       DisplayError(_("Bad FEN position in clipboard"), 0);
13807       return ;
13808     } else {
13809       int savedBlackPlaysFirst = blackPlaysFirst;
13810       EditPositionEvent();
13811       blackPlaysFirst = savedBlackPlaysFirst;
13812       CopyBoard(boards[0], initial_position);
13813           /* [HGM] copy FEN attributes as well */
13814           {   int i;
13815               initialRulePlies = FENrulePlies;
13816               epStatus[0] = FENepStatus;
13817               for( i=0; i<nrCastlingRights; i++ )
13818                   castlingRights[0][i] = FENcastlingRights[i];
13819           }
13820       EditPositionDone();
13821       DisplayBothClocks();
13822       DrawPosition(FALSE, boards[currentMove]);
13823     }
13824   }
13825 }