Swapped 'tell' and 'message' parsing order for colorization. Sometimes
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245
246 /* States for ics_getting_history */
247 #define H_FALSE 0
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
253
254 /* whosays values for GameEnds */
255 #define GE_ICS 0
256 #define GE_ENGINE 1
257 #define GE_PLAYER 2
258 #define GE_FILE 3
259 #define GE_XBOARD 4
260 #define GE_ENGINE1 5
261 #define GE_ENGINE2 6
262
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
265
266 /* Different types of move when calling RegisterMove */
267 #define CMAIL_MOVE   0
268 #define CMAIL_RESIGN 1
269 #define CMAIL_DRAW   2
270 #define CMAIL_ACCEPT 3
271
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
276
277 /* Telnet protocol constants */
278 #define TN_WILL 0373
279 #define TN_WONT 0374
280 #define TN_DO   0375
281 #define TN_DONT 0376
282 #define TN_IAC  0377
283 #define TN_ECHO 0001
284 #define TN_SGA  0003
285 #define TN_PORT 23
286
287 /* [AS] */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
289 {
290     assert( dst != NULL );
291     assert( src != NULL );
292     assert( count > 0 );
293
294     strncpy( dst, src, count );
295     dst[ count-1 ] = '\0';
296     return dst;
297 }
298
299 #if 0
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
302 {
303     size_t  dst_len;
304
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     dst_len = strlen(dst);
310
311     assert( count > dst_len ); /* Buffer size must be greater than current length */
312
313     safeStrCpy( dst + dst_len, src, count - dst_len );
314
315     return dst;
316 }
317 #endif
318
319 /* Some compiler can't cast u64 to double
320  * This function do the job for us:
321
322  * We use the highest bit for cast, this only
323  * works if the highest bit is not
324  * in use (This should not happen)
325  *
326  * We used this for all compiler
327  */
328 double
329 u64ToDouble(u64 value)
330 {
331   double r;
332   u64 tmp = value & u64Const(0x7fffffffffffffff);
333   r = (double)(s64)tmp;
334   if (value & u64Const(0x8000000000000000))
335        r +=  9.2233720368547758080e18; /* 2^63 */
336  return r;
337 }
338
339 /* Fake up flags for now, as we aren't keeping track of castling
340    availability yet. [HGM] Change of logic: the flag now only
341    indicates the type of castlings allowed by the rule of the game.
342    The actual rights themselves are maintained in the array
343    castlingRights, as part of the game history, and are not probed
344    by this function.
345  */
346 int
347 PosFlags(index)
348 {
349   int flags = F_ALL_CASTLE_OK;
350   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351   switch (gameInfo.variant) {
352   case VariantSuicide:
353     flags &= ~F_ALL_CASTLE_OK;
354   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355     flags |= F_IGNORE_CHECK;
356   case VariantLosers:
357     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358     break;
359   case VariantAtomic:
360     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361     break;
362   case VariantKriegspiel:
363     flags |= F_KRIEGSPIEL_CAPTURE;
364     break;
365   case VariantCapaRandom: 
366   case VariantFischeRandom:
367     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368   case VariantNoCastle:
369   case VariantShatranj:
370   case VariantCourier:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /* 
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char  epStatus[MAX_MOVES];
460 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int   initialRulePlies, FENrulePlies;
465 char  FENepStatus;
466 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 int loadFlag = 0; 
468 int shuffleOpenings;
469
470 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_SIZE>=10)
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
570
571 #if (BOARD_SIZE>=12)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<MAX_MOVES; i++ ) {
662             pvInfoList[i].depth = -1;
663             epStatus[i]=EP_NONE;
664             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { int norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815     
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825     
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830 #if 0
831         char *p, *q;
832         q = first.program;
833         while (*q != ' ' && *q != NULLCHAR) q++;
834         p = q;
835         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837         sprintf(programVersion, "%s + ", PACKAGE_STRING);
838         strncat(programVersion, p, q - p);
839 #else
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 #endif
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantBerolina:   /* might work if TestLegality is off */
902       case VariantCapaRandom: /* should work */
903       case VariantJanus:      /* should work */
904       case VariantSuper:      /* experimental */
905       case VariantGreat:      /* experimental, requires legality testing to be off */
906         break;
907       }
908     }
909
910     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
911     InitEngineUCI( installDir, &second );
912 }
913
914 int NextIntegerFromString( char ** str, long * value )
915 {
916     int result = -1;
917     char * s = *str;
918
919     while( *s == ' ' || *s == '\t' ) {
920         s++;
921     }
922
923     *value = 0;
924
925     if( *s >= '0' && *s <= '9' ) {
926         while( *s >= '0' && *s <= '9' ) {
927             *value = *value * 10 + (*s - '0');
928             s++;
929         }
930
931         result = 0;
932     }
933
934     *str = s;
935
936     return result;
937 }
938
939 int NextTimeControlFromString( char ** str, long * value )
940 {
941     long temp;
942     int result = NextIntegerFromString( str, &temp );
943
944     if( result == 0 ) {
945         *value = temp * 60; /* Minutes */
946         if( **str == ':' ) {
947             (*str)++;
948             result = NextIntegerFromString( str, &temp );
949             *value += temp; /* Seconds */
950         }
951     }
952
953     return result;
954 }
955
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
958     int result = -1; long temp, temp2;
959
960     if(**str != '+') return -1; // old params remain in force!
961     (*str)++;
962     if( NextTimeControlFromString( str, &temp ) ) return -1;
963
964     if(**str != '/') {
965         /* time only: incremental or sudden-death time control */
966         if(**str == '+') { /* increment follows; read it */
967             (*str)++;
968             if(result = NextIntegerFromString( str, &temp2)) return -1;
969             *inc = temp2 * 1000;
970         } else *inc = 0;
971         *moves = 0; *tc = temp * 1000; 
972         return 0;
973     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
974
975     (*str)++; /* classical time control */
976     result = NextTimeControlFromString( str, &temp2);
977     if(result == 0) {
978         *moves = temp/60;
979         *tc    = temp2 * 1000;
980         *inc   = 0;
981     }
982     return result;
983 }
984
985 int GetTimeQuota(int movenr)
986 {   /* [HGM] get time to add from the multi-session time-control string */
987     int moves=1; /* kludge to force reading of first session */
988     long time, increment;
989     char *s = fullTimeControlString;
990
991     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992     do {
993         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995         if(movenr == -1) return time;    /* last move before new session     */
996         if(!moves) return increment;     /* current session is incremental   */
997         if(movenr >= 0) movenr -= moves; /* we already finished this session */
998     } while(movenr >= -1);               /* try again for next session       */
999
1000     return 0; // no new time quota on this move
1001 }
1002
1003 int
1004 ParseTimeControl(tc, ti, mps)
1005      char *tc;
1006      int ti;
1007      int mps;
1008 {
1009 #if 0
1010     int matched, min, sec;
1011
1012     matched = sscanf(tc, "%d:%d", &min, &sec);
1013     if (matched == 1) {
1014         timeControl = min * 60 * 1000;
1015     } else if (matched == 2) {
1016         timeControl = (min * 60 + sec) * 1000;
1017     } else {
1018         return FALSE;
1019     }
1020 #else
1021     long tc1;
1022     long tc2;
1023     char buf[MSG_SIZ];
1024
1025     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1026     if(ti > 0) {
1027         if(mps)
1028              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029         else sprintf(buf, "+%s+%d", tc, ti);
1030     } else {
1031         if(mps)
1032              sprintf(buf, "+%d/%s", mps, tc);
1033         else sprintf(buf, "+%s", tc);
1034     }
1035     fullTimeControlString = StrSave(buf);
1036
1037     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038         return FALSE;
1039     }
1040
1041     if( *tc == '/' ) {
1042         /* Parse second time control */
1043         tc++;
1044
1045         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1046             return FALSE;
1047         }
1048
1049         if( tc2 == 0 ) {
1050             return FALSE;
1051         }
1052
1053         timeControl_2 = tc2 * 1000;
1054     }
1055     else {
1056         timeControl_2 = 0;
1057     }
1058
1059     if( tc1 == 0 ) {
1060         return FALSE;
1061     }
1062
1063     timeControl = tc1 * 1000;
1064 #endif
1065
1066     if (ti >= 0) {
1067         timeIncrement = ti * 1000;  /* convert to ms */
1068         movesPerSession = 0;
1069     } else {
1070         timeIncrement = 0;
1071         movesPerSession = mps;
1072     }
1073     return TRUE;
1074 }
1075
1076 void
1077 InitBackEnd2()
1078 {
1079     if (appData.debugMode) {
1080         fprintf(debugFP, "%s\n", programVersion);
1081     }
1082
1083     if (appData.matchGames > 0) {
1084         appData.matchMode = TRUE;
1085     } else if (appData.matchMode) {
1086         appData.matchGames = 1;
1087     }
1088     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089         appData.matchGames = appData.sameColorGames;
1090     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1093     }
1094     Reset(TRUE, FALSE);
1095     if (appData.noChessProgram || first.protocolVersion == 1) {
1096       InitBackEnd3();
1097     } else {
1098       /* kludge: allow timeout for initial "feature" commands */
1099       FreezeUI();
1100       DisplayMessage("", _("Starting chess program"));
1101       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102     }
1103 }
1104
1105 void
1106 InitBackEnd3 P((void))
1107 {
1108     GameMode initialMode;
1109     char buf[MSG_SIZ];
1110     int err;
1111
1112     InitChessProgram(&first, startedFromSetupPosition);
1113
1114
1115     if (appData.icsActive) {
1116 #ifdef WIN32
1117         /* [DM] Make a console window if needed [HGM] merged ifs */
1118         ConsoleCreate(); 
1119 #endif
1120         err = establish();
1121         if (err != 0) {
1122             if (*appData.icsCommPort != NULLCHAR) {
1123                 sprintf(buf, _("Could not open comm port %s"),  
1124                         appData.icsCommPort);
1125             } else {
1126                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1127                         appData.icsHost, appData.icsPort);
1128             }
1129             DisplayFatalError(buf, err, 1);
1130             return;
1131         }
1132         SetICSMode();
1133         telnetISR =
1134           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1135         fromUserISR =
1136           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137     } else if (appData.noChessProgram) {
1138         SetNCPMode();
1139     } else {
1140         SetGNUMode();
1141     }
1142
1143     if (*appData.cmailGameName != NULLCHAR) {
1144         SetCmailMode();
1145         OpenLoopback(&cmailPR);
1146         cmailISR =
1147           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149     
1150     ThawUI();
1151     DisplayMessage("", "");
1152     if (StrCaseCmp(appData.initialMode, "") == 0) {
1153       initialMode = BeginningOfGame;
1154     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155       initialMode = TwoMachinesPlay;
1156     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157       initialMode = AnalyzeFile; 
1158     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159       initialMode = AnalyzeMode;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161       initialMode = MachinePlaysWhite;
1162     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163       initialMode = MachinePlaysBlack;
1164     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165       initialMode = EditGame;
1166     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167       initialMode = EditPosition;
1168     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169       initialMode = Training;
1170     } else {
1171       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172       DisplayFatalError(buf, 0, 2);
1173       return;
1174     }
1175
1176     if (appData.matchMode) {
1177         /* Set up machine vs. machine match */
1178         if (appData.noChessProgram) {
1179             DisplayFatalError(_("Can't have a match with no chess programs"),
1180                               0, 2);
1181             return;
1182         }
1183         matchMode = TRUE;
1184         matchGame = 1;
1185         if (*appData.loadGameFile != NULLCHAR) {
1186             int index = appData.loadGameIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadGameFromFile(appData.loadGameFile,
1189                                   index,
1190                                   appData.loadGameFile, FALSE)) {
1191                 DisplayFatalError(_("Bad game file"), 0, 1);
1192                 return;
1193             }
1194         } else if (*appData.loadPositionFile != NULLCHAR) {
1195             int index = appData.loadPositionIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadPositionFromFile(appData.loadPositionFile,
1198                                       index,
1199                                       appData.loadPositionFile)) {
1200                 DisplayFatalError(_("Bad position file"), 0, 1);
1201                 return;
1202             }
1203         }
1204         TwoMachinesEvent();
1205     } else if (*appData.cmailGameName != NULLCHAR) {
1206         /* Set up cmail mode */
1207         ReloadCmailMsgEvent(TRUE);
1208     } else {
1209         /* Set up other modes */
1210         if (initialMode == AnalyzeFile) {
1211           if (*appData.loadGameFile == NULLCHAR) {
1212             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213             return;
1214           }
1215         }
1216         if (*appData.loadGameFile != NULLCHAR) {
1217             (void) LoadGameFromFile(appData.loadGameFile,
1218                                     appData.loadGameIndex,
1219                                     appData.loadGameFile, TRUE);
1220         } else if (*appData.loadPositionFile != NULLCHAR) {
1221             (void) LoadPositionFromFile(appData.loadPositionFile,
1222                                         appData.loadPositionIndex,
1223                                         appData.loadPositionFile);
1224             /* [HGM] try to make self-starting even after FEN load */
1225             /* to allow automatic setup of fairy variants with wtm */
1226             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227                 gameMode = BeginningOfGame;
1228                 setboardSpoiledMachineBlack = 1;
1229             }
1230             /* [HGM] loadPos: make that every new game uses the setup */
1231             /* from file as long as we do not switch variant          */
1232             if(!blackPlaysFirst) { int i;
1233                 startedFromPositionFile = TRUE;
1234                 CopyBoard(filePosition, boards[0]);
1235                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1236             }
1237         }
1238         if (initialMode == AnalyzeMode) {
1239           if (appData.noChessProgram) {
1240             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245             return;
1246           }
1247           AnalyzeModeEvent();
1248         } else if (initialMode == AnalyzeFile) {
1249           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250           ShowThinkingEvent();
1251           AnalyzeFileEvent();
1252           AnalysisPeriodicEvent(1);
1253         } else if (initialMode == MachinePlaysWhite) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           MachineWhiteEvent();
1265         } else if (initialMode == MachinePlaysBlack) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineBlackEvent();
1277         } else if (initialMode == TwoMachinesPlay) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           TwoMachinesEvent();
1289         } else if (initialMode == EditGame) {
1290           EditGameEvent();
1291         } else if (initialMode == EditPosition) {
1292           EditPositionEvent();
1293         } else if (initialMode == Training) {
1294           if (*appData.loadGameFile == NULLCHAR) {
1295             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1296             return;
1297           }
1298           TrainingEvent();
1299         }
1300     }
1301 }
1302
1303 /*
1304  * Establish will establish a contact to a remote host.port.
1305  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306  *  used to talk to the host.
1307  * Returns 0 if okay, error code if not.
1308  */
1309 int
1310 establish()
1311 {
1312     char buf[MSG_SIZ];
1313
1314     if (*appData.icsCommPort != NULLCHAR) {
1315         /* Talk to the host through a serial comm port */
1316         return OpenCommPort(appData.icsCommPort, &icsPR);
1317
1318     } else if (*appData.gateway != NULLCHAR) {
1319         if (*appData.remoteShell == NULLCHAR) {
1320             /* Use the rcmd protocol to run telnet program on a gateway host */
1321             snprintf(buf, sizeof(buf), "%s %s %s",
1322                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1323             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1324
1325         } else {
1326             /* Use the rsh program to run telnet program on a gateway host */
1327             if (*appData.remoteUser == NULLCHAR) {
1328                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329                         appData.gateway, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             } else {
1332                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333                         appData.remoteShell, appData.gateway, 
1334                         appData.remoteUser, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             }
1337             return StartChildProcess(buf, "", &icsPR);
1338
1339         }
1340     } else if (appData.useTelnet) {
1341         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1342
1343     } else {
1344         /* TCP socket interface differs somewhat between
1345            Unix and NT; handle details in the front end.
1346            */
1347         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1348     }
1349 }
1350
1351 void
1352 show_bytes(fp, buf, count)
1353      FILE *fp;
1354      char *buf;
1355      int count;
1356 {
1357     while (count--) {
1358         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359             fprintf(fp, "\\%03o", *buf & 0xff);
1360         } else {
1361             putc(*buf, fp);
1362         }
1363         buf++;
1364     }
1365     fflush(fp);
1366 }
1367
1368 /* Returns an errno value */
1369 int
1370 OutputMaybeTelnet(pr, message, count, outError)
1371      ProcRef pr;
1372      char *message;
1373      int count;
1374      int *outError;
1375 {
1376     char buf[8192], *p, *q, *buflim;
1377     int left, newcount, outcount;
1378
1379     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380         *appData.gateway != NULLCHAR) {
1381         if (appData.debugMode) {
1382             fprintf(debugFP, ">ICS: ");
1383             show_bytes(debugFP, message, count);
1384             fprintf(debugFP, "\n");
1385         }
1386         return OutputToProcess(pr, message, count, outError);
1387     }
1388
1389     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1390     p = message;
1391     q = buf;
1392     left = count;
1393     newcount = 0;
1394     while (left) {
1395         if (q >= buflim) {
1396             if (appData.debugMode) {
1397                 fprintf(debugFP, ">ICS: ");
1398                 show_bytes(debugFP, buf, newcount);
1399                 fprintf(debugFP, "\n");
1400             }
1401             outcount = OutputToProcess(pr, buf, newcount, outError);
1402             if (outcount < newcount) return -1; /* to be sure */
1403             q = buf;
1404             newcount = 0;
1405         }
1406         if (*p == '\n') {
1407             *q++ = '\r';
1408             newcount++;
1409         } else if (((unsigned char) *p) == TN_IAC) {
1410             *q++ = (char) TN_IAC;
1411             newcount ++;
1412         }
1413         *q++ = *p++;
1414         newcount++;
1415         left--;
1416     }
1417     if (appData.debugMode) {
1418         fprintf(debugFP, ">ICS: ");
1419         show_bytes(debugFP, buf, newcount);
1420         fprintf(debugFP, "\n");
1421     }
1422     outcount = OutputToProcess(pr, buf, newcount, outError);
1423     if (outcount < newcount) return -1; /* to be sure */
1424     return count;
1425 }
1426
1427 void
1428 read_from_player(isr, closure, message, count, error)
1429      InputSourceRef isr;
1430      VOIDSTAR closure;
1431      char *message;
1432      int count;
1433      int error;
1434 {
1435     int outError, outCount;
1436     static int gotEof = 0;
1437
1438     /* Pass data read from player on to ICS */
1439     if (count > 0) {
1440         gotEof = 0;
1441         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442         if (outCount < count) {
1443             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1444         }
1445     } else if (count < 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448     } else if (gotEof++ > 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451     }
1452 }
1453
1454 void
1455 SendToICS(s)
1456      char *s;
1457 {
1458     int count, outCount, outError;
1459
1460     if (icsPR == NULL) return;
1461
1462     count = strlen(s);
1463     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464     if (outCount < count) {
1465         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466     }
1467 }
1468
1469 /* This is used for sending logon scripts to the ICS. Sending
1470    without a delay causes problems when using timestamp on ICC
1471    (at least on my machine). */
1472 void
1473 SendToICSDelayed(s,msdelay)
1474      char *s;
1475      long msdelay;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     if (appData.debugMode) {
1483         fprintf(debugFP, ">ICS: ");
1484         show_bytes(debugFP, s, count);
1485         fprintf(debugFP, "\n");
1486     }
1487     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1488                                       msdelay);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494
1495 /* Remove all highlighting escape sequences in s
1496    Also deletes any suffix starting with '(' 
1497    */
1498 char *
1499 StripHighlightAndTitle(s)
1500      char *s;
1501 {
1502     static char retbuf[MSG_SIZ];
1503     char *p = retbuf;
1504
1505     while (*s != NULLCHAR) {
1506         while (*s == '\033') {
1507             while (*s != NULLCHAR && !isalpha(*s)) s++;
1508             if (*s != NULLCHAR) s++;
1509         }
1510         while (*s != NULLCHAR && *s != '\033') {
1511             if (*s == '(' || *s == '[') {
1512                 *p = NULLCHAR;
1513                 return retbuf;
1514             }
1515             *p++ = *s++;
1516         }
1517     }
1518     *p = NULLCHAR;
1519     return retbuf;
1520 }
1521
1522 /* Remove all highlighting escape sequences in s */
1523 char *
1524 StripHighlight(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             *p++ = *s++;
1537         }
1538     }
1539     *p = NULLCHAR;
1540     return retbuf;
1541 }
1542
1543 char *variantNames[] = VARIANT_NAMES;
1544 char *
1545 VariantName(v)
1546      VariantClass v;
1547 {
1548     return variantNames[v];
1549 }
1550
1551
1552 /* Identify a variant from the strings the chess servers use or the
1553    PGN Variant tag names we use. */
1554 VariantClass
1555 StringToVariant(e)
1556      char *e;
1557 {
1558     char *p;
1559     int wnum = -1;
1560     VariantClass v = VariantNormal;
1561     int i, found = FALSE;
1562     char buf[MSG_SIZ];
1563
1564     if (!e) return v;
1565
1566     /* [HGM] skip over optional board-size prefixes */
1567     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569         while( *e++ != '_');
1570     }
1571
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr") 
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in 
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767     
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948
1949 }
1950
1951
1952 void
1953 VariantSwitch(Board board, VariantClass newVariant)
1954 {
1955    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974
1975   if (appData.debugMode) {
1976     fprintf(debugFP, "Switch board from %s to %s\n",
1977                VariantName(gameInfo.variant), VariantName(newVariant));
1978     setbuf(debugFP, NULL);
1979   }
1980     shuffleOpenings = 0;       /* [HGM] shuffle */
1981     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982     switch(newVariant) {
1983             case VariantShogi:
1984               newWidth = 9;  newHeight = 9;
1985               gameInfo.holdingsSize = 7;
1986             case VariantBughouse:
1987             case VariantCrazyhouse:
1988               newHoldingsWidth = 2; break;
1989             default:
1990               newHoldingsWidth = gameInfo.holdingsSize = 0;
1991     }
1992
1993     if(newWidth  != gameInfo.boardWidth  ||
1994        newHeight != gameInfo.boardHeight ||
1995        newHoldingsWidth != gameInfo.holdingsWidth ) {
1996
1997         /* shift position to new playing area, if needed */
1998         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999            for(i=0; i<BOARD_HEIGHT; i++) 
2000                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2002                                                      board[i][j];
2003            for(i=0; i<newHeight; i++) {
2004                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2006            }
2007         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++)
2009                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                  board[i][j];
2012         }
2013
2014         gameInfo.boardWidth  = newWidth;
2015         gameInfo.boardHeight = newHeight;
2016         gameInfo.holdingsWidth = newHoldingsWidth;
2017         gameInfo.variant = newVariant;
2018         InitDrawingSizes(-2, 0);
2019
2020         /* [HGM] The following should definitely be solved in a better way */
2021 #if 0
2022         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024         saveEP = epStatus[0];
2025 #endif
2026         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2027 #if 0
2028         epStatus[0] = saveEP;
2029         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2031 #endif
2032     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2033
2034     forwardMostMove = oldForwardMostMove;
2035     backwardMostMove = oldBackwardMostMove;
2036     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2037 }
2038
2039 static int loggedOn = FALSE;
2040
2041 /*-- Game start info cache: --*/
2042 int gs_gamenum;
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2049
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2052
2053 void
2054 read_from_ics(isr, closure, data, count, error)
2055      InputSourceRef isr;
2056      VOIDSTAR closure;
2057      char *data;
2058      int count;
2059      int error;
2060 {
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2070     
2071     static int started = STARTED_NONE;
2072     static char parse[20000];
2073     static int parse_pos = 0;
2074     static char buf[BUF_SIZE + 1];
2075     static int firstTime = TRUE, intfSet = FALSE;
2076     static ColorClass prevColor = ColorNormal;
2077     static int savingComment = FALSE;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085
2086     if (appData.debugMode) {
2087       if (!error) {
2088         fprintf(debugFP, "<ICS: ");
2089         show_bytes(debugFP, data, count);
2090         fprintf(debugFP, "\n");
2091       }
2092     }
2093
2094     if (appData.debugMode) { int f = forwardMostMove;
2095         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106         /* Copy in new characters, removing nulls and \r's */
2107         buf_len = leftover_len;
2108         for (i = 0; i < count; i++) {
2109             if (data[i] != NULLCHAR && data[i] != '\r')
2110               buf[buf_len++] = data[i];
2111             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2112                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2113                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2115             }
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 (
2420                     /* Regular tells and says */
2421                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2422                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2423                     looking_at(buf, &i, "* says: ") ||
2424                     /* Don't color "message" or "messages" output */
2425                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2426                     looking_at(buf, &i, "*. * at *:*: ") ||
2427                     looking_at(buf, &i, "--* (*:*): ") ||
2428                     /* Message notifications (same color as tells) */
2429                     looking_at(buf, &i, "* has left a message ") ||
2430                     looking_at(buf, &i, "* just sent you a message:\n") ||
2431                     /* Whispers and kibitzes */
2432                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2433                     looking_at(buf, &i, "* kibitzes: ") ||
2434                     /* Channel tells */
2435                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2436
2437                   if (tkind == 1 && strchr(star_match[0], ':')) {
2438                       /* Avoid "tells you:" spoofs in channels */
2439                      tkind = 3;
2440                   }
2441                   if (star_match[0][0] == NULLCHAR ||
2442                       strchr(star_match[0], ' ') ||
2443                       (tkind == 3 && strchr(star_match[1], ' '))) {
2444                     /* Reject bogus matches */
2445                     i = oldi;
2446                   } else {
2447                     if (appData.colorize) {
2448                       if (oldi > next_out) {
2449                         SendToPlayer(&buf[next_out], oldi - next_out);
2450                         next_out = oldi;
2451                       }
2452                       switch (tkind) {
2453                       case 1:
2454                         Colorize(ColorTell, FALSE);
2455                         curColor = ColorTell;
2456                         break;
2457                       case 2:
2458                         Colorize(ColorKibitz, FALSE);
2459                         curColor = ColorKibitz;
2460                         break;
2461                       case 3:
2462                         p = strrchr(star_match[1], '(');
2463                         if (p == NULL) {
2464                           p = star_match[1];
2465                         } else {
2466                           p++;
2467                         }
2468                         if (atoi(p) == 1) {
2469                           Colorize(ColorChannel1, FALSE);
2470                           curColor = ColorChannel1;
2471                         } else {
2472                           Colorize(ColorChannel, FALSE);
2473                           curColor = ColorChannel;
2474                         }
2475                         break;
2476                       case 5:
2477                         curColor = ColorNormal;
2478                         break;
2479                       }
2480                     }
2481                     if (started == STARTED_NONE && appData.autoComment &&
2482                         (gameMode == IcsObserving ||
2483                          gameMode == IcsPlayingWhite ||
2484                          gameMode == IcsPlayingBlack)) {
2485                       parse_pos = i - oldi;
2486                       memcpy(parse, &buf[oldi], parse_pos);
2487                       parse[parse_pos] = NULLCHAR;
2488                       started = STARTED_COMMENT;
2489                       savingComment = TRUE;
2490                     } else {
2491                       started = STARTED_CHATTER;
2492                       savingComment = FALSE;
2493                     }
2494                     loggedOn = TRUE;
2495                     continue;
2496                   }
2497                 }
2498
2499                 if (looking_at(buf, &i, "* s-shouts: ") ||
2500                     looking_at(buf, &i, "* c-shouts: ")) {
2501                     if (appData.colorize) {
2502                         if (oldi > next_out) {
2503                             SendToPlayer(&buf[next_out], oldi - next_out);
2504                             next_out = oldi;
2505                         }
2506                         Colorize(ColorSShout, FALSE);
2507                         curColor = ColorSShout;
2508                     }
2509                     loggedOn = TRUE;
2510                     started = STARTED_CHATTER;
2511                     continue;
2512                 }
2513
2514                 if (looking_at(buf, &i, "--->")) {
2515                     loggedOn = TRUE;
2516                     continue;
2517                 }
2518
2519                 if (looking_at(buf, &i, "* shouts: ") ||
2520                     looking_at(buf, &i, "--> ")) {
2521                     if (appData.colorize) {
2522                         if (oldi > next_out) {
2523                             SendToPlayer(&buf[next_out], oldi - next_out);
2524                             next_out = oldi;
2525                         }
2526                         Colorize(ColorShout, FALSE);
2527                         curColor = ColorShout;
2528                     }
2529                     loggedOn = TRUE;
2530                     started = STARTED_CHATTER;
2531                     continue;
2532                 }
2533
2534                 if (looking_at( buf, &i, "Challenge:")) {
2535                     if (appData.colorize) {
2536                         if (oldi > next_out) {
2537                             SendToPlayer(&buf[next_out], oldi - next_out);
2538                             next_out = oldi;
2539                         }
2540                         Colorize(ColorChallenge, FALSE);
2541                         curColor = ColorChallenge;
2542                     }
2543                     loggedOn = TRUE;
2544                     continue;
2545                 }
2546
2547                 if (looking_at(buf, &i, "* offers you") ||
2548                     looking_at(buf, &i, "* offers to be") ||
2549                     looking_at(buf, &i, "* would like to") ||
2550                     looking_at(buf, &i, "* requests to") ||
2551                     looking_at(buf, &i, "Your opponent offers") ||
2552                     looking_at(buf, &i, "Your opponent requests")) {
2553
2554                     if (appData.colorize) {
2555                         if (oldi > next_out) {
2556                             SendToPlayer(&buf[next_out], oldi - next_out);
2557                             next_out = oldi;
2558                         }
2559                         Colorize(ColorRequest, FALSE);
2560                         curColor = ColorRequest;
2561                     }
2562                     continue;
2563                 }
2564
2565                 if (looking_at(buf, &i, "* (*) seeking")) {
2566                     if (appData.colorize) {
2567                         if (oldi > next_out) {
2568                             SendToPlayer(&buf[next_out], oldi - next_out);
2569                             next_out = oldi;
2570                         }
2571                         Colorize(ColorSeek, FALSE);
2572                         curColor = ColorSeek;
2573                     }
2574                     continue;
2575             }
2576
2577             if (looking_at(buf, &i, "\\   ")) {
2578                 if (prevColor != ColorNormal) {
2579                     if (oldi > next_out) {
2580                         SendToPlayer(&buf[next_out], oldi - next_out);
2581                         next_out = oldi;
2582                     }
2583                     Colorize(prevColor, TRUE);
2584                     curColor = prevColor;
2585                 }
2586                 if (savingComment) {
2587                     parse_pos = i - oldi;
2588                     memcpy(parse, &buf[oldi], parse_pos);
2589                     parse[parse_pos] = NULLCHAR;
2590                     started = STARTED_COMMENT;
2591                 } else {
2592                     started = STARTED_CHATTER;
2593                 }
2594                 continue;
2595             }
2596
2597             if (looking_at(buf, &i, "Black Strength :") ||
2598                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2599                 looking_at(buf, &i, "<10>") ||
2600                 looking_at(buf, &i, "#@#")) {
2601                 /* Wrong board style */
2602                 loggedOn = TRUE;
2603                 SendToICS(ics_prefix);
2604                 SendToICS("set style 12\n");
2605                 SendToICS(ics_prefix);
2606                 SendToICS("refresh\n");
2607                 continue;
2608             }
2609             
2610             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2611                 ICSInitScript();
2612                 have_sent_ICS_logon = 1;
2613                 continue;
2614             }
2615               
2616             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2617                 (looking_at(buf, &i, "\n<12> ") ||
2618                  looking_at(buf, &i, "<12> "))) {
2619                 loggedOn = TRUE;
2620                 if (oldi > next_out) {
2621                     SendToPlayer(&buf[next_out], oldi - next_out);
2622                 }
2623                 next_out = i;
2624                 started = STARTED_BOARD;
2625                 parse_pos = 0;
2626                 continue;
2627             }
2628
2629             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2630                 looking_at(buf, &i, "<b1> ")) {
2631                 if (oldi > next_out) {
2632                     SendToPlayer(&buf[next_out], oldi - next_out);
2633                 }
2634                 next_out = i;
2635                 started = STARTED_HOLDINGS;
2636                 parse_pos = 0;
2637                 continue;
2638             }
2639
2640             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2641                 loggedOn = TRUE;
2642                 /* Header for a move list -- first line */
2643
2644                 switch (ics_getting_history) {
2645                   case H_FALSE:
2646                     switch (gameMode) {
2647                       case IcsIdle:
2648                       case BeginningOfGame:
2649                         /* User typed "moves" or "oldmoves" while we
2650                            were idle.  Pretend we asked for these
2651                            moves and soak them up so user can step
2652                            through them and/or save them.
2653                            */
2654                         Reset(FALSE, TRUE);
2655                         gameMode = IcsObserving;
2656                         ModeHighlight();
2657                         ics_gamenum = -1;
2658                         ics_getting_history = H_GOT_UNREQ_HEADER;
2659                         break;
2660                       case EditGame: /*?*/
2661                       case EditPosition: /*?*/
2662                         /* Should above feature work in these modes too? */
2663                         /* For now it doesn't */
2664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2665                         break;
2666                       default:
2667                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2668                         break;
2669                     }
2670                     break;
2671                   case H_REQUESTED:
2672                     /* Is this the right one? */
2673                     if (gameInfo.white && gameInfo.black &&
2674                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2675                         strcmp(gameInfo.black, star_match[2]) == 0) {
2676                         /* All is well */
2677                         ics_getting_history = H_GOT_REQ_HEADER;
2678                     }
2679                     break;
2680                   case H_GOT_REQ_HEADER:
2681                   case H_GOT_UNREQ_HEADER:
2682                   case H_GOT_UNWANTED_HEADER:
2683                   case H_GETTING_MOVES:
2684                     /* Should not happen */
2685                     DisplayError(_("Error gathering move list: two headers"), 0);
2686                     ics_getting_history = H_FALSE;
2687                     break;
2688                 }
2689
2690                 /* Save player ratings into gameInfo if needed */
2691                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2692                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2693                     (gameInfo.whiteRating == -1 ||
2694                      gameInfo.blackRating == -1)) {
2695
2696                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2697                     gameInfo.blackRating = string_to_rating(star_match[3]);
2698                     if (appData.debugMode)
2699                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2700                               gameInfo.whiteRating, gameInfo.blackRating);
2701                 }
2702                 continue;
2703             }
2704
2705             if (looking_at(buf, &i,
2706               "* * match, initial time: * minute*, increment: * second")) {
2707                 /* Header for a move list -- second line */
2708                 /* Initial board will follow if this is a wild game */
2709                 if (gameInfo.event != NULL) free(gameInfo.event);
2710                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2711                 gameInfo.event = StrSave(str);
2712                 /* [HGM] we switched variant. Translate boards if needed. */
2713                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2714                 continue;
2715             }
2716
2717             if (looking_at(buf, &i, "Move  ")) {
2718                 /* Beginning of a move list */
2719                 switch (ics_getting_history) {
2720                   case H_FALSE:
2721                     /* Normally should not happen */
2722                     /* Maybe user hit reset while we were parsing */
2723                     break;
2724                   case H_REQUESTED:
2725                     /* Happens if we are ignoring a move list that is not
2726                      * the one we just requested.  Common if the user
2727                      * tries to observe two games without turning off
2728                      * getMoveList */
2729                     break;
2730                   case H_GETTING_MOVES:
2731                     /* Should not happen */
2732                     DisplayError(_("Error gathering move list: nested"), 0);
2733                     ics_getting_history = H_FALSE;
2734                     break;
2735                   case H_GOT_REQ_HEADER:
2736                     ics_getting_history = H_GETTING_MOVES;
2737                     started = STARTED_MOVES;
2738                     parse_pos = 0;
2739                     if (oldi > next_out) {
2740                         SendToPlayer(&buf[next_out], oldi - next_out);
2741                     }
2742                     break;
2743                   case H_GOT_UNREQ_HEADER:
2744                     ics_getting_history = H_GETTING_MOVES;
2745                     started = STARTED_MOVES_NOHIDE;
2746                     parse_pos = 0;
2747                     break;
2748                   case H_GOT_UNWANTED_HEADER:
2749                     ics_getting_history = H_FALSE;
2750                     break;
2751                 }
2752                 continue;
2753             }                           
2754             
2755             if (looking_at(buf, &i, "% ") ||
2756                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2757                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2758                 savingComment = FALSE;
2759                 switch (started) {
2760                   case STARTED_MOVES:
2761                   case STARTED_MOVES_NOHIDE:
2762                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2763                     parse[parse_pos + i - oldi] = NULLCHAR;
2764                     ParseGameHistory(parse);
2765 #if ZIPPY
2766                     if (appData.zippyPlay && first.initDone) {
2767                         FeedMovesToProgram(&first, forwardMostMove);
2768                         if (gameMode == IcsPlayingWhite) {
2769                             if (WhiteOnMove(forwardMostMove)) {
2770                                 if (first.sendTime) {
2771                                   if (first.useColors) {
2772                                     SendToProgram("black\n", &first); 
2773                                   }
2774                                   SendTimeRemaining(&first, TRUE);
2775                                 }
2776 #if 0
2777                                 if (first.useColors) {
2778                                   SendToProgram("white\ngo\n", &first);
2779                                 } else {
2780                                   SendToProgram("go\n", &first);
2781                                 }
2782 #else
2783                                 if (first.useColors) {
2784                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2785                                 }
2786                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2787 #endif
2788                                 first.maybeThinking = TRUE;
2789                             } else {
2790                                 if (first.usePlayother) {
2791                                   if (first.sendTime) {
2792                                     SendTimeRemaining(&first, TRUE);
2793                                   }
2794                                   SendToProgram("playother\n", &first);
2795                                   firstMove = FALSE;
2796                                 } else {
2797                                   firstMove = TRUE;
2798                                 }
2799                             }
2800                         } else if (gameMode == IcsPlayingBlack) {
2801                             if (!WhiteOnMove(forwardMostMove)) {
2802                                 if (first.sendTime) {
2803                                   if (first.useColors) {
2804                                     SendToProgram("white\n", &first);
2805                                   }
2806                                   SendTimeRemaining(&first, FALSE);
2807                                 }
2808 #if 0
2809                                 if (first.useColors) {
2810                                   SendToProgram("black\ngo\n", &first);
2811                                 } else {
2812                                   SendToProgram("go\n", &first);
2813                                 }
2814 #else
2815                                 if (first.useColors) {
2816                                   SendToProgram("black\n", &first);
2817                                 }
2818                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2819 #endif
2820                                 first.maybeThinking = TRUE;
2821                             } else {
2822                                 if (first.usePlayother) {
2823                                   if (first.sendTime) {
2824                                     SendTimeRemaining(&first, FALSE);
2825                                   }
2826                                   SendToProgram("playother\n", &first);
2827                                   firstMove = FALSE;
2828                                 } else {
2829                                   firstMove = TRUE;
2830                                 }
2831                             }
2832                         }                       
2833                     }
2834 #endif
2835                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2836                         /* Moves came from oldmoves or moves command
2837                            while we weren't doing anything else.
2838                            */
2839                         currentMove = forwardMostMove;
2840                         ClearHighlights();/*!!could figure this out*/
2841                         flipView = appData.flipView;
2842                         DrawPosition(FALSE, boards[currentMove]);
2843                         DisplayBothClocks();
2844                         sprintf(str, "%s vs. %s",
2845                                 gameInfo.white, gameInfo.black);
2846                         DisplayTitle(str);
2847                         gameMode = IcsIdle;
2848                     } else {
2849                         /* Moves were history of an active game */
2850                         if (gameInfo.resultDetails != NULL) {
2851                             free(gameInfo.resultDetails);
2852                             gameInfo.resultDetails = NULL;
2853                         }
2854                     }
2855                     HistorySet(parseList, backwardMostMove,
2856                                forwardMostMove, currentMove-1);
2857                     DisplayMove(currentMove - 1);
2858                     if (started == STARTED_MOVES) next_out = i;
2859                     started = STARTED_NONE;
2860                     ics_getting_history = H_FALSE;
2861                     break;
2862
2863                   case STARTED_OBSERVE:
2864                     started = STARTED_NONE;
2865                     SendToICS(ics_prefix);
2866                     SendToICS("refresh\n");
2867                     break;
2868
2869                   default:
2870                     break;
2871                 }
2872                 if(bookHit) { // [HGM] book: simulate book reply
2873                     static char bookMove[MSG_SIZ]; // a bit generous?
2874
2875                     programStats.nodes = programStats.depth = programStats.time = 
2876                     programStats.score = programStats.got_only_move = 0;
2877                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2878
2879                     strcpy(bookMove, "move ");
2880                     strcat(bookMove, bookHit);
2881                     HandleMachineMove(bookMove, &first);
2882                 }
2883                 continue;
2884             }
2885             
2886             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2887                  started == STARTED_HOLDINGS ||
2888                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2889                 /* Accumulate characters in move list or board */
2890                 parse[parse_pos++] = buf[i];
2891             }
2892             
2893             /* Start of game messages.  Mostly we detect start of game
2894                when the first board image arrives.  On some versions
2895                of the ICS, though, we need to do a "refresh" after starting
2896                to observe in order to get the current board right away. */
2897             if (looking_at(buf, &i, "Adding game * to observation list")) {
2898                 started = STARTED_OBSERVE;
2899                 continue;
2900             }
2901
2902             /* Handle auto-observe */
2903             if (appData.autoObserve &&
2904                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2905                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2906                 char *player;
2907                 /* Choose the player that was highlighted, if any. */
2908                 if (star_match[0][0] == '\033' ||
2909                     star_match[1][0] != '\033') {
2910                     player = star_match[0];
2911                 } else {
2912                     player = star_match[2];
2913                 }
2914                 sprintf(str, "%sobserve %s\n",
2915                         ics_prefix, StripHighlightAndTitle(player));
2916                 SendToICS(str);
2917
2918                 /* Save ratings from notify string */
2919                 strcpy(player1Name, star_match[0]);
2920                 player1Rating = string_to_rating(star_match[1]);
2921                 strcpy(player2Name, star_match[2]);
2922                 player2Rating = string_to_rating(star_match[3]);
2923
2924                 if (appData.debugMode)
2925                   fprintf(debugFP, 
2926                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2927                           player1Name, player1Rating,
2928                           player2Name, player2Rating);
2929
2930                 continue;
2931             }
2932
2933             /* Deal with automatic examine mode after a game,
2934                and with IcsObserving -> IcsExamining transition */
2935             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2936                 looking_at(buf, &i, "has made you an examiner of game *")) {
2937
2938                 int gamenum = atoi(star_match[0]);
2939                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2940                     gamenum == ics_gamenum) {
2941                     /* We were already playing or observing this game;
2942                        no need to refetch history */
2943                     gameMode = IcsExamining;
2944                     if (pausing) {
2945                         pauseExamForwardMostMove = forwardMostMove;
2946                     } else if (currentMove < forwardMostMove) {
2947                         ForwardInner(forwardMostMove);
2948                     }
2949                 } else {
2950                     /* I don't think this case really can happen */
2951                     SendToICS(ics_prefix);
2952                     SendToICS("refresh\n");
2953                 }
2954                 continue;
2955             }    
2956             
2957             /* Error messages */
2958 //          if (ics_user_moved) {
2959             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2960                 if (looking_at(buf, &i, "Illegal move") ||
2961                     looking_at(buf, &i, "Not a legal move") ||
2962                     looking_at(buf, &i, "Your king is in check") ||
2963                     looking_at(buf, &i, "It isn't your turn") ||
2964                     looking_at(buf, &i, "It is not your move")) {
2965                     /* Illegal move */
2966                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2967                         currentMove = --forwardMostMove;
2968                         DisplayMove(currentMove - 1); /* before DMError */
2969                         DrawPosition(FALSE, boards[currentMove]);
2970                         SwitchClocks();
2971                         DisplayBothClocks();
2972                     }
2973                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2974                     ics_user_moved = 0;
2975                     continue;
2976                 }
2977             }
2978
2979             if (looking_at(buf, &i, "still have time") ||
2980                 looking_at(buf, &i, "not out of time") ||
2981                 looking_at(buf, &i, "either player is out of time") ||
2982                 looking_at(buf, &i, "has timeseal; checking")) {
2983                 /* We must have called his flag a little too soon */
2984                 whiteFlag = blackFlag = FALSE;
2985                 continue;
2986             }
2987
2988             if (looking_at(buf, &i, "added * seconds to") ||
2989                 looking_at(buf, &i, "seconds were added to")) {
2990                 /* Update the clocks */
2991                 SendToICS(ics_prefix);
2992                 SendToICS("refresh\n");
2993                 continue;
2994             }
2995
2996             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2997                 ics_clock_paused = TRUE;
2998                 StopClocks();
2999                 continue;
3000             }
3001
3002             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3003                 ics_clock_paused = FALSE;
3004                 StartClocks();
3005                 continue;
3006             }
3007
3008             /* Grab player ratings from the Creating: message.
3009                Note we have to check for the special case when
3010                the ICS inserts things like [white] or [black]. */
3011             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3012                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3013                 /* star_matches:
3014                    0    player 1 name (not necessarily white)
3015                    1    player 1 rating
3016                    2    empty, white, or black (IGNORED)
3017                    3    player 2 name (not necessarily black)
3018                    4    player 2 rating
3019                    
3020                    The names/ratings are sorted out when the game
3021                    actually starts (below).
3022                 */
3023                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3024                 player1Rating = string_to_rating(star_match[1]);
3025                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3026                 player2Rating = string_to_rating(star_match[4]);
3027
3028                 if (appData.debugMode)
3029                   fprintf(debugFP, 
3030                           "Ratings from 'Creating:' %s %d, %s %d\n",
3031                           player1Name, player1Rating,
3032                           player2Name, player2Rating);
3033
3034                 continue;
3035             }
3036             
3037             /* Improved generic start/end-of-game messages */
3038             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3039                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3040                 /* If tkind == 0: */
3041                 /* star_match[0] is the game number */
3042                 /*           [1] is the white player's name */
3043                 /*           [2] is the black player's name */
3044                 /* For end-of-game: */
3045                 /*           [3] is the reason for the game end */
3046                 /*           [4] is a PGN end game-token, preceded by " " */
3047                 /* For start-of-game: */
3048                 /*           [3] begins with "Creating" or "Continuing" */
3049                 /*           [4] is " *" or empty (don't care). */
3050                 int gamenum = atoi(star_match[0]);
3051                 char *whitename, *blackname, *why, *endtoken;
3052                 ChessMove endtype = (ChessMove) 0;
3053
3054                 if (tkind == 0) {
3055                   whitename = star_match[1];
3056                   blackname = star_match[2];
3057                   why = star_match[3];
3058                   endtoken = star_match[4];
3059                 } else {
3060                   whitename = star_match[1];
3061                   blackname = star_match[3];
3062                   why = star_match[5];
3063                   endtoken = star_match[6];
3064                 }
3065
3066                 /* Game start messages */
3067                 if (strncmp(why, "Creating ", 9) == 0 ||
3068                     strncmp(why, "Continuing ", 11) == 0) {
3069                     gs_gamenum = gamenum;
3070                     strcpy(gs_kind, strchr(why, ' ') + 1);
3071 #if ZIPPY
3072                     if (appData.zippyPlay) {
3073                         ZippyGameStart(whitename, blackname);
3074                     }
3075 #endif /*ZIPPY*/
3076                     continue;
3077                 }
3078
3079                 /* Game end messages */
3080                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3081                     ics_gamenum != gamenum) {
3082                     continue;
3083                 }
3084                 while (endtoken[0] == ' ') endtoken++;
3085                 switch (endtoken[0]) {
3086                   case '*':
3087                   default:
3088                     endtype = GameUnfinished;
3089                     break;
3090                   case '0':
3091                     endtype = BlackWins;
3092                     break;
3093                   case '1':
3094                     if (endtoken[1] == '/')
3095                       endtype = GameIsDrawn;
3096                     else
3097                       endtype = WhiteWins;
3098                     break;
3099                 }
3100                 GameEnds(endtype, why, GE_ICS);
3101 #if ZIPPY
3102                 if (appData.zippyPlay && first.initDone) {
3103                     ZippyGameEnd(endtype, why);
3104                     if (first.pr == NULL) {
3105                       /* Start the next process early so that we'll
3106                          be ready for the next challenge */
3107                       StartChessProgram(&first);
3108                     }
3109                     /* Send "new" early, in case this command takes
3110                        a long time to finish, so that we'll be ready
3111                        for the next challenge. */
3112                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3113                     Reset(TRUE, TRUE);
3114                 }
3115 #endif /*ZIPPY*/
3116                 continue;
3117             }
3118
3119             if (looking_at(buf, &i, "Removing game * from observation") ||
3120                 looking_at(buf, &i, "no longer observing game *") ||
3121                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3122                 if (gameMode == IcsObserving &&
3123                     atoi(star_match[0]) == ics_gamenum)
3124                   {
3125                       /* icsEngineAnalyze */
3126                       if (appData.icsEngineAnalyze) {
3127                             ExitAnalyzeMode();
3128                             ModeHighlight();
3129                       }
3130                       StopClocks();
3131                       gameMode = IcsIdle;
3132                       ics_gamenum = -1;
3133                       ics_user_moved = FALSE;
3134                   }
3135                 continue;
3136             }
3137
3138             if (looking_at(buf, &i, "no longer examining game *")) {
3139                 if (gameMode == IcsExamining &&
3140                     atoi(star_match[0]) == ics_gamenum)
3141                   {
3142                       gameMode = IcsIdle;
3143                       ics_gamenum = -1;
3144                       ics_user_moved = FALSE;
3145                   }
3146                 continue;
3147             }
3148
3149             /* Advance leftover_start past any newlines we find,
3150                so only partial lines can get reparsed */
3151             if (looking_at(buf, &i, "\n")) {
3152                 prevColor = curColor;
3153                 if (curColor != ColorNormal) {
3154                     if (oldi > next_out) {
3155                         SendToPlayer(&buf[next_out], oldi - next_out);
3156                         next_out = oldi;
3157                     }
3158                     Colorize(ColorNormal, FALSE);
3159                     curColor = ColorNormal;
3160                 }
3161                 if (started == STARTED_BOARD) {
3162                     started = STARTED_NONE;
3163                     parse[parse_pos] = NULLCHAR;
3164                     ParseBoard12(parse);
3165                     ics_user_moved = 0;
3166
3167                     /* Send premove here */
3168                     if (appData.premove) {
3169                       char str[MSG_SIZ];
3170                       if (currentMove == 0 &&
3171                           gameMode == IcsPlayingWhite &&
3172                           appData.premoveWhite) {
3173                         sprintf(str, "%s%s\n", ics_prefix,
3174                                 appData.premoveWhiteText);
3175                         if (appData.debugMode)
3176                           fprintf(debugFP, "Sending premove:\n");
3177                         SendToICS(str);
3178                       } else if (currentMove == 1 &&
3179                                  gameMode == IcsPlayingBlack &&
3180                                  appData.premoveBlack) {
3181                         sprintf(str, "%s%s\n", ics_prefix,
3182                                 appData.premoveBlackText);
3183                         if (appData.debugMode)
3184                           fprintf(debugFP, "Sending premove:\n");
3185                         SendToICS(str);
3186                       } else if (gotPremove) {
3187                         gotPremove = 0;
3188                         ClearPremoveHighlights();
3189                         if (appData.debugMode)
3190                           fprintf(debugFP, "Sending premove:\n");
3191                           UserMoveEvent(premoveFromX, premoveFromY, 
3192                                         premoveToX, premoveToY, 
3193                                         premovePromoChar);
3194                       }
3195                     }
3196
3197                     /* Usually suppress following prompt */
3198                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3199                         if (looking_at(buf, &i, "*% ")) {
3200                             savingComment = FALSE;
3201                         }
3202                     }
3203                     next_out = i;
3204                 } else if (started == STARTED_HOLDINGS) {
3205                     int gamenum;
3206                     char new_piece[MSG_SIZ];
3207                     started = STARTED_NONE;
3208                     parse[parse_pos] = NULLCHAR;
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3211                                                         parse, currentMove);
3212                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3213                         gamenum == ics_gamenum) {
3214                         if (gameInfo.variant == VariantNormal) {
3215                           /* [HGM] We seem to switch variant during a game!
3216                            * Presumably no holdings were displayed, so we have
3217                            * to move the position two files to the right to
3218                            * create room for them!
3219                            */
3220                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3221                           /* Get a move list just to see the header, which
3222                              will tell us whether this is really bug or zh */
3223                           if (ics_getting_history == H_FALSE) {
3224                             ics_getting_history = H_REQUESTED;
3225                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3226                             SendToICS(str);
3227                           }
3228                         }
3229                         new_piece[0] = NULLCHAR;
3230                         sscanf(parse, "game %d white [%s black [%s <- %s",
3231                                &gamenum, white_holding, black_holding,
3232                                new_piece);
3233                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3234                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3235                         /* [HGM] copy holdings to board holdings area */
3236                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3237                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3238 #if ZIPPY
3239                         if (appData.zippyPlay && first.initDone) {
3240                             ZippyHoldings(white_holding, black_holding,
3241                                           new_piece);
3242                         }
3243 #endif /*ZIPPY*/
3244                         if (tinyLayout || smallLayout) {
3245                             char wh[16], bh[16];
3246                             PackHolding(wh, white_holding);
3247                             PackHolding(bh, black_holding);
3248                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3249                                     gameInfo.white, gameInfo.black);
3250                         } else {
3251                             sprintf(str, "%s [%s] vs. %s [%s]",
3252                                     gameInfo.white, white_holding,
3253                                     gameInfo.black, black_holding);
3254                         }
3255
3256                         DrawPosition(FALSE, boards[currentMove]);
3257                         DisplayTitle(str);
3258                     }
3259                     /* Suppress following prompt */
3260                     if (looking_at(buf, &i, "*% ")) {
3261                         savingComment = FALSE;
3262                     }
3263                     next_out = i;
3264                 }
3265                 continue;
3266             }
3267
3268             i++;                /* skip unparsed character and loop back */
3269         }
3270         
3271         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3272             started != STARTED_HOLDINGS && i > next_out) {
3273             SendToPlayer(&buf[next_out], i - next_out);
3274             next_out = i;
3275         }
3276         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3277         
3278         leftover_len = buf_len - leftover_start;
3279         /* if buffer ends with something we couldn't parse,
3280            reparse it after appending the next read */
3281         
3282     } else if (count == 0) {
3283         RemoveInputSource(isr);
3284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3285     } else {
3286         DisplayFatalError(_("Error reading from ICS"), error, 1);
3287     }
3288 }
3289
3290
3291 /* Board style 12 looks like this:
3292    
3293    <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
3294    
3295  * The "<12> " is stripped before it gets to this routine.  The two
3296  * trailing 0's (flip state and clock ticking) are later addition, and
3297  * some chess servers may not have them, or may have only the first.
3298  * Additional trailing fields may be added in the future.  
3299  */
3300
3301 #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"
3302
3303 #define RELATION_OBSERVING_PLAYED    0
3304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3305 #define RELATION_PLAYING_MYMOVE      1
3306 #define RELATION_PLAYING_NOTMYMOVE  -1
3307 #define RELATION_EXAMINING           2
3308 #define RELATION_ISOLATED_BOARD     -3
3309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3310
3311 void
3312 ParseBoard12(string)
3313      char *string;
3314
3315     GameMode newGameMode;
3316     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3317     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3318     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3319     char to_play, board_chars[200];
3320     char move_str[500], str[500], elapsed_time[500];
3321     char black[32], white[32];
3322     Board board;
3323     int prevMove = currentMove;
3324     int ticking = 2;
3325     ChessMove moveType;
3326     int fromX, fromY, toX, toY;
3327     char promoChar;
3328     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3329     char *bookHit = NULL; // [HGM] book
3330
3331     fromX = fromY = toX = toY = -1;
3332     
3333     newGame = FALSE;
3334
3335     if (appData.debugMode)
3336       fprintf(debugFP, _("Parsing board: %s\n"), string);
3337
3338     move_str[0] = NULLCHAR;
3339     elapsed_time[0] = NULLCHAR;
3340     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3341         int  i = 0, j;
3342         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3343             if(string[i] == ' ') { ranks++; files = 0; }
3344             else files++;
3345             i++;
3346         }
3347         for(j = 0; j <i; j++) board_chars[j] = string[j];
3348         board_chars[i] = '\0';
3349         string += i + 1;
3350     }
3351     n = sscanf(string, PATTERN, &to_play, &double_push,
3352                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3353                &gamenum, white, black, &relation, &basetime, &increment,
3354                &white_stren, &black_stren, &white_time, &black_time,
3355                &moveNum, str, elapsed_time, move_str, &ics_flip,
3356                &ticking);
3357
3358     if (n < 21) {
3359         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3360         DisplayError(str, 0);
3361         return;
3362     }
3363
3364     /* Convert the move number to internal form */
3365     moveNum = (moveNum - 1) * 2;
3366     if (to_play == 'B') moveNum++;
3367     if (moveNum >= MAX_MOVES) {
3368       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3369                         0, 1);
3370       return;
3371     }
3372     
3373     switch (relation) {
3374       case RELATION_OBSERVING_PLAYED:
3375       case RELATION_OBSERVING_STATIC:
3376         if (gamenum == -1) {
3377             /* Old ICC buglet */
3378             relation = RELATION_OBSERVING_STATIC;
3379         }
3380         newGameMode = IcsObserving;
3381         break;
3382       case RELATION_PLAYING_MYMOVE:
3383       case RELATION_PLAYING_NOTMYMOVE:
3384         newGameMode =
3385           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3386             IcsPlayingWhite : IcsPlayingBlack;
3387         break;
3388       case RELATION_EXAMINING:
3389         newGameMode = IcsExamining;
3390         break;
3391       case RELATION_ISOLATED_BOARD:
3392       default:
3393         /* Just display this board.  If user was doing something else,
3394            we will forget about it until the next board comes. */ 
3395         newGameMode = IcsIdle;
3396         break;
3397       case RELATION_STARTING_POSITION:
3398         newGameMode = gameMode;
3399         break;
3400     }
3401     
3402     /* Modify behavior for initial board display on move listing
3403        of wild games.
3404        */
3405     switch (ics_getting_history) {
3406       case H_FALSE:
3407       case H_REQUESTED:
3408         break;
3409       case H_GOT_REQ_HEADER:
3410       case H_GOT_UNREQ_HEADER:
3411         /* This is the initial position of the current game */
3412         gamenum = ics_gamenum;
3413         moveNum = 0;            /* old ICS bug workaround */
3414         if (to_play == 'B') {
3415           startedFromSetupPosition = TRUE;
3416           blackPlaysFirst = TRUE;
3417           moveNum = 1;
3418           if (forwardMostMove == 0) forwardMostMove = 1;
3419           if (backwardMostMove == 0) backwardMostMove = 1;
3420           if (currentMove == 0) currentMove = 1;
3421         }
3422         newGameMode = gameMode;
3423         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3424         break;
3425       case H_GOT_UNWANTED_HEADER:
3426         /* This is an initial board that we don't want */
3427         return;
3428       case H_GETTING_MOVES:
3429         /* Should not happen */
3430         DisplayError(_("Error gathering move list: extra board"), 0);
3431         ics_getting_history = H_FALSE;
3432         return;
3433     }
3434     
3435     /* Take action if this is the first board of a new game, or of a
3436        different game than is currently being displayed.  */
3437     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3438         relation == RELATION_ISOLATED_BOARD) {
3439         
3440         /* Forget the old game and get the history (if any) of the new one */
3441         if (gameMode != BeginningOfGame) {
3442           Reset(FALSE, TRUE);
3443         }
3444         newGame = TRUE;
3445         if (appData.autoRaiseBoard) BoardToTop();
3446         prevMove = -3;
3447         if (gamenum == -1) {
3448             newGameMode = IcsIdle;
3449         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3450                    appData.getMoveList) {
3451             /* Need to get game history */
3452             ics_getting_history = H_REQUESTED;
3453             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3454             SendToICS(str);
3455         }
3456         
3457         /* Initially flip the board to have black on the bottom if playing
3458            black or if the ICS flip flag is set, but let the user change
3459            it with the Flip View button. */
3460         flipView = appData.autoFlipView ? 
3461           (newGameMode == IcsPlayingBlack) || ics_flip :
3462           appData.flipView;
3463         
3464         /* Done with values from previous mode; copy in new ones */
3465         gameMode = newGameMode;
3466         ModeHighlight();
3467         ics_gamenum = gamenum;
3468         if (gamenum == gs_gamenum) {
3469             int klen = strlen(gs_kind);
3470             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3471             sprintf(str, "ICS %s", gs_kind);
3472             gameInfo.event = StrSave(str);
3473         } else {
3474             gameInfo.event = StrSave("ICS game");
3475         }
3476         gameInfo.site = StrSave(appData.icsHost);
3477         gameInfo.date = PGNDate();
3478         gameInfo.round = StrSave("-");
3479         gameInfo.white = StrSave(white);
3480         gameInfo.black = StrSave(black);
3481         timeControl = basetime * 60 * 1000;
3482         timeControl_2 = 0;
3483         timeIncrement = increment * 1000;
3484         movesPerSession = 0;
3485         gameInfo.timeControl = TimeControlTagValue();
3486         VariantSwitch(board, StringToVariant(gameInfo.event) );
3487   if (appData.debugMode) {
3488     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3489     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3490     setbuf(debugFP, NULL);
3491   }
3492
3493         gameInfo.outOfBook = NULL;
3494         
3495         /* Do we have the ratings? */
3496         if (strcmp(player1Name, white) == 0 &&
3497             strcmp(player2Name, black) == 0) {
3498             if (appData.debugMode)
3499               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3500                       player1Rating, player2Rating);
3501             gameInfo.whiteRating = player1Rating;
3502             gameInfo.blackRating = player2Rating;
3503         } else if (strcmp(player2Name, white) == 0 &&
3504                    strcmp(player1Name, black) == 0) {
3505             if (appData.debugMode)
3506               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3507                       player2Rating, player1Rating);
3508             gameInfo.whiteRating = player2Rating;
3509             gameInfo.blackRating = player1Rating;
3510         }
3511         player1Name[0] = player2Name[0] = NULLCHAR;
3512
3513         /* Silence shouts if requested */
3514         if (appData.quietPlay &&
3515             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3516             SendToICS(ics_prefix);
3517             SendToICS("set shout 0\n");
3518         }
3519     }
3520     
3521     /* Deal with midgame name changes */
3522     if (!newGame) {
3523         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3524             if (gameInfo.white) free(gameInfo.white);
3525             gameInfo.white = StrSave(white);
3526         }
3527         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3528             if (gameInfo.black) free(gameInfo.black);
3529             gameInfo.black = StrSave(black);
3530         }
3531     }
3532     
3533     /* Throw away game result if anything actually changes in examine mode */
3534     if (gameMode == IcsExamining && !newGame) {
3535         gameInfo.result = GameUnfinished;
3536         if (gameInfo.resultDetails != NULL) {
3537             free(gameInfo.resultDetails);
3538             gameInfo.resultDetails = NULL;
3539         }
3540     }
3541     
3542     /* In pausing && IcsExamining mode, we ignore boards coming
3543        in if they are in a different variation than we are. */
3544     if (pauseExamInvalid) return;
3545     if (pausing && gameMode == IcsExamining) {
3546         if (moveNum <= pauseExamForwardMostMove) {
3547             pauseExamInvalid = TRUE;
3548             forwardMostMove = pauseExamForwardMostMove;
3549             return;
3550         }
3551     }
3552     
3553   if (appData.debugMode) {
3554     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3555   }
3556     /* Parse the board */
3557     for (k = 0; k < ranks; k++) {
3558       for (j = 0; j < files; j++)
3559         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3560       if(gameInfo.holdingsWidth > 1) {
3561            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3562            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3563       }
3564     }
3565     CopyBoard(boards[moveNum], board);
3566     if (moveNum == 0) {
3567         startedFromSetupPosition =
3568           !CompareBoards(board, initialPosition);
3569         if(startedFromSetupPosition)
3570             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3571     }
3572
3573     /* [HGM] Set castling rights. Take the outermost Rooks,
3574        to make it also work for FRC opening positions. Note that board12
3575        is really defective for later FRC positions, as it has no way to
3576        indicate which Rook can castle if they are on the same side of King.
3577        For the initial position we grant rights to the outermost Rooks,
3578        and remember thos rights, and we then copy them on positions
3579        later in an FRC game. This means WB might not recognize castlings with
3580        Rooks that have moved back to their original position as illegal,
3581        but in ICS mode that is not its job anyway.
3582     */
3583     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3584     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3585
3586         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590             if(board[0][i] == WhiteRook) j = i;
3591         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598
3599         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3600         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3602         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603             if(board[BOARD_HEIGHT-1][k] == bKing)
3604                 initialRights[5] = castlingRights[moveNum][5] = k;
3605     } else { int r;
3606         r = castlingRights[moveNum][0] = initialRights[0];
3607         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3608         r = castlingRights[moveNum][1] = initialRights[1];
3609         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3610         r = castlingRights[moveNum][3] = initialRights[3];
3611         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3612         r = castlingRights[moveNum][4] = initialRights[4];
3613         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3614         /* wildcastle kludge: always assume King has rights */
3615         r = castlingRights[moveNum][2] = initialRights[2];
3616         r = castlingRights[moveNum][5] = initialRights[5];
3617     }
3618     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3619     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3620
3621     
3622     if (ics_getting_history == H_GOT_REQ_HEADER ||
3623         ics_getting_history == H_GOT_UNREQ_HEADER) {
3624         /* This was an initial position from a move list, not
3625            the current position */
3626         return;
3627     }
3628     
3629     /* Update currentMove and known move number limits */
3630     newMove = newGame || moveNum > forwardMostMove;
3631
3632     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3633     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3634         takeback = forwardMostMove - moveNum;
3635         for (i = 0; i < takeback; i++) {
3636              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3637              SendToProgram("undo\n", &first);
3638         }
3639     }
3640
3641     if (newGame) {
3642         forwardMostMove = backwardMostMove = currentMove = moveNum;
3643         if (gameMode == IcsExamining && moveNum == 0) {
3644           /* Workaround for ICS limitation: we are not told the wild
3645              type when starting to examine a game.  But if we ask for
3646              the move list, the move list header will tell us */
3647             ics_getting_history = H_REQUESTED;
3648             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3649             SendToICS(str);
3650         }
3651     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3652                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3653         forwardMostMove = moveNum;
3654         if (!pausing || currentMove > forwardMostMove)
3655           currentMove = forwardMostMove;
3656     } else {
3657         /* New part of history that is not contiguous with old part */ 
3658         if (pausing && gameMode == IcsExamining) {
3659             pauseExamInvalid = TRUE;
3660             forwardMostMove = pauseExamForwardMostMove;
3661             return;
3662         }
3663         forwardMostMove = backwardMostMove = currentMove = moveNum;
3664         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3665             ics_getting_history = H_REQUESTED;
3666             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3667             SendToICS(str);
3668         }
3669     }
3670     
3671     /* Update the clocks */
3672     if (strchr(elapsed_time, '.')) {
3673       /* Time is in ms */
3674       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3675       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3676     } else {
3677       /* Time is in seconds */
3678       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3679       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3680     }
3681       
3682
3683 #if ZIPPY
3684     if (appData.zippyPlay && newGame &&
3685         gameMode != IcsObserving && gameMode != IcsIdle &&
3686         gameMode != IcsExamining)
3687       ZippyFirstBoard(moveNum, basetime, increment);
3688 #endif
3689     
3690     /* Put the move on the move list, first converting
3691        to canonical algebraic form. */
3692     if (moveNum > 0) {
3693   if (appData.debugMode) {
3694     if (appData.debugMode) { int f = forwardMostMove;
3695         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3696                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3697     }
3698     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3699     fprintf(debugFP, "moveNum = %d\n", moveNum);
3700     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3701     setbuf(debugFP, NULL);
3702   }
3703         if (moveNum <= backwardMostMove) {
3704             /* We don't know what the board looked like before
3705                this move.  Punt. */
3706             strcpy(parseList[moveNum - 1], move_str);
3707             strcat(parseList[moveNum - 1], " ");
3708             strcat(parseList[moveNum - 1], elapsed_time);
3709             moveList[moveNum - 1][0] = NULLCHAR;
3710         } else if (strcmp(move_str, "none") == 0) {
3711             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3712             /* Again, we don't know what the board looked like;
3713                this is really the start of the game. */
3714             parseList[moveNum - 1][0] = NULLCHAR;
3715             moveList[moveNum - 1][0] = NULLCHAR;
3716             backwardMostMove = moveNum;
3717             startedFromSetupPosition = TRUE;
3718             fromX = fromY = toX = toY = -1;
3719         } else {
3720           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3721           //                 So we parse the long-algebraic move string in stead of the SAN move
3722           int valid; char buf[MSG_SIZ], *prom;
3723
3724           // str looks something like "Q/a1-a2"; kill the slash
3725           if(str[1] == '/') 
3726                 sprintf(buf, "%c%s", str[0], str+2);
3727           else  strcpy(buf, str); // might be castling
3728           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3729                 strcat(buf, prom); // long move lacks promo specification!
3730           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3731                 if(appData.debugMode) 
3732                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3733                 strcpy(move_str, buf);
3734           }
3735           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3736                                 &fromX, &fromY, &toX, &toY, &promoChar)
3737                || ParseOneMove(buf, moveNum - 1, &moveType,
3738                                 &fromX, &fromY, &toX, &toY, &promoChar);
3739           // end of long SAN patch
3740           if (valid) {
3741             (void) CoordsToAlgebraic(boards[moveNum - 1],
3742                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3743                                      fromY, fromX, toY, toX, promoChar,
3744                                      parseList[moveNum-1]);
3745             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3746                              castlingRights[moveNum]) ) {
3747               case MT_NONE:
3748               case MT_STALEMATE:
3749               default:
3750                 break;
3751               case MT_CHECK:
3752                 if(gameInfo.variant != VariantShogi)
3753                     strcat(parseList[moveNum - 1], "+");
3754                 break;
3755               case MT_CHECKMATE:
3756               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3757                 strcat(parseList[moveNum - 1], "#");
3758                 break;
3759             }
3760             strcat(parseList[moveNum - 1], " ");
3761             strcat(parseList[moveNum - 1], elapsed_time);
3762             /* currentMoveString is set as a side-effect of ParseOneMove */
3763             strcpy(moveList[moveNum - 1], currentMoveString);
3764             strcat(moveList[moveNum - 1], "\n");
3765           } else {
3766             /* Move from ICS was illegal!?  Punt. */
3767   if (appData.debugMode) {
3768     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3769     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3770   }
3771 #if 0
3772             if (appData.testLegality && appData.debugMode) {
3773                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3774                 DisplayError(str, 0);
3775             }
3776 #endif
3777             strcpy(parseList[moveNum - 1], move_str);
3778             strcat(parseList[moveNum - 1], " ");
3779             strcat(parseList[moveNum - 1], elapsed_time);
3780             moveList[moveNum - 1][0] = NULLCHAR;
3781             fromX = fromY = toX = toY = -1;
3782           }
3783         }
3784   if (appData.debugMode) {
3785     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3786     setbuf(debugFP, NULL);
3787   }
3788
3789 #if ZIPPY
3790         /* Send move to chess program (BEFORE animating it). */
3791         if (appData.zippyPlay && !newGame && newMove && 
3792            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3793
3794             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3795                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3796                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3797                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3798                             move_str);
3799                     DisplayError(str, 0);
3800                 } else {
3801                     if (first.sendTime) {
3802                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3803                     }
3804                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3805                     if (firstMove && !bookHit) {
3806                         firstMove = FALSE;
3807                         if (first.useColors) {
3808                           SendToProgram(gameMode == IcsPlayingWhite ?
3809                                         "white\ngo\n" :
3810                                         "black\ngo\n", &first);
3811                         } else {
3812                           SendToProgram("go\n", &first);
3813                         }
3814                         first.maybeThinking = TRUE;
3815                     }
3816                 }
3817             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3818               if (moveList[moveNum - 1][0] == NULLCHAR) {
3819                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3820                 DisplayError(str, 0);
3821               } else {
3822                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3823                 SendMoveToProgram(moveNum - 1, &first);
3824               }
3825             }
3826         }
3827 #endif
3828     }
3829
3830     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3831         /* If move comes from a remote source, animate it.  If it
3832            isn't remote, it will have already been animated. */
3833         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3834             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3835         }
3836         if (!pausing && appData.highlightLastMove) {
3837             SetHighlights(fromX, fromY, toX, toY);
3838         }
3839     }
3840     
3841     /* Start the clocks */
3842     whiteFlag = blackFlag = FALSE;
3843     appData.clockMode = !(basetime == 0 && increment == 0);
3844     if (ticking == 0) {
3845       ics_clock_paused = TRUE;
3846       StopClocks();
3847     } else if (ticking == 1) {
3848       ics_clock_paused = FALSE;
3849     }
3850     if (gameMode == IcsIdle ||
3851         relation == RELATION_OBSERVING_STATIC ||
3852         relation == RELATION_EXAMINING ||
3853         ics_clock_paused)
3854       DisplayBothClocks();
3855     else
3856       StartClocks();
3857     
3858     /* Display opponents and material strengths */
3859     if (gameInfo.variant != VariantBughouse &&
3860         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3861         if (tinyLayout || smallLayout) {
3862             if(gameInfo.variant == VariantNormal)
3863                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3865                     basetime, increment);
3866             else
3867                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3868                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3869                     basetime, increment, (int) gameInfo.variant);
3870         } else {
3871             if(gameInfo.variant == VariantNormal)
3872                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3873                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3874                     basetime, increment);
3875             else
3876                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3877                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3878                     basetime, increment, VariantName(gameInfo.variant));
3879         }
3880         DisplayTitle(str);
3881   if (appData.debugMode) {
3882     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3883   }
3884     }
3885
3886    
3887     /* Display the board */
3888     if (!pausing && !appData.noGUI) {
3889       
3890       if (appData.premove)
3891           if (!gotPremove || 
3892              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3893              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3894               ClearPremoveHighlights();
3895
3896       DrawPosition(FALSE, boards[currentMove]);
3897       DisplayMove(moveNum - 1);
3898       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3899             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3900               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3901         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3902       }
3903     }
3904
3905     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3906 #if ZIPPY
3907     if(bookHit) { // [HGM] book: simulate book reply
3908         static char bookMove[MSG_SIZ]; // a bit generous?
3909
3910         programStats.nodes = programStats.depth = programStats.time = 
3911         programStats.score = programStats.got_only_move = 0;
3912         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3913
3914         strcpy(bookMove, "move ");
3915         strcat(bookMove, bookHit);
3916         HandleMachineMove(bookMove, &first);
3917     }
3918 #endif
3919 }
3920
3921 void
3922 GetMoveListEvent()
3923 {
3924     char buf[MSG_SIZ];
3925     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3926         ics_getting_history = H_REQUESTED;
3927         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3928         SendToICS(buf);
3929     }
3930 }
3931
3932 void
3933 AnalysisPeriodicEvent(force)
3934      int force;
3935 {
3936     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3937          && !force) || !appData.periodicUpdates)
3938       return;
3939
3940     /* Send . command to Crafty to collect stats */
3941     SendToProgram(".\n", &first);
3942
3943     /* Don't send another until we get a response (this makes
3944        us stop sending to old Crafty's which don't understand
3945        the "." command (sending illegal cmds resets node count & time,
3946        which looks bad)) */
3947     programStats.ok_to_send = 0;
3948 }
3949
3950 void
3951 SendMoveToProgram(moveNum, cps)
3952      int moveNum;
3953      ChessProgramState *cps;
3954 {
3955     char buf[MSG_SIZ];
3956
3957     if (cps->useUsermove) {
3958       SendToProgram("usermove ", cps);
3959     }
3960     if (cps->useSAN) {
3961       char *space;
3962       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3963         int len = space - parseList[moveNum];
3964         memcpy(buf, parseList[moveNum], len);
3965         buf[len++] = '\n';
3966         buf[len] = NULLCHAR;
3967       } else {
3968         sprintf(buf, "%s\n", parseList[moveNum]);
3969       }
3970       SendToProgram(buf, cps);
3971     } else {
3972       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3973         AlphaRank(moveList[moveNum], 4);
3974         SendToProgram(moveList[moveNum], cps);
3975         AlphaRank(moveList[moveNum], 4); // and back
3976       } else
3977       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3978        * the engine. It would be nice to have a better way to identify castle 
3979        * moves here. */
3980       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3981                                                                          && cps->useOOCastle) {
3982         int fromX = moveList[moveNum][0] - AAA; 
3983         int fromY = moveList[moveNum][1] - ONE;
3984         int toX = moveList[moveNum][2] - AAA; 
3985         int toY = moveList[moveNum][3] - ONE;
3986         if((boards[moveNum][fromY][fromX] == WhiteKing 
3987             && boards[moveNum][toY][toX] == WhiteRook)
3988            || (boards[moveNum][fromY][fromX] == BlackKing 
3989                && boards[moveNum][toY][toX] == BlackRook)) {
3990           if(toX > fromX) SendToProgram("O-O\n", cps);
3991           else SendToProgram("O-O-O\n", cps);
3992         }
3993         else SendToProgram(moveList[moveNum], cps);
3994       }
3995       else SendToProgram(moveList[moveNum], cps);
3996       /* End of additions by Tord */
3997     }
3998
3999     /* [HGM] setting up the opening has brought engine in force mode! */
4000     /*       Send 'go' if we are in a mode where machine should play. */
4001     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4002         (gameMode == TwoMachinesPlay   ||
4003 #ifdef ZIPPY
4004          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4005 #endif
4006          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4007         SendToProgram("go\n", cps);
4008   if (appData.debugMode) {
4009     fprintf(debugFP, "(extra)\n");
4010   }
4011     }
4012     setboardSpoiledMachineBlack = 0;
4013 }
4014
4015 void
4016 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4017      ChessMove moveType;
4018      int fromX, fromY, toX, toY;
4019 {
4020     char user_move[MSG_SIZ];
4021
4022     switch (moveType) {
4023       default:
4024         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4025                 (int)moveType, fromX, fromY, toX, toY);
4026         DisplayError(user_move + strlen("say "), 0);
4027         break;
4028       case WhiteKingSideCastle:
4029       case BlackKingSideCastle:
4030       case WhiteQueenSideCastleWild:
4031       case BlackQueenSideCastleWild:
4032       /* PUSH Fabien */
4033       case WhiteHSideCastleFR:
4034       case BlackHSideCastleFR:
4035       /* POP Fabien */
4036         sprintf(user_move, "o-o\n");
4037         break;
4038       case WhiteQueenSideCastle:
4039       case BlackQueenSideCastle:
4040       case WhiteKingSideCastleWild:
4041       case BlackKingSideCastleWild:
4042       /* PUSH Fabien */
4043       case WhiteASideCastleFR:
4044       case BlackASideCastleFR:
4045       /* POP Fabien */
4046         sprintf(user_move, "o-o-o\n");
4047         break;
4048       case WhitePromotionQueen:
4049       case BlackPromotionQueen:
4050       case WhitePromotionRook:
4051       case BlackPromotionRook:
4052       case WhitePromotionBishop:
4053       case BlackPromotionBishop:
4054       case WhitePromotionKnight:
4055       case BlackPromotionKnight:
4056       case WhitePromotionKing:
4057       case BlackPromotionKing:
4058       case WhitePromotionChancellor:
4059       case BlackPromotionChancellor:
4060       case WhitePromotionArchbishop:
4061       case BlackPromotionArchbishop:
4062         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4063             sprintf(user_move, "%c%c%c%c=%c\n",
4064                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4065                 PieceToChar(WhiteFerz));
4066         else if(gameInfo.variant == VariantGreat)
4067             sprintf(user_move, "%c%c%c%c=%c\n",
4068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4069                 PieceToChar(WhiteMan));
4070         else
4071             sprintf(user_move, "%c%c%c%c=%c\n",
4072                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4073                 PieceToChar(PromoPiece(moveType)));
4074         break;
4075       case WhiteDrop:
4076       case BlackDrop:
4077         sprintf(user_move, "%c@%c%c\n",
4078                 ToUpper(PieceToChar((ChessSquare) fromX)),
4079                 AAA + toX, ONE + toY);
4080         break;
4081       case NormalMove:
4082       case WhiteCapturesEnPassant:
4083       case BlackCapturesEnPassant:
4084       case IllegalMove:  /* could be a variant we don't quite understand */
4085         sprintf(user_move, "%c%c%c%c\n",
4086                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4087         break;
4088     }
4089     SendToICS(user_move);
4090 }
4091
4092 void
4093 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4094      int rf, ff, rt, ft;
4095      char promoChar;
4096      char move[7];
4097 {
4098     if (rf == DROP_RANK) {
4099         sprintf(move, "%c@%c%c\n",
4100                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4101     } else {
4102         if (promoChar == 'x' || promoChar == NULLCHAR) {
4103             sprintf(move, "%c%c%c%c\n",
4104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4105         } else {
4106             sprintf(move, "%c%c%c%c%c\n",
4107                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4108         }
4109     }
4110 }
4111
4112 void
4113 ProcessICSInitScript(f)
4114      FILE *f;
4115 {
4116     char buf[MSG_SIZ];
4117
4118     while (fgets(buf, MSG_SIZ, f)) {
4119         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4120     }
4121
4122     fclose(f);
4123 }
4124
4125
4126 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4127 void
4128 AlphaRank(char *move, int n)
4129 {
4130 //    char *p = move, c; int x, y;
4131
4132     if (appData.debugMode) {
4133         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4134     }
4135
4136     if(move[1]=='*' && 
4137        move[2]>='0' && move[2]<='9' &&
4138        move[3]>='a' && move[3]<='x'    ) {
4139         move[1] = '@';
4140         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4141         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4142     } else
4143     if(move[0]>='0' && move[0]<='9' &&
4144        move[1]>='a' && move[1]<='x' &&
4145        move[2]>='0' && move[2]<='9' &&
4146        move[3]>='a' && move[3]<='x'    ) {
4147         /* input move, Shogi -> normal */
4148         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4149         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4150         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4151         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4152     } else
4153     if(move[1]=='@' &&
4154        move[3]>='0' && move[3]<='9' &&
4155        move[2]>='a' && move[2]<='x'    ) {
4156         move[1] = '*';
4157         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4158         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4159     } else
4160     if(
4161        move[0]>='a' && move[0]<='x' &&
4162        move[3]>='0' && move[3]<='9' &&
4163        move[2]>='a' && move[2]<='x'    ) {
4164          /* output move, normal -> Shogi */
4165         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4166         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4167         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4168         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4169         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4170     }
4171     if (appData.debugMode) {
4172         fprintf(debugFP, "   out = '%s'\n", move);
4173     }
4174 }
4175
4176 /* Parser for moves from gnuchess, ICS, or user typein box */
4177 Boolean
4178 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4179      char *move;
4180      int moveNum;
4181      ChessMove *moveType;
4182      int *fromX, *fromY, *toX, *toY;
4183      char *promoChar;
4184 {       
4185     if (appData.debugMode) {
4186         fprintf(debugFP, "move to parse: %s\n", move);
4187     }
4188     *moveType = yylexstr(moveNum, move);
4189
4190     switch (*moveType) {
4191       case WhitePromotionChancellor:
4192       case BlackPromotionChancellor:
4193       case WhitePromotionArchbishop:
4194       case BlackPromotionArchbishop:
4195       case WhitePromotionQueen:
4196       case BlackPromotionQueen:
4197       case WhitePromotionRook:
4198       case BlackPromotionRook:
4199       case WhitePromotionBishop:
4200       case BlackPromotionBishop:
4201       case WhitePromotionKnight:
4202       case BlackPromotionKnight:
4203       case WhitePromotionKing:
4204       case BlackPromotionKing:
4205       case NormalMove:
4206       case WhiteCapturesEnPassant:
4207       case BlackCapturesEnPassant:
4208       case WhiteKingSideCastle:
4209       case WhiteQueenSideCastle:
4210       case BlackKingSideCastle:
4211       case BlackQueenSideCastle:
4212       case WhiteKingSideCastleWild:
4213       case WhiteQueenSideCastleWild:
4214       case BlackKingSideCastleWild:
4215       case BlackQueenSideCastleWild:
4216       /* Code added by Tord: */
4217       case WhiteHSideCastleFR:
4218       case WhiteASideCastleFR:
4219       case BlackHSideCastleFR:
4220       case BlackASideCastleFR:
4221       /* End of code added by Tord */
4222       case IllegalMove:         /* bug or odd chess variant */
4223         *fromX = currentMoveString[0] - AAA;
4224         *fromY = currentMoveString[1] - ONE;
4225         *toX = currentMoveString[2] - AAA;
4226         *toY = currentMoveString[3] - ONE;
4227         *promoChar = currentMoveString[4];
4228         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4229             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4230     if (appData.debugMode) {
4231         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4232     }
4233             *fromX = *fromY = *toX = *toY = 0;
4234             return FALSE;
4235         }
4236         if (appData.testLegality) {
4237           return (*moveType != IllegalMove);
4238         } else {
4239           return !(fromX == fromY && toX == toY);
4240         }
4241
4242       case WhiteDrop:
4243       case BlackDrop:
4244         *fromX = *moveType == WhiteDrop ?
4245           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4246           (int) CharToPiece(ToLower(currentMoveString[0]));
4247         *fromY = DROP_RANK;
4248         *toX = currentMoveString[2] - AAA;
4249         *toY = currentMoveString[3] - ONE;
4250         *promoChar = NULLCHAR;
4251         return TRUE;
4252
4253       case AmbiguousMove:
4254       case ImpossibleMove:
4255       case (ChessMove) 0:       /* end of file */
4256       case ElapsedTime:
4257       case Comment:
4258       case PGNTag:
4259       case NAG:
4260       case WhiteWins:
4261       case BlackWins:
4262       case GameIsDrawn:
4263       default:
4264     if (appData.debugMode) {
4265         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4266     }
4267         /* bug? */
4268         *fromX = *fromY = *toX = *toY = 0;
4269         *promoChar = NULLCHAR;
4270         return FALSE;
4271     }
4272 }
4273
4274 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4275 // All positions will have equal probability, but the current method will not provide a unique
4276 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4277 #define DARK 1
4278 #define LITE 2
4279 #define ANY 3
4280
4281 int squaresLeft[4];
4282 int piecesLeft[(int)BlackPawn];
4283 int seed, nrOfShuffles;
4284
4285 void GetPositionNumber()
4286 {       // sets global variable seed
4287         int i;
4288
4289         seed = appData.defaultFrcPosition;
4290         if(seed < 0) { // randomize based on time for negative FRC position numbers
4291                 for(i=0; i<50; i++) seed += random();
4292                 seed = random() ^ random() >> 8 ^ random() << 8;
4293                 if(seed<0) seed = -seed;
4294         }
4295 }
4296
4297 int put(Board board, int pieceType, int rank, int n, int shade)
4298 // put the piece on the (n-1)-th empty squares of the given shade
4299 {
4300         int i;
4301
4302         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4303                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4304                         board[rank][i] = (ChessSquare) pieceType;
4305                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4306                         squaresLeft[ANY]--;
4307                         piecesLeft[pieceType]--; 
4308                         return i;
4309                 }
4310         }
4311         return -1;
4312 }
4313
4314
4315 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4316 // calculate where the next piece goes, (any empty square), and put it there
4317 {
4318         int i;
4319
4320         i = seed % squaresLeft[shade];
4321         nrOfShuffles *= squaresLeft[shade];
4322         seed /= squaresLeft[shade];
4323         put(board, pieceType, rank, i, shade);
4324 }
4325
4326 void AddTwoPieces(Board board, int pieceType, int rank)
4327 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4328 {
4329         int i, n=squaresLeft[ANY], j=n-1, k;
4330
4331         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4332         i = seed % k;  // pick one
4333         nrOfShuffles *= k;
4334         seed /= k;
4335         while(i >= j) i -= j--;
4336         j = n - 1 - j; i += j;
4337         put(board, pieceType, rank, j, ANY);
4338         put(board, pieceType, rank, i, ANY);
4339 }
4340
4341 void SetUpShuffle(Board board, int number)
4342 {
4343         int i, p, first=1;
4344
4345         GetPositionNumber(); nrOfShuffles = 1;
4346
4347         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4348         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4349         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4350
4351         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4352
4353         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4354             p = (int) board[0][i];
4355             if(p < (int) BlackPawn) piecesLeft[p] ++;
4356             board[0][i] = EmptySquare;
4357         }
4358
4359         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4360             // shuffles restricted to allow normal castling put KRR first
4361             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4362                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4363             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4364                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4365             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4366                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4367             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4368                 put(board, WhiteRook, 0, 0, ANY);
4369             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4370         }
4371
4372         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4373             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4374             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4375                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4376                 while(piecesLeft[p] >= 2) {
4377                     AddOnePiece(board, p, 0, LITE);
4378                     AddOnePiece(board, p, 0, DARK);
4379                 }
4380                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4381             }
4382
4383         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4384             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4385             // but we leave King and Rooks for last, to possibly obey FRC restriction
4386             if(p == (int)WhiteRook) continue;
4387             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4388             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4389         }
4390
4391         // now everything is placed, except perhaps King (Unicorn) and Rooks
4392
4393         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4394             // Last King gets castling rights
4395             while(piecesLeft[(int)WhiteUnicorn]) {
4396                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4397                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4398             }
4399
4400             while(piecesLeft[(int)WhiteKing]) {
4401                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4402                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4403             }
4404
4405
4406         } else {
4407             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4408             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4409         }
4410
4411         // Only Rooks can be left; simply place them all
4412         while(piecesLeft[(int)WhiteRook]) {
4413                 i = put(board, WhiteRook, 0, 0, ANY);
4414                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4415                         if(first) {
4416                                 first=0;
4417                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4418                         }
4419                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4420                 }
4421         }
4422         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4423             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4424         }
4425
4426         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4427 }
4428
4429 int SetCharTable( char *table, const char * map )
4430 /* [HGM] moved here from winboard.c because of its general usefulness */
4431 /*       Basically a safe strcpy that uses the last character as King */
4432 {
4433     int result = FALSE; int NrPieces;
4434
4435     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4436                     && NrPieces >= 12 && !(NrPieces&1)) {
4437         int i; /* [HGM] Accept even length from 12 to 34 */
4438
4439         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4440         for( i=0; i<NrPieces/2-1; i++ ) {
4441             table[i] = map[i];
4442             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4443         }
4444         table[(int) WhiteKing]  = map[NrPieces/2-1];
4445         table[(int) BlackKing]  = map[NrPieces-1];
4446
4447         result = TRUE;
4448     }
4449
4450     return result;
4451 }
4452
4453 void Prelude(Board board)
4454 {       // [HGM] superchess: random selection of exo-pieces
4455         int i, j, k; ChessSquare p; 
4456         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4457
4458         GetPositionNumber(); // use FRC position number
4459
4460         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4461             SetCharTable(pieceToChar, appData.pieceToCharTable);
4462             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4463                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4464         }
4465
4466         j = seed%4;                 seed /= 4; 
4467         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4468         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4469         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4470         j = seed%3 + (seed%3 >= j); seed /= 3; 
4471         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4472         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4473         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4474         j = seed%3;                 seed /= 3; 
4475         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4476         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4477         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4478         j = seed%2 + (seed%2 >= j); seed /= 2; 
4479         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4480         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4481         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4482         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4483         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4484         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4485         put(board, exoPieces[0],    0, 0, ANY);
4486         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4487 }
4488
4489 void
4490 InitPosition(redraw)
4491      int redraw;
4492 {
4493     ChessSquare (* pieces)[BOARD_SIZE];
4494     int i, j, pawnRow, overrule,
4495     oldx = gameInfo.boardWidth,
4496     oldy = gameInfo.boardHeight,
4497     oldh = gameInfo.holdingsWidth,
4498     oldv = gameInfo.variant;
4499
4500     currentMove = forwardMostMove = backwardMostMove = 0;
4501     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4502
4503     /* [AS] Initialize pv info list [HGM] and game status */
4504     {
4505         for( i=0; i<MAX_MOVES; i++ ) {
4506             pvInfoList[i].depth = 0;
4507             epStatus[i]=EP_NONE;
4508             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4509         }
4510
4511         initialRulePlies = 0; /* 50-move counter start */
4512
4513         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4514         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4515     }
4516
4517     
4518     /* [HGM] logic here is completely changed. In stead of full positions */
4519     /* the initialized data only consist of the two backranks. The switch */
4520     /* selects which one we will use, which is than copied to the Board   */
4521     /* initialPosition, which for the rest is initialized by Pawns and    */
4522     /* empty squares. This initial position is then copied to boards[0],  */
4523     /* possibly after shuffling, so that it remains available.            */
4524
4525     gameInfo.holdingsWidth = 0; /* default board sizes */
4526     gameInfo.boardWidth    = 8;
4527     gameInfo.boardHeight   = 8;
4528     gameInfo.holdingsSize  = 0;
4529     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4530     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4531     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4532
4533     switch (gameInfo.variant) {
4534     case VariantFischeRandom:
4535       shuffleOpenings = TRUE;
4536     default:
4537       pieces = FIDEArray;
4538       break;
4539     case VariantShatranj:
4540       pieces = ShatranjArray;
4541       nrCastlingRights = 0;
4542       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4543       break;
4544     case VariantTwoKings:
4545       pieces = twoKingsArray;
4546       break;
4547     case VariantCapaRandom:
4548       shuffleOpenings = TRUE;
4549     case VariantCapablanca:
4550       pieces = CapablancaArray;
4551       gameInfo.boardWidth = 10;
4552       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4553       break;
4554     case VariantGothic:
4555       pieces = GothicArray;
4556       gameInfo.boardWidth = 10;
4557       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4558       break;
4559     case VariantJanus:
4560       pieces = JanusArray;
4561       gameInfo.boardWidth = 10;
4562       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4563       nrCastlingRights = 6;
4564         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4565         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4566         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4567         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4568         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4569         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4570       break;
4571     case VariantFalcon:
4572       pieces = FalconArray;
4573       gameInfo.boardWidth = 10;
4574       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4575       break;
4576     case VariantXiangqi:
4577       pieces = XiangqiArray;
4578       gameInfo.boardWidth  = 9;
4579       gameInfo.boardHeight = 10;
4580       nrCastlingRights = 0;
4581       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4582       break;
4583     case VariantShogi:
4584       pieces = ShogiArray;
4585       gameInfo.boardWidth  = 9;
4586       gameInfo.boardHeight = 9;
4587       gameInfo.holdingsSize = 7;
4588       nrCastlingRights = 0;
4589       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4590       break;
4591     case VariantCourier:
4592       pieces = CourierArray;
4593       gameInfo.boardWidth  = 12;
4594       nrCastlingRights = 0;
4595       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4596       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4597       break;
4598     case VariantKnightmate:
4599       pieces = KnightmateArray;
4600       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4601       break;
4602     case VariantFairy:
4603       pieces = fairyArray;
4604       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4605       break;
4606     case VariantGreat:
4607       pieces = GreatArray;
4608       gameInfo.boardWidth = 10;
4609       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4610       gameInfo.holdingsSize = 8;
4611       break;
4612     case VariantSuper:
4613       pieces = FIDEArray;
4614       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4615       gameInfo.holdingsSize = 8;
4616       startedFromSetupPosition = TRUE;
4617       break;
4618     case VariantCrazyhouse:
4619     case VariantBughouse:
4620       pieces = FIDEArray;
4621       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4622       gameInfo.holdingsSize = 5;
4623       break;
4624     case VariantWildCastle:
4625       pieces = FIDEArray;
4626       /* !!?shuffle with kings guaranteed to be on d or e file */
4627       shuffleOpenings = 1;
4628       break;
4629     case VariantNoCastle:
4630       pieces = FIDEArray;
4631       nrCastlingRights = 0;
4632       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4633       /* !!?unconstrained back-rank shuffle */
4634       shuffleOpenings = 1;
4635       break;
4636     }
4637
4638     overrule = 0;
4639     if(appData.NrFiles >= 0) {
4640         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4641         gameInfo.boardWidth = appData.NrFiles;
4642     }
4643     if(appData.NrRanks >= 0) {
4644         gameInfo.boardHeight = appData.NrRanks;
4645     }
4646     if(appData.holdingsSize >= 0) {
4647         i = appData.holdingsSize;
4648         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4649         gameInfo.holdingsSize = i;
4650     }
4651     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4652     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4653         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4654
4655     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4656     if(pawnRow < 1) pawnRow = 1;
4657
4658     /* User pieceToChar list overrules defaults */
4659     if(appData.pieceToCharTable != NULL)
4660         SetCharTable(pieceToChar, appData.pieceToCharTable);
4661
4662     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4663
4664         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4665             s = (ChessSquare) 0; /* account holding counts in guard band */
4666         for( i=0; i<BOARD_HEIGHT; i++ )
4667             initialPosition[i][j] = s;
4668
4669         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4670         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4671         initialPosition[pawnRow][j] = WhitePawn;
4672         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4673         if(gameInfo.variant == VariantXiangqi) {
4674             if(j&1) {
4675                 initialPosition[pawnRow][j] = 
4676                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4677                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4678                    initialPosition[2][j] = WhiteCannon;
4679                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4680                 }
4681             }
4682         }
4683         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4684     }
4685     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4686
4687             j=BOARD_LEFT+1;
4688             initialPosition[1][j] = WhiteBishop;
4689             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4690             j=BOARD_RGHT-2;
4691             initialPosition[1][j] = WhiteRook;
4692             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4693     }
4694
4695     if( nrCastlingRights == -1) {
4696         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4697         /*       This sets default castling rights from none to normal corners   */
4698         /* Variants with other castling rights must set them themselves above    */
4699         nrCastlingRights = 6;
4700        
4701         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4702         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4703         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4704         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4705         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4706         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4707      }
4708
4709      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4710      if(gameInfo.variant == VariantGreat) { // promotion commoners
4711         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4712         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4713         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4714         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4715      }
4716 #if 0
4717     if(gameInfo.variant == VariantFischeRandom) {
4718       if( appData.defaultFrcPosition < 0 ) {
4719         ShuffleFRC( initialPosition );
4720       }
4721       else {
4722         SetupFRC( initialPosition, appData.defaultFrcPosition );
4723       }
4724       startedFromSetupPosition = TRUE;
4725     } else 
4726 #else
4727   if (appData.debugMode) {
4728     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4729   }
4730     if(shuffleOpenings) {
4731         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4732         startedFromSetupPosition = TRUE;
4733     }
4734 #endif
4735     if(startedFromPositionFile) {
4736       /* [HGM] loadPos: use PositionFile for every new game */
4737       CopyBoard(initialPosition, filePosition);
4738       for(i=0; i<nrCastlingRights; i++)
4739           castlingRights[0][i] = initialRights[i] = fileRights[i];
4740       startedFromSetupPosition = TRUE;
4741     }
4742
4743     CopyBoard(boards[0], initialPosition);
4744
4745     if(oldx != gameInfo.boardWidth ||
4746        oldy != gameInfo.boardHeight ||
4747        oldh != gameInfo.holdingsWidth
4748 #ifdef GOTHIC
4749        || oldv == VariantGothic ||        // For licensing popups
4750        gameInfo.variant == VariantGothic
4751 #endif
4752 #ifdef FALCON
4753        || oldv == VariantFalcon ||
4754        gameInfo.variant == VariantFalcon
4755 #endif
4756                                          )
4757             InitDrawingSizes(-2 ,0);
4758
4759     if (redraw)
4760       DrawPosition(TRUE, boards[currentMove]);
4761 }
4762
4763 void
4764 SendBoard(cps, moveNum)
4765      ChessProgramState *cps;
4766      int moveNum;
4767 {
4768     char message[MSG_SIZ];
4769     
4770     if (cps->useSetboard) {
4771       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4772       sprintf(message, "setboard %s\n", fen);
4773       SendToProgram(message, cps);
4774       free(fen);
4775
4776     } else {
4777       ChessSquare *bp;
4778       int i, j;
4779       /* Kludge to set black to move, avoiding the troublesome and now
4780        * deprecated "black" command.
4781        */
4782       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4783
4784       SendToProgram("edit\n", cps);
4785       SendToProgram("#\n", cps);
4786       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4787         bp = &boards[moveNum][i][BOARD_LEFT];
4788         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4789           if ((int) *bp < (int) BlackPawn) {
4790             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4791                     AAA + j, ONE + i);
4792             if(message[0] == '+' || message[0] == '~') {
4793                 sprintf(message, "%c%c%c+\n",
4794                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4795                         AAA + j, ONE + i);
4796             }
4797             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4798                 message[1] = BOARD_RGHT   - 1 - j + '1';
4799                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4800             }
4801             SendToProgram(message, cps);
4802           }
4803         }
4804       }
4805     
4806       SendToProgram("c\n", cps);
4807       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4808         bp = &boards[moveNum][i][BOARD_LEFT];
4809         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4810           if (((int) *bp != (int) EmptySquare)
4811               && ((int) *bp >= (int) BlackPawn)) {
4812             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4813                     AAA + j, ONE + i);
4814             if(message[0] == '+' || message[0] == '~') {
4815                 sprintf(message, "%c%c%c+\n",
4816                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4817                         AAA + j, ONE + i);
4818             }
4819             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4820                 message[1] = BOARD_RGHT   - 1 - j + '1';
4821                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4822             }
4823             SendToProgram(message, cps);
4824           }
4825         }
4826       }
4827     
4828       SendToProgram(".\n", cps);
4829     }
4830     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4831 }
4832
4833 int
4834 IsPromotion(fromX, fromY, toX, toY)
4835      int fromX, fromY, toX, toY;
4836 {
4837     /* [HGM] add Shogi promotions */
4838     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4839     ChessSquare piece;
4840
4841     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4842       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4843    /* [HGM] Note to self: line above also weeds out drops */
4844     piece = boards[currentMove][fromY][fromX];
4845     if(gameInfo.variant == VariantShogi) {
4846         promotionZoneSize = 3;
4847         highestPromotingPiece = (int)WhiteKing;
4848         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4849            and if in normal chess we then allow promotion to King, why not
4850            allow promotion of other piece in Shogi?                         */
4851     }
4852     if((int)piece >= BlackPawn) {
4853         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4854              return FALSE;
4855         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4856     } else {
4857         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4858            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4859     }
4860     return ( (int)piece <= highestPromotingPiece );
4861 }
4862
4863 int
4864 InPalace(row, column)
4865      int row, column;
4866 {   /* [HGM] for Xiangqi */
4867     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4868          column < (BOARD_WIDTH + 4)/2 &&
4869          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4870     return FALSE;
4871 }
4872
4873 int
4874 PieceForSquare (x, y)
4875      int x;
4876      int y;
4877 {
4878   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4879      return -1;
4880   else
4881      return boards[currentMove][y][x];
4882 }
4883
4884 int
4885 OKToStartUserMove(x, y)
4886      int x, y;
4887 {
4888     ChessSquare from_piece;
4889     int white_piece;
4890
4891     if (matchMode) return FALSE;
4892     if (gameMode == EditPosition) return TRUE;
4893
4894     if (x >= 0 && y >= 0)
4895       from_piece = boards[currentMove][y][x];
4896     else
4897       from_piece = EmptySquare;
4898
4899     if (from_piece == EmptySquare) return FALSE;
4900
4901     white_piece = (int)from_piece >= (int)WhitePawn &&
4902       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4903
4904     switch (gameMode) {
4905       case PlayFromGameFile:
4906       case AnalyzeFile:
4907       case TwoMachinesPlay:
4908       case EndOfGame:
4909         return FALSE;
4910
4911       case IcsObserving:
4912       case IcsIdle:
4913         return FALSE;
4914
4915       case MachinePlaysWhite:
4916       case IcsPlayingBlack:
4917         if (appData.zippyPlay) return FALSE;
4918         if (white_piece) {
4919             DisplayMoveError(_("You are playing Black"));
4920             return FALSE;
4921         }
4922         break;
4923
4924       case MachinePlaysBlack:
4925       case IcsPlayingWhite:
4926         if (appData.zippyPlay) return FALSE;
4927         if (!white_piece) {
4928             DisplayMoveError(_("You are playing White"));
4929             return FALSE;
4930         }
4931         break;
4932
4933       case EditGame:
4934         if (!white_piece && WhiteOnMove(currentMove)) {
4935             DisplayMoveError(_("It is White's turn"));
4936             return FALSE;
4937         }           
4938         if (white_piece && !WhiteOnMove(currentMove)) {
4939             DisplayMoveError(_("It is Black's turn"));
4940             return FALSE;
4941         }           
4942         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4943             /* Editing correspondence game history */
4944             /* Could disallow this or prompt for confirmation */
4945             cmailOldMove = -1;
4946         }
4947         if (currentMove < forwardMostMove) {
4948             /* Discarding moves */
4949             /* Could prompt for confirmation here,
4950                but I don't think that's such a good idea */
4951             forwardMostMove = currentMove;
4952         }
4953         break;
4954
4955       case BeginningOfGame:
4956         if (appData.icsActive) return FALSE;
4957         if (!appData.noChessProgram) {
4958             if (!white_piece) {
4959                 DisplayMoveError(_("You are playing White"));
4960                 return FALSE;
4961             }
4962         }
4963         break;
4964         
4965       case Training:
4966         if (!white_piece && WhiteOnMove(currentMove)) {
4967             DisplayMoveError(_("It is White's turn"));
4968             return FALSE;
4969         }           
4970         if (white_piece && !WhiteOnMove(currentMove)) {
4971             DisplayMoveError(_("It is Black's turn"));
4972             return FALSE;
4973         }           
4974         break;
4975
4976       default:
4977       case IcsExamining:
4978         break;
4979     }
4980     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4981         && gameMode != AnalyzeFile && gameMode != Training) {
4982         DisplayMoveError(_("Displayed position is not current"));
4983         return FALSE;
4984     }
4985     return TRUE;
4986 }
4987
4988 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4989 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4990 int lastLoadGameUseList = FALSE;
4991 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4992 ChessMove lastLoadGameStart = (ChessMove) 0;
4993
4994
4995 ChessMove
4996 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4997      int fromX, fromY, toX, toY;
4998      int promoChar;
4999 {
5000     ChessMove moveType;
5001     ChessSquare pdown, pup;
5002
5003     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5004     if ((fromX == toX) && (fromY == toY)) {
5005         return ImpossibleMove;
5006     }
5007
5008     /* [HGM] suppress all moves into holdings area and guard band */
5009     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5010             return ImpossibleMove;
5011
5012     /* [HGM] <sameColor> moved to here from winboard.c */
5013     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5014     pdown = boards[currentMove][fromY][fromX];
5015     pup = boards[currentMove][toY][toX];
5016     if (    gameMode != EditPosition &&
5017             (WhitePawn <= pdown && pdown < BlackPawn &&
5018              WhitePawn <= pup && pup < BlackPawn  ||
5019              BlackPawn <= pdown && pdown < EmptySquare &&
5020              BlackPawn <= pup && pup < EmptySquare 
5021             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5022                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5023                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5024         )           )
5025          return ImpossibleMove;
5026
5027     /* Check if the user is playing in turn.  This is complicated because we
5028        let the user "pick up" a piece before it is his turn.  So the piece he
5029        tried to pick up may have been captured by the time he puts it down!
5030        Therefore we use the color the user is supposed to be playing in this
5031        test, not the color of the piece that is currently on the starting
5032        square---except in EditGame mode, where the user is playing both
5033        sides; fortunately there the capture race can't happen.  (It can
5034        now happen in IcsExamining mode, but that's just too bad.  The user
5035        will get a somewhat confusing message in that case.)
5036        */
5037
5038     switch (gameMode) {
5039       case PlayFromGameFile:
5040       case AnalyzeFile:
5041       case TwoMachinesPlay:
5042       case EndOfGame:
5043       case IcsObserving:
5044       case IcsIdle:
5045         /* We switched into a game mode where moves are not accepted,
5046            perhaps while the mouse button was down. */
5047         return ImpossibleMove;
5048
5049       case MachinePlaysWhite:
5050         /* User is moving for Black */
5051         if (WhiteOnMove(currentMove)) {
5052             DisplayMoveError(_("It is White's turn"));
5053             return ImpossibleMove;
5054         }
5055         break;
5056
5057       case MachinePlaysBlack:
5058         /* User is moving for White */
5059         if (!WhiteOnMove(currentMove)) {
5060             DisplayMoveError(_("It is Black's turn"));
5061             return ImpossibleMove;
5062         }
5063         break;
5064
5065       case EditGame:
5066       case IcsExamining:
5067       case BeginningOfGame:
5068       case AnalyzeMode:
5069       case Training:
5070         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5071             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5072             /* User is moving for Black */
5073             if (WhiteOnMove(currentMove)) {
5074                 DisplayMoveError(_("It is White's turn"));
5075                 return ImpossibleMove;
5076             }
5077         } else {
5078             /* User is moving for White */
5079             if (!WhiteOnMove(currentMove)) {
5080                 DisplayMoveError(_("It is Black's turn"));
5081                 return ImpossibleMove;
5082             }
5083         }
5084         break;
5085
5086       case IcsPlayingBlack:
5087         /* User is moving for Black */
5088         if (WhiteOnMove(currentMove)) {
5089             if (!appData.premove) {
5090                 DisplayMoveError(_("It is White's turn"));
5091             } else if (toX >= 0 && toY >= 0) {
5092                 premoveToX = toX;
5093                 premoveToY = toY;
5094                 premoveFromX = fromX;
5095                 premoveFromY = fromY;
5096                 premovePromoChar = promoChar;
5097                 gotPremove = 1;
5098                 if (appData.debugMode) 
5099                     fprintf(debugFP, "Got premove: fromX %d,"
5100                             "fromY %d, toX %d, toY %d\n",
5101                             fromX, fromY, toX, toY);
5102             }
5103             return ImpossibleMove;
5104         }
5105         break;
5106
5107       case IcsPlayingWhite:
5108         /* User is moving for White */
5109         if (!WhiteOnMove(currentMove)) {
5110             if (!appData.premove) {
5111                 DisplayMoveError(_("It is Black's turn"));
5112             } else if (toX >= 0 && toY >= 0) {
5113                 premoveToX = toX;
5114                 premoveToY = toY;
5115                 premoveFromX = fromX;
5116                 premoveFromY = fromY;
5117                 premovePromoChar = promoChar;
5118                 gotPremove = 1;
5119                 if (appData.debugMode) 
5120                     fprintf(debugFP, "Got premove: fromX %d,"
5121                             "fromY %d, toX %d, toY %d\n",
5122                             fromX, fromY, toX, toY);
5123             }
5124             return ImpossibleMove;
5125         }
5126         break;
5127
5128       default:
5129         break;
5130
5131       case EditPosition:
5132         /* EditPosition, empty square, or different color piece;
5133            click-click move is possible */
5134         if (toX == -2 || toY == -2) {
5135             boards[0][fromY][fromX] = EmptySquare;
5136             return AmbiguousMove;
5137         } else if (toX >= 0 && toY >= 0) {
5138             boards[0][toY][toX] = boards[0][fromY][fromX];
5139             boards[0][fromY][fromX] = EmptySquare;
5140             return AmbiguousMove;
5141         }
5142         return ImpossibleMove;
5143     }
5144
5145     /* [HGM] If move started in holdings, it means a drop */
5146     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5147          if( pup != EmptySquare ) return ImpossibleMove;
5148          if(appData.testLegality) {
5149              /* it would be more logical if LegalityTest() also figured out
5150               * which drops are legal. For now we forbid pawns on back rank.
5151               * Shogi is on its own here...
5152               */
5153              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5154                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5155                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5156          }
5157          return WhiteDrop; /* Not needed to specify white or black yet */
5158     }
5159
5160     userOfferedDraw = FALSE;
5161         
5162     /* [HGM] always test for legality, to get promotion info */
5163     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5164                           epStatus[currentMove], castlingRights[currentMove],
5165                                          fromY, fromX, toY, toX, promoChar);
5166
5167     /* [HGM] but possibly ignore an IllegalMove result */
5168     if (appData.testLegality) {
5169         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5170             DisplayMoveError(_("Illegal move"));
5171             return ImpossibleMove;
5172         }
5173     }
5174 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5175     return moveType;
5176     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5177        function is made into one that returns an OK move type if FinishMove
5178        should be called. This to give the calling driver routine the
5179        opportunity to finish the userMove input with a promotion popup,
5180        without bothering the user with this for invalid or illegal moves */
5181
5182 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5183 }
5184
5185 /* Common tail of UserMoveEvent and DropMenuEvent */
5186 int
5187 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5188      ChessMove moveType;
5189      int fromX, fromY, toX, toY;
5190      /*char*/int promoChar;
5191 {
5192     char *bookHit = 0;
5193 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5194     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5195         // [HGM] superchess: suppress promotions to non-available piece
5196         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5197         if(WhiteOnMove(currentMove)) {
5198             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5199         } else {
5200             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5201         }
5202     }
5203
5204     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5205        move type in caller when we know the move is a legal promotion */
5206     if(moveType == NormalMove && promoChar)
5207         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5208 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5209     /* [HGM] convert drag-and-drop piece drops to standard form */
5210     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5211          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5212            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5213                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5214 //         fromX = boards[currentMove][fromY][fromX];
5215            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5216            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5217            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5218            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5219          fromY = DROP_RANK;
5220     }
5221
5222     /* [HGM] <popupFix> The following if has been moved here from
5223        UserMoveEvent(). Because it seemed to belon here (why not allow
5224        piece drops in training games?), and because it can only be
5225        performed after it is known to what we promote. */
5226     if (gameMode == Training) {
5227       /* compare the move played on the board to the next move in the
5228        * game. If they match, display the move and the opponent's response. 
5229        * If they don't match, display an error message.
5230        */
5231       int saveAnimate;
5232       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5233       CopyBoard(testBoard, boards[currentMove]);
5234       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5235
5236       if (CompareBoards(testBoard, boards[currentMove+1])) {
5237         ForwardInner(currentMove+1);
5238
5239         /* Autoplay the opponent's response.
5240          * if appData.animate was TRUE when Training mode was entered,
5241          * the response will be animated.
5242          */
5243         saveAnimate = appData.animate;
5244         appData.animate = animateTraining;
5245         ForwardInner(currentMove+1);
5246         appData.animate = saveAnimate;
5247
5248         /* check for the end of the game */
5249         if (currentMove >= forwardMostMove) {
5250           gameMode = PlayFromGameFile;
5251           ModeHighlight();
5252           SetTrainingModeOff();
5253           DisplayInformation(_("End of game"));
5254         }
5255       } else {
5256         DisplayError(_("Incorrect move"), 0);
5257       }
5258       return 1;
5259     }
5260
5261   /* Ok, now we know that the move is good, so we can kill
5262      the previous line in Analysis Mode */
5263   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5264     forwardMostMove = currentMove;
5265   }
5266
5267   /* If we need the chess program but it's dead, restart it */
5268   ResurrectChessProgram();
5269
5270   /* A user move restarts a paused game*/
5271   if (pausing)
5272     PauseEvent();
5273
5274   thinkOutput[0] = NULLCHAR;
5275
5276   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5277
5278   if (gameMode == BeginningOfGame) {
5279     if (appData.noChessProgram) {
5280       gameMode = EditGame;
5281       SetGameInfo();
5282     } else {
5283       char buf[MSG_SIZ];
5284       gameMode = MachinePlaysBlack;
5285       StartClocks();
5286       SetGameInfo();
5287       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5288       DisplayTitle(buf);
5289       if (first.sendName) {
5290         sprintf(buf, "name %s\n", gameInfo.white);
5291         SendToProgram(buf, &first);
5292       }
5293       StartClocks();
5294     }
5295     ModeHighlight();
5296   }
5297 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5298   /* Relay move to ICS or chess engine */
5299   if (appData.icsActive) {
5300     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5301         gameMode == IcsExamining) {
5302       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5303       ics_user_moved = 1;
5304     }
5305   } else {
5306     if (first.sendTime && (gameMode == BeginningOfGame ||
5307                            gameMode == MachinePlaysWhite ||
5308                            gameMode == MachinePlaysBlack)) {
5309       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5310     }
5311     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5312          // [HGM] book: if program might be playing, let it use book
5313         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5314         first.maybeThinking = TRUE;
5315     } else SendMoveToProgram(forwardMostMove-1, &first);
5316     if (currentMove == cmailOldMove + 1) {
5317       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5318     }
5319   }
5320
5321   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5322
5323   switch (gameMode) {
5324   case EditGame:
5325     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5326                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5327     case MT_NONE:
5328     case MT_CHECK:
5329       break;
5330     case MT_CHECKMATE:
5331     case MT_STAINMATE:
5332       if (WhiteOnMove(currentMove)) {
5333         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5334       } else {
5335         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5336       }
5337       break;
5338     case MT_STALEMATE:
5339       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5340       break;
5341     }
5342     break;
5343     
5344   case MachinePlaysBlack:
5345   case MachinePlaysWhite:
5346     /* disable certain menu options while machine is thinking */
5347     SetMachineThinkingEnables();
5348     break;
5349
5350   default:
5351     break;
5352   }
5353
5354   if(bookHit) { // [HGM] book: simulate book reply
5355         static char bookMove[MSG_SIZ]; // a bit generous?
5356
5357         programStats.nodes = programStats.depth = programStats.time = 
5358         programStats.score = programStats.got_only_move = 0;
5359         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5360
5361         strcpy(bookMove, "move ");
5362         strcat(bookMove, bookHit);
5363         HandleMachineMove(bookMove, &first);
5364   }
5365   return 1;
5366 }
5367
5368 void
5369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5370      int fromX, fromY, toX, toY;
5371      int promoChar;
5372 {
5373     /* [HGM] This routine was added to allow calling of its two logical
5374        parts from other modules in the old way. Before, UserMoveEvent()
5375        automatically called FinishMove() if the move was OK, and returned
5376        otherwise. I separated the two, in order to make it possible to
5377        slip a promotion popup in between. But that it always needs two
5378        calls, to the first part, (now called UserMoveTest() ), and to
5379        FinishMove if the first part succeeded. Calls that do not need
5380        to do anything in between, can call this routine the old way. 
5381     */
5382     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5383 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5384     if(moveType != ImpossibleMove)
5385         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5386 }
5387
5388 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5389 {
5390 //    char * hint = lastHint;
5391     FrontEndProgramStats stats;
5392
5393     stats.which = cps == &first ? 0 : 1;
5394     stats.depth = cpstats->depth;
5395     stats.nodes = cpstats->nodes;
5396     stats.score = cpstats->score;
5397     stats.time = cpstats->time;
5398     stats.pv = cpstats->movelist;
5399     stats.hint = lastHint;
5400     stats.an_move_index = 0;
5401     stats.an_move_count = 0;
5402
5403     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5404         stats.hint = cpstats->move_name;
5405         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5406         stats.an_move_count = cpstats->nr_moves;
5407     }
5408
5409     SetProgramStats( &stats );
5410 }
5411
5412 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5413 {   // [HGM] book: this routine intercepts moves to simulate book replies
5414     char *bookHit = NULL;
5415
5416     //first determine if the incoming move brings opponent into his book
5417     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5418         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5419     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5420     if(bookHit != NULL && !cps->bookSuspend) {
5421         // make sure opponent is not going to reply after receiving move to book position
5422         SendToProgram("force\n", cps);
5423         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5424     }
5425     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5426     // now arrange restart after book miss
5427     if(bookHit) {
5428         // after a book hit we never send 'go', and the code after the call to this routine
5429         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5430         char buf[MSG_SIZ];
5431         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5432         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5433         SendToProgram(buf, cps);
5434         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5435     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5436         SendToProgram("go\n", cps);
5437         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5438     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5439         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5440             SendToProgram("go\n", cps); 
5441         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5442     }
5443     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5444 }
5445
5446 char *savedMessage;
5447 ChessProgramState *savedState;
5448 void DeferredBookMove(void)
5449 {
5450         if(savedState->lastPing != savedState->lastPong)
5451                     ScheduleDelayedEvent(DeferredBookMove, 10);
5452         else
5453         HandleMachineMove(savedMessage, savedState);
5454 }
5455
5456 void
5457 HandleMachineMove(message, cps)
5458      char *message;
5459      ChessProgramState *cps;
5460 {
5461     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5462     char realname[MSG_SIZ];
5463     int fromX, fromY, toX, toY;
5464     ChessMove moveType;
5465     char promoChar;
5466     char *p;
5467     int machineWhite;
5468     char *bookHit;
5469
5470 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5471     /*
5472      * Kludge to ignore BEL characters
5473      */
5474     while (*message == '\007') message++;
5475
5476     /*
5477      * [HGM] engine debug message: ignore lines starting with '#' character
5478      */
5479     if(cps->debug && *message == '#') return;
5480
5481     /*
5482      * Look for book output
5483      */
5484     if (cps == &first && bookRequested) {
5485         if (message[0] == '\t' || message[0] == ' ') {
5486             /* Part of the book output is here; append it */
5487             strcat(bookOutput, message);
5488             strcat(bookOutput, "  \n");
5489             return;
5490         } else if (bookOutput[0] != NULLCHAR) {
5491             /* All of book output has arrived; display it */
5492             char *p = bookOutput;
5493             while (*p != NULLCHAR) {
5494                 if (*p == '\t') *p = ' ';
5495                 p++;
5496             }
5497             DisplayInformation(bookOutput);
5498             bookRequested = FALSE;
5499             /* Fall through to parse the current output */
5500         }
5501     }
5502
5503     /*
5504      * Look for machine move.
5505      */
5506     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5507         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5508     {
5509         /* This method is only useful on engines that support ping */
5510         if (cps->lastPing != cps->lastPong) {
5511           if (gameMode == BeginningOfGame) {
5512             /* Extra move from before last new; ignore */
5513             if (appData.debugMode) {
5514                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5515             }
5516           } else {
5517             if (appData.debugMode) {
5518                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5519                         cps->which, gameMode);
5520             }
5521
5522             SendToProgram("undo\n", cps);
5523           }
5524           return;
5525         }
5526
5527         switch (gameMode) {
5528           case BeginningOfGame:
5529             /* Extra move from before last reset; ignore */
5530             if (appData.debugMode) {
5531                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5532             }
5533             return;
5534
5535           case EndOfGame:
5536           case IcsIdle:
5537           default:
5538             /* Extra move after we tried to stop.  The mode test is
5539                not a reliable way of detecting this problem, but it's
5540                the best we can do on engines that don't support ping.
5541             */
5542             if (appData.debugMode) {
5543                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5544                         cps->which, gameMode);
5545             }
5546             SendToProgram("undo\n", cps);
5547             return;
5548
5549           case MachinePlaysWhite:
5550           case IcsPlayingWhite:
5551             machineWhite = TRUE;
5552             break;
5553
5554           case MachinePlaysBlack:
5555           case IcsPlayingBlack:
5556             machineWhite = FALSE;
5557             break;
5558
5559           case TwoMachinesPlay:
5560             machineWhite = (cps->twoMachinesColor[0] == 'w');
5561             break;
5562         }
5563         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5564             if (appData.debugMode) {
5565                 fprintf(debugFP,
5566                         "Ignoring move out of turn by %s, gameMode %d"
5567                         ", forwardMost %d\n",
5568                         cps->which, gameMode, forwardMostMove);
5569             }
5570             return;
5571         }
5572
5573     if (appData.debugMode) { int f = forwardMostMove;
5574         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5575                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5576     }
5577         if(cps->alphaRank) AlphaRank(machineMove, 4);
5578         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5579                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5580             /* Machine move could not be parsed; ignore it. */
5581             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5582                     machineMove, cps->which);
5583             DisplayError(buf1, 0);
5584             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5585                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5586             if (gameMode == TwoMachinesPlay) {
5587               GameEnds(machineWhite ? BlackWins : WhiteWins,
5588                        buf1, GE_XBOARD);
5589             }
5590             return;
5591         }
5592
5593         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5594         /* So we have to redo legality test with true e.p. status here,  */
5595         /* to make sure an illegal e.p. capture does not slip through,   */
5596         /* to cause a forfeit on a justified illegal-move complaint      */
5597         /* of the opponent.                                              */
5598         if( gameMode==TwoMachinesPlay && appData.testLegality
5599             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5600                                                               ) {
5601            ChessMove moveType;
5602            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5603                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5604                              fromY, fromX, toY, toX, promoChar);
5605             if (appData.debugMode) {
5606                 int i;
5607                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5608                     castlingRights[forwardMostMove][i], castlingRank[i]);
5609                 fprintf(debugFP, "castling rights\n");
5610             }
5611             if(moveType == IllegalMove) {
5612                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5613                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5614                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5615                            buf1, GE_XBOARD);
5616                 return;
5617            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5618            /* [HGM] Kludge to handle engines that send FRC-style castling
5619               when they shouldn't (like TSCP-Gothic) */
5620            switch(moveType) {
5621              case WhiteASideCastleFR:
5622              case BlackASideCastleFR:
5623                toX+=2;
5624                currentMoveString[2]++;
5625                break;
5626              case WhiteHSideCastleFR:
5627              case BlackHSideCastleFR:
5628                toX--;
5629                currentMoveString[2]--;
5630                break;
5631              default: ; // nothing to do, but suppresses warning of pedantic compilers
5632            }
5633         }
5634         hintRequested = FALSE;
5635         lastHint[0] = NULLCHAR;
5636         bookRequested = FALSE;
5637         /* Program may be pondering now */
5638         cps->maybeThinking = TRUE;
5639         if (cps->sendTime == 2) cps->sendTime = 1;
5640         if (cps->offeredDraw) cps->offeredDraw--;
5641
5642 #if ZIPPY
5643         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5644             first.initDone) {
5645           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5646           ics_user_moved = 1;
5647           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5648                 char buf[3*MSG_SIZ];
5649
5650                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5651                         programStats.score / 100.,
5652                         programStats.depth,
5653                         programStats.time / 100.,
5654                         (unsigned int)programStats.nodes,
5655                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5656                         programStats.movelist);
5657                 SendToICS(buf);
5658           }
5659         }
5660 #endif
5661         /* currentMoveString is set as a side-effect of ParseOneMove */
5662         strcpy(machineMove, currentMoveString);
5663         strcat(machineMove, "\n");
5664         strcpy(moveList[forwardMostMove], machineMove);
5665
5666         /* [AS] Save move info and clear stats for next move */
5667         pvInfoList[ forwardMostMove ].score = programStats.score;
5668         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5669         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5670         ClearProgramStats();
5671         thinkOutput[0] = NULLCHAR;
5672         hiddenThinkOutputState = 0;
5673
5674         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5675
5676         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5677         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5678             int count = 0;
5679
5680             while( count < adjudicateLossPlies ) {
5681                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5682
5683                 if( count & 1 ) {
5684                     score = -score; /* Flip score for winning side */
5685                 }
5686
5687                 if( score > adjudicateLossThreshold ) {
5688                     break;
5689                 }
5690
5691                 count++;
5692             }
5693
5694             if( count >= adjudicateLossPlies ) {
5695                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5696
5697                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5698                     "Xboard adjudication", 
5699                     GE_XBOARD );
5700
5701                 return;
5702             }
5703         }
5704
5705         if( gameMode == TwoMachinesPlay ) {
5706           // [HGM] some adjudications useful with buggy engines
5707             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5708           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5709
5710
5711             if( appData.testLegality )
5712             {   /* [HGM] Some more adjudications for obstinate engines */
5713                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5714                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5715                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5716                 static int moveCount = 6;
5717                 ChessMove result;
5718                 char *reason = NULL;
5719
5720                 /* Count what is on board. */
5721                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5722                 {   ChessSquare p = boards[forwardMostMove][i][j];
5723                     int m=i;
5724
5725                     switch((int) p)
5726                     {   /* count B,N,R and other of each side */
5727                         case WhiteKing:
5728                         case BlackKing:
5729                              NrK++; break; // [HGM] atomic: count Kings
5730                         case WhiteKnight:
5731                              NrWN++; break;
5732                         case WhiteBishop:
5733                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5734                              bishopsColor |= 1 << ((i^j)&1);
5735                              NrWB++; break;
5736                         case BlackKnight:
5737                              NrBN++; break;
5738                         case BlackBishop:
5739                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5740                              bishopsColor |= 1 << ((i^j)&1);
5741                              NrBB++; break;
5742                         case WhiteRook:
5743                              NrWR++; break;
5744                         case BlackRook:
5745                              NrBR++; break;
5746                         case WhiteQueen:
5747                              NrWQ++; break;
5748                         case BlackQueen:
5749                              NrBQ++; break;
5750                         case EmptySquare: 
5751                              break;
5752                         case BlackPawn:
5753                              m = 7-i;
5754                         case WhitePawn:
5755                              PawnAdvance += m; NrPawns++;
5756                     }
5757                     NrPieces += (p != EmptySquare);
5758                     NrW += ((int)p < (int)BlackPawn);
5759                     if(gameInfo.variant == VariantXiangqi && 
5760                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5761                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5762                         NrW -= ((int)p < (int)BlackPawn);
5763                     }
5764                 }
5765
5766                 /* Some material-based adjudications that have to be made before stalemate test */
5767                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5768                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5769                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5770                      if(appData.checkMates) {
5771                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5772                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5773                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5774                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5775                          return;
5776                      }
5777                 }
5778
5779                 /* Bare King in Shatranj (loses) or Losers (wins) */
5780                 if( NrW == 1 || NrPieces - NrW == 1) {
5781                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5782                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5783                      if(appData.checkMates) {
5784                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5785                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5787                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5788                          return;
5789                      }
5790                   } else
5791                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5792                   {    /* bare King */
5793                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5794                         if(appData.checkMates) {
5795                             /* but only adjudicate if adjudication enabled */
5796                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5797                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5798                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5799                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5800                             return;
5801                         }
5802                   }
5803                 } else bare = 1;
5804
5805
5806             // don't wait for engine to announce game end if we can judge ourselves
5807             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5808                                        castlingRights[forwardMostMove]) ) {
5809               case MT_CHECK:
5810                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5811                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5812                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5813                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5814                             checkCnt++;
5815                         if(checkCnt >= 2) {
5816                             reason = "Xboard adjudication: 3rd check";
5817                             epStatus[forwardMostMove] = EP_CHECKMATE;
5818                             break;
5819                         }
5820                     }
5821                 }
5822               case MT_NONE:
5823               default:
5824                 break;
5825               case MT_STALEMATE:
5826               case MT_STAINMATE:
5827                 reason = "Xboard adjudication: Stalemate";
5828                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5829                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5830                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5831                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5832                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5833                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5834                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5835                                                                         EP_CHECKMATE : EP_WINS);
5836                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5837                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5838                 }
5839                 break;
5840               case MT_CHECKMATE:
5841                 reason = "Xboard adjudication: Checkmate";
5842                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5843                 break;
5844             }
5845
5846                 switch(i = epStatus[forwardMostMove]) {
5847                     case EP_STALEMATE:
5848                         result = GameIsDrawn; break;
5849                     case EP_CHECKMATE:
5850                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5851                     case EP_WINS:
5852                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5853                     default:
5854                         result = (ChessMove) 0;
5855                 }
5856                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5857                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5858                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859                     GameEnds( result, reason, GE_XBOARD );
5860                     return;
5861                 }
5862
5863                 /* Next absolutely insufficient mating material. */
5864                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5865                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5866                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5867                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5868                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5869
5870                      /* always flag draws, for judging claims */
5871                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5872
5873                      if(appData.materialDraws) {
5874                          /* but only adjudicate them if adjudication enabled */
5875                          SendToProgram("force\n", cps->other); // suppress reply
5876                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5877                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5878                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5879                          return;
5880                      }
5881                 }
5882
5883                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5884                 if(NrPieces == 4 && 
5885                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5886                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5887                    || NrWN==2 || NrBN==2     /* KNNK */
5888                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5889                   ) ) {
5890                      if(--moveCount < 0 && appData.trivialDraws)
5891                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5892                           SendToProgram("force\n", cps->other); // suppress reply
5893                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5894                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5895                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5896                           return;
5897                      }
5898                 } else moveCount = 6;
5899             }
5900           }
5901 #if 1
5902     if (appData.debugMode) { int i;
5903       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5904               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5905               appData.drawRepeats);
5906       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5907            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5908
5909     }
5910 #endif
5911                 /* Check for rep-draws */
5912                 count = 0;
5913                 for(k = forwardMostMove-2;
5914                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5915                         epStatus[k] < EP_UNKNOWN &&
5916                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5917                     k-=2)
5918                 {   int rights=0;
5919 #if 0
5920     if (appData.debugMode) {
5921       fprintf(debugFP, " loop\n");
5922     }
5923 #endif
5924                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5925 #if 0
5926     if (appData.debugMode) {
5927       fprintf(debugFP, "match\n");
5928     }
5929 #endif
5930                         /* compare castling rights */
5931                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5932                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5933                                 rights++; /* King lost rights, while rook still had them */
5934                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5935                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5936                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5937                                    rights++; /* but at least one rook lost them */
5938                         }
5939                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5940                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5941                                 rights++; 
5942                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5943                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5944                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5945                                    rights++;
5946                         }
5947 #if 0
5948     if (appData.debugMode) {
5949       for(i=0; i<nrCastlingRights; i++)
5950       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5951     }
5952
5953     if (appData.debugMode) {
5954       fprintf(debugFP, " %d %d\n", rights, k);
5955     }
5956 #endif
5957                         if( rights == 0 && ++count > appData.drawRepeats-2
5958                             && appData.drawRepeats > 1) {
5959                              /* adjudicate after user-specified nr of repeats */
5960                              SendToProgram("force\n", cps->other); // suppress reply
5961                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5962                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5963                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5964                                 // [HGM] xiangqi: check for forbidden perpetuals
5965                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5966                                 for(m=forwardMostMove; m>k; m-=2) {
5967                                     if(MateTest(boards[m], PosFlags(m), 
5968                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5969                                         ourPerpetual = 0; // the current mover did not always check
5970                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5971                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5972                                         hisPerpetual = 0; // the opponent did not always check
5973                                 }
5974                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5975                                                                         ourPerpetual, hisPerpetual);
5976                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5977                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5978                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5979                                     return;
5980                                 }
5981                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5982                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5983                                 // Now check for perpetual chases
5984                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5985                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5986                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5987                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5988                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5989                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5990                                         return;
5991                                     }
5992                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5993                                         break; // Abort repetition-checking loop.
5994                                 }
5995                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5996                              }
5997                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5998                              return;
5999                         }
6000                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6001                              epStatus[forwardMostMove] = EP_REP_DRAW;
6002                     }
6003                 }
6004
6005                 /* Now we test for 50-move draws. Determine ply count */
6006                 count = forwardMostMove;
6007                 /* look for last irreversble move */
6008                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6009                     count--;
6010                 /* if we hit starting position, add initial plies */
6011                 if( count == backwardMostMove )
6012                     count -= initialRulePlies;
6013                 count = forwardMostMove - count; 
6014                 if( count >= 100)
6015                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6016                          /* this is used to judge if draw claims are legal */
6017                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6018                          SendToProgram("force\n", cps->other); // suppress reply
6019                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6020                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6021                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6022                          return;
6023                 }
6024
6025                 /* if draw offer is pending, treat it as a draw claim
6026                  * when draw condition present, to allow engines a way to
6027                  * claim draws before making their move to avoid a race
6028                  * condition occurring after their move
6029                  */
6030                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6031                          char *p = NULL;
6032                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6033                              p = "Draw claim: 50-move rule";
6034                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6035                              p = "Draw claim: 3-fold repetition";
6036                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6037                              p = "Draw claim: insufficient mating material";
6038                          if( p != NULL ) {
6039                              SendToProgram("force\n", cps->other); // suppress reply
6040                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6041                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6042                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6043                              return;
6044                          }
6045                 }
6046
6047
6048                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6049                     SendToProgram("force\n", cps->other); // suppress reply
6050                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6051                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6052
6053                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6054
6055                     return;
6056                 }
6057         }
6058
6059         bookHit = NULL;
6060         if (gameMode == TwoMachinesPlay) {
6061             /* [HGM] relaying draw offers moved to after reception of move */
6062             /* and interpreting offer as claim if it brings draw condition */
6063             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6064                 SendToProgram("draw\n", cps->other);
6065             }
6066             if (cps->other->sendTime) {
6067                 SendTimeRemaining(cps->other,
6068                                   cps->other->twoMachinesColor[0] == 'w');
6069             }
6070             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6071             if (firstMove && !bookHit) {
6072                 firstMove = FALSE;
6073                 if (cps->other->useColors) {
6074                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6075                 }
6076                 SendToProgram("go\n", cps->other);
6077             }
6078             cps->other->maybeThinking = TRUE;
6079         }
6080
6081         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6082         
6083         if (!pausing && appData.ringBellAfterMoves) {
6084             RingBell();
6085         }
6086
6087         /* 
6088          * Reenable menu items that were disabled while
6089          * machine was thinking
6090          */
6091         if (gameMode != TwoMachinesPlay)
6092             SetUserThinkingEnables();
6093
6094         // [HGM] book: after book hit opponent has received move and is now in force mode
6095         // force the book reply into it, and then fake that it outputted this move by jumping
6096         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6097         if(bookHit) {
6098                 static char bookMove[MSG_SIZ]; // a bit generous?
6099
6100                 strcpy(bookMove, "move ");
6101                 strcat(bookMove, bookHit);
6102                 message = bookMove;
6103                 cps = cps->other;
6104                 programStats.nodes = programStats.depth = programStats.time = 
6105                 programStats.score = programStats.got_only_move = 0;
6106                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6107
6108                 if(cps->lastPing != cps->lastPong) {
6109                     savedMessage = message; // args for deferred call
6110                     savedState = cps;
6111                     ScheduleDelayedEvent(DeferredBookMove, 10);
6112                     return;
6113                 }
6114                 goto FakeBookMove;
6115         }
6116
6117         return;
6118     }
6119
6120     /* Set special modes for chess engines.  Later something general
6121      *  could be added here; for now there is just one kludge feature,
6122      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6123      *  when "xboard" is given as an interactive command.
6124      */
6125     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6126         cps->useSigint = FALSE;
6127         cps->useSigterm = FALSE;
6128     }
6129     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6130       ParseFeatures(message+8, cps);
6131       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6132     }
6133
6134     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6135      * want this, I was asked to put it in, and obliged.
6136      */
6137     if (!strncmp(message, "setboard ", 9)) {
6138         Board initial_position; int i;
6139
6140         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6141
6142         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6143             DisplayError(_("Bad FEN received from engine"), 0);
6144             return ;
6145         } else {
6146            Reset(FALSE, FALSE);
6147            CopyBoard(boards[0], initial_position);
6148            initialRulePlies = FENrulePlies;
6149            epStatus[0] = FENepStatus;
6150            for( i=0; i<nrCastlingRights; i++ )
6151                 castlingRights[0][i] = FENcastlingRights[i];
6152            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6153            else gameMode = MachinePlaysBlack;                 
6154            DrawPosition(FALSE, boards[currentMove]);
6155         }
6156         return;
6157     }
6158
6159     /*
6160      * Look for communication commands
6161      */
6162     if (!strncmp(message, "telluser ", 9)) {
6163         DisplayNote(message + 9);
6164         return;
6165     }
6166     if (!strncmp(message, "tellusererror ", 14)) {
6167         DisplayError(message + 14, 0);
6168         return;
6169     }
6170     if (!strncmp(message, "tellopponent ", 13)) {
6171       if (appData.icsActive) {
6172         if (loggedOn) {
6173           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6174           SendToICS(buf1);
6175         }
6176       } else {
6177         DisplayNote(message + 13);
6178       }
6179       return;
6180     }
6181     if (!strncmp(message, "tellothers ", 11)) {
6182       if (appData.icsActive) {
6183         if (loggedOn) {
6184           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6185           SendToICS(buf1);
6186         }
6187       }
6188       return;
6189     }
6190     if (!strncmp(message, "tellall ", 8)) {
6191       if (appData.icsActive) {
6192         if (loggedOn) {
6193           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6194           SendToICS(buf1);
6195         }
6196       } else {
6197         DisplayNote(message + 8);
6198       }
6199       return;
6200     }
6201     if (strncmp(message, "warning", 7) == 0) {
6202         /* Undocumented feature, use tellusererror in new code */
6203         DisplayError(message, 0);
6204         return;
6205     }
6206     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6207         strcpy(realname, cps->tidy);
6208         strcat(realname, " query");
6209         AskQuestion(realname, buf2, buf1, cps->pr);
6210         return;
6211     }
6212     /* Commands from the engine directly to ICS.  We don't allow these to be 
6213      *  sent until we are logged on. Crafty kibitzes have been known to 
6214      *  interfere with the login process.
6215      */
6216     if (loggedOn) {
6217         if (!strncmp(message, "tellics ", 8)) {
6218             SendToICS(message + 8);
6219             SendToICS("\n");
6220             return;
6221         }
6222         if (!strncmp(message, "tellicsnoalias ", 15)) {
6223             SendToICS(ics_prefix);
6224             SendToICS(message + 15);
6225             SendToICS("\n");
6226             return;
6227         }
6228         /* The following are for backward compatibility only */
6229         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6230             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6231             SendToICS(ics_prefix);
6232             SendToICS(message);
6233             SendToICS("\n");
6234             return;
6235         }
6236     }
6237     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6238         return;
6239     }
6240     /*
6241      * If the move is illegal, cancel it and redraw the board.
6242      * Also deal with other error cases.  Matching is rather loose
6243      * here to accommodate engines written before the spec.
6244      */
6245     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6246         strncmp(message, "Error", 5) == 0) {
6247         if (StrStr(message, "name") || 
6248             StrStr(message, "rating") || StrStr(message, "?") ||
6249             StrStr(message, "result") || StrStr(message, "board") ||
6250             StrStr(message, "bk") || StrStr(message, "computer") ||
6251             StrStr(message, "variant") || StrStr(message, "hint") ||
6252             StrStr(message, "random") || StrStr(message, "depth") ||
6253             StrStr(message, "accepted")) {
6254             return;
6255         }
6256         if (StrStr(message, "protover")) {
6257           /* Program is responding to input, so it's apparently done
6258              initializing, and this error message indicates it is
6259              protocol version 1.  So we don't need to wait any longer
6260              for it to initialize and send feature commands. */
6261           FeatureDone(cps, 1);
6262           cps->protocolVersion = 1;
6263           return;
6264         }
6265         cps->maybeThinking = FALSE;
6266
6267         if (StrStr(message, "draw")) {
6268             /* Program doesn't have "draw" command */
6269             cps->sendDrawOffers = 0;
6270             return;
6271         }
6272         if (cps->sendTime != 1 &&
6273             (StrStr(message, "time") || StrStr(message, "otim"))) {
6274           /* Program apparently doesn't have "time" or "otim" command */
6275           cps->sendTime = 0;
6276           return;
6277         }
6278         if (StrStr(message, "analyze")) {
6279             cps->analysisSupport = FALSE;
6280             cps->analyzing = FALSE;
6281             Reset(FALSE, TRUE);
6282             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6283             DisplayError(buf2, 0);
6284             return;
6285         }
6286         if (StrStr(message, "(no matching move)st")) {
6287           /* Special kludge for GNU Chess 4 only */
6288           cps->stKludge = TRUE;
6289           SendTimeControl(cps, movesPerSession, timeControl,
6290                           timeIncrement, appData.searchDepth,
6291                           searchTime);
6292           return;
6293         }
6294         if (StrStr(message, "(no matching move)sd")) {
6295           /* Special kludge for GNU Chess 4 only */
6296           cps->sdKludge = TRUE;
6297           SendTimeControl(cps, movesPerSession, timeControl,
6298                           timeIncrement, appData.searchDepth,
6299                           searchTime);
6300           return;
6301         }
6302         if (!StrStr(message, "llegal")) {
6303             return;
6304         }
6305         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6306             gameMode == IcsIdle) return;
6307         if (forwardMostMove <= backwardMostMove) return;
6308 #if 0
6309         /* Following removed: it caused a bug where a real illegal move
6310            message in analyze mored would be ignored. */
6311         if (cps == &first && programStats.ok_to_send == 0) {
6312             /* Bogus message from Crafty responding to "."  This filtering
6313                can miss some of the bad messages, but fortunately the bug 
6314                is fixed in current Crafty versions, so it doesn't matter. */
6315             return;
6316         }
6317 #endif
6318         if (pausing) PauseEvent();
6319         if (gameMode == PlayFromGameFile) {
6320             /* Stop reading this game file */
6321             gameMode = EditGame;
6322             ModeHighlight();
6323         }
6324         currentMove = --forwardMostMove;
6325         DisplayMove(currentMove-1); /* before DisplayMoveError */
6326         SwitchClocks();
6327         DisplayBothClocks();
6328         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6329                 parseList[currentMove], cps->which);
6330         DisplayMoveError(buf1);
6331         DrawPosition(FALSE, boards[currentMove]);
6332
6333         /* [HGM] illegal-move claim should forfeit game when Xboard */
6334         /* only passes fully legal moves                            */
6335         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6336             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6337                                 "False illegal-move claim", GE_XBOARD );
6338         }
6339         return;
6340     }
6341     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6342         /* Program has a broken "time" command that
6343            outputs a string not ending in newline.
6344            Don't use it. */
6345         cps->sendTime = 0;
6346     }
6347     
6348     /*
6349      * If chess program startup fails, exit with an error message.
6350      * Attempts to recover here are futile.
6351      */
6352     if ((StrStr(message, "unknown host") != NULL)
6353         || (StrStr(message, "No remote directory") != NULL)
6354         || (StrStr(message, "not found") != NULL)
6355         || (StrStr(message, "No such file") != NULL)
6356         || (StrStr(message, "can't alloc") != NULL)
6357         || (StrStr(message, "Permission denied") != NULL)) {
6358
6359         cps->maybeThinking = FALSE;
6360         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6361                 cps->which, cps->program, cps->host, message);
6362         RemoveInputSource(cps->isr);
6363         DisplayFatalError(buf1, 0, 1);
6364         return;
6365     }
6366     
6367     /* 
6368      * Look for hint output
6369      */
6370     if (sscanf(message, "Hint: %s", buf1) == 1) {
6371         if (cps == &first && hintRequested) {
6372             hintRequested = FALSE;
6373             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6374                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6375                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6376                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6377                                     fromY, fromX, toY, toX, promoChar, buf1);
6378                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6379                 DisplayInformation(buf2);
6380             } else {
6381                 /* Hint move could not be parsed!? */
6382               snprintf(buf2, sizeof(buf2),
6383                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6384                         buf1, cps->which);
6385                 DisplayError(buf2, 0);
6386             }
6387         } else {
6388             strcpy(lastHint, buf1);
6389         }
6390         return;
6391     }
6392
6393     /*
6394      * Ignore other messages if game is not in progress
6395      */
6396     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6397         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6398
6399     /*
6400      * look for win, lose, draw, or draw offer
6401      */
6402     if (strncmp(message, "1-0", 3) == 0) {
6403         char *p, *q, *r = "";
6404         p = strchr(message, '{');
6405         if (p) {
6406             q = strchr(p, '}');
6407             if (q) {
6408                 *q = NULLCHAR;
6409                 r = p + 1;
6410             }
6411         }
6412         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6413         return;
6414     } else if (strncmp(message, "0-1", 3) == 0) {
6415         char *p, *q, *r = "";
6416         p = strchr(message, '{');
6417         if (p) {
6418             q = strchr(p, '}');
6419             if (q) {
6420                 *q = NULLCHAR;
6421                 r = p + 1;
6422             }
6423         }
6424         /* Kludge for Arasan 4.1 bug */
6425         if (strcmp(r, "Black resigns") == 0) {
6426             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6427             return;
6428         }
6429         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6430         return;
6431     } else if (strncmp(message, "1/2", 3) == 0) {
6432         char *p, *q, *r = "";
6433         p = strchr(message, '{');
6434         if (p) {
6435             q = strchr(p, '}');
6436             if (q) {
6437                 *q = NULLCHAR;
6438                 r = p + 1;
6439             }
6440         }
6441             
6442         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6443         return;
6444
6445     } else if (strncmp(message, "White resign", 12) == 0) {
6446         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6447         return;
6448     } else if (strncmp(message, "Black resign", 12) == 0) {
6449         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6450         return;
6451     } else if (strncmp(message, "White matches", 13) == 0 ||
6452                strncmp(message, "Black matches", 13) == 0   ) {
6453         /* [HGM] ignore GNUShogi noises */
6454         return;
6455     } else if (strncmp(message, "White", 5) == 0 &&
6456                message[5] != '(' &&
6457                StrStr(message, "Black") == NULL) {
6458         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6459         return;
6460     } else if (strncmp(message, "Black", 5) == 0 &&
6461                message[5] != '(') {
6462         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6463         return;
6464     } else if (strcmp(message, "resign") == 0 ||
6465                strcmp(message, "computer resigns") == 0) {
6466         switch (gameMode) {
6467           case MachinePlaysBlack:
6468           case IcsPlayingBlack:
6469             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6470             break;
6471           case MachinePlaysWhite:
6472           case IcsPlayingWhite:
6473             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6474             break;
6475           case TwoMachinesPlay:
6476             if (cps->twoMachinesColor[0] == 'w')
6477               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6478             else
6479               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6480             break;
6481           default:
6482             /* can't happen */
6483             break;
6484         }
6485         return;
6486     } else if (strncmp(message, "opponent mates", 14) == 0) {
6487         switch (gameMode) {
6488           case MachinePlaysBlack:
6489           case IcsPlayingBlack:
6490             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6491             break;
6492           case MachinePlaysWhite:
6493           case IcsPlayingWhite:
6494             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6495             break;
6496           case TwoMachinesPlay:
6497             if (cps->twoMachinesColor[0] == 'w')
6498               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6499             else
6500               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6501             break;
6502           default:
6503             /* can't happen */
6504             break;
6505         }
6506         return;
6507     } else if (strncmp(message, "computer mates", 14) == 0) {
6508         switch (gameMode) {
6509           case MachinePlaysBlack:
6510           case IcsPlayingBlack:
6511             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6512             break;
6513           case MachinePlaysWhite:
6514           case IcsPlayingWhite:
6515             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6516             break;
6517           case TwoMachinesPlay:
6518             if (cps->twoMachinesColor[0] == 'w')
6519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520             else
6521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6522             break;
6523           default:
6524             /* can't happen */
6525             break;
6526         }
6527         return;
6528     } else if (strncmp(message, "checkmate", 9) == 0) {
6529         if (WhiteOnMove(forwardMostMove)) {
6530             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6531         } else {
6532             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6533         }
6534         return;
6535     } else if (strstr(message, "Draw") != NULL ||
6536                strstr(message, "game is a draw") != NULL) {
6537         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6538         return;
6539     } else if (strstr(message, "offer") != NULL &&
6540                strstr(message, "draw") != NULL) {
6541 #if ZIPPY
6542         if (appData.zippyPlay && first.initDone) {
6543             /* Relay offer to ICS */
6544             SendToICS(ics_prefix);
6545             SendToICS("draw\n");
6546         }
6547 #endif
6548         cps->offeredDraw = 2; /* valid until this engine moves twice */
6549         if (gameMode == TwoMachinesPlay) {
6550             if (cps->other->offeredDraw) {
6551                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6552             /* [HGM] in two-machine mode we delay relaying draw offer      */
6553             /* until after we also have move, to see if it is really claim */
6554             }
6555 #if 0
6556               else {
6557                 if (cps->other->sendDrawOffers) {
6558                     SendToProgram("draw\n", cps->other);
6559                 }
6560             }
6561 #endif
6562         } else if (gameMode == MachinePlaysWhite ||
6563                    gameMode == MachinePlaysBlack) {
6564           if (userOfferedDraw) {
6565             DisplayInformation(_("Machine accepts your draw offer"));
6566             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6567           } else {
6568             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6569           }
6570         }
6571     }
6572
6573     
6574     /*
6575      * Look for thinking output
6576      */
6577     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6578           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6579                                 ) {
6580         int plylev, mvleft, mvtot, curscore, time;
6581         char mvname[MOVE_LEN];
6582         u64 nodes; // [DM]
6583         char plyext;
6584         int ignore = FALSE;
6585         int prefixHint = FALSE;
6586         mvname[0] = NULLCHAR;
6587
6588         switch (gameMode) {
6589           case MachinePlaysBlack:
6590           case IcsPlayingBlack:
6591             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6592             break;
6593           case MachinePlaysWhite:
6594           case IcsPlayingWhite:
6595             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6596             break;
6597           case AnalyzeMode:
6598           case AnalyzeFile:
6599             break;
6600           case IcsObserving: /* [DM] icsEngineAnalyze */
6601             if (!appData.icsEngineAnalyze) ignore = TRUE;
6602             break;
6603           case TwoMachinesPlay:
6604             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6605                 ignore = TRUE;
6606             }
6607             break;
6608           default:
6609             ignore = TRUE;
6610             break;
6611         }
6612
6613         if (!ignore) {
6614             buf1[0] = NULLCHAR;
6615             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6616                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6617
6618                 if (plyext != ' ' && plyext != '\t') {
6619                     time *= 100;
6620                 }
6621
6622                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6623                 if( cps->scoreIsAbsolute && 
6624                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6625                 {
6626                     curscore = -curscore;
6627                 }
6628
6629
6630                 programStats.depth = plylev;
6631                 programStats.nodes = nodes;
6632                 programStats.time = time;
6633                 programStats.score = curscore;
6634                 programStats.got_only_move = 0;
6635
6636                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6637                         int ticklen;
6638
6639                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6640                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6641                         if(WhiteOnMove(forwardMostMove)) 
6642                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6643                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6644                 }
6645
6646                 /* Buffer overflow protection */
6647                 if (buf1[0] != NULLCHAR) {
6648                     if (strlen(buf1) >= sizeof(programStats.movelist)
6649                         && appData.debugMode) {
6650                         fprintf(debugFP,
6651                                 "PV is too long; using the first %d bytes.\n",
6652                                 sizeof(programStats.movelist) - 1);
6653                     }
6654
6655                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6656                 } else {
6657                     sprintf(programStats.movelist, " no PV\n");
6658                 }
6659
6660                 if (programStats.seen_stat) {
6661                     programStats.ok_to_send = 1;
6662                 }
6663
6664                 if (strchr(programStats.movelist, '(') != NULL) {
6665                     programStats.line_is_book = 1;
6666                     programStats.nr_moves = 0;
6667                     programStats.moves_left = 0;
6668                 } else {
6669                     programStats.line_is_book = 0;
6670                 }
6671
6672                 SendProgramStatsToFrontend( cps, &programStats );
6673
6674                 /* 
6675                     [AS] Protect the thinkOutput buffer from overflow... this
6676                     is only useful if buf1 hasn't overflowed first!
6677                 */
6678                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6679                         plylev, 
6680                         (gameMode == TwoMachinesPlay ?
6681                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6682                         ((double) curscore) / 100.0,
6683                         prefixHint ? lastHint : "",
6684                         prefixHint ? " " : "" );
6685
6686                 if( buf1[0] != NULLCHAR ) {
6687                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6688
6689                     if( strlen(buf1) > max_len ) {
6690                         if( appData.debugMode) {
6691                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6692                         }
6693                         buf1[max_len+1] = '\0';
6694                     }
6695
6696                     strcat( thinkOutput, buf1 );
6697                 }
6698
6699                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6700                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6701                     DisplayMove(currentMove - 1);
6702                     DisplayAnalysis();
6703                 }
6704                 return;
6705
6706             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6707                 /* crafty (9.25+) says "(only move) <move>"
6708                  * if there is only 1 legal move
6709                  */
6710                 sscanf(p, "(only move) %s", buf1);
6711                 sprintf(thinkOutput, "%s (only move)", buf1);
6712                 sprintf(programStats.movelist, "%s (only move)", buf1);
6713                 programStats.depth = 1;
6714                 programStats.nr_moves = 1;
6715                 programStats.moves_left = 1;
6716                 programStats.nodes = 1;
6717                 programStats.time = 1;
6718                 programStats.got_only_move = 1;
6719
6720                 /* Not really, but we also use this member to
6721                    mean "line isn't going to change" (Crafty
6722                    isn't searching, so stats won't change) */
6723                 programStats.line_is_book = 1;
6724
6725                 SendProgramStatsToFrontend( cps, &programStats );
6726                 
6727                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6728                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6729                     DisplayMove(currentMove - 1);
6730                     DisplayAnalysis();
6731                 }
6732                 return;
6733             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6734                               &time, &nodes, &plylev, &mvleft,
6735                               &mvtot, mvname) >= 5) {
6736                 /* The stat01: line is from Crafty (9.29+) in response
6737                    to the "." command */
6738                 programStats.seen_stat = 1;
6739                 cps->maybeThinking = TRUE;
6740
6741                 if (programStats.got_only_move || !appData.periodicUpdates)
6742                   return;
6743
6744                 programStats.depth = plylev;
6745                 programStats.time = time;
6746                 programStats.nodes = nodes;
6747                 programStats.moves_left = mvleft;
6748                 programStats.nr_moves = mvtot;
6749                 strcpy(programStats.move_name, mvname);
6750                 programStats.ok_to_send = 1;
6751                 programStats.movelist[0] = '\0';
6752
6753                 SendProgramStatsToFrontend( cps, &programStats );
6754
6755                 DisplayAnalysis();
6756                 return;
6757
6758             } else if (strncmp(message,"++",2) == 0) {
6759                 /* Crafty 9.29+ outputs this */
6760                 programStats.got_fail = 2;
6761                 return;
6762
6763             } else if (strncmp(message,"--",2) == 0) {
6764                 /* Crafty 9.29+ outputs this */
6765                 programStats.got_fail = 1;
6766                 return;
6767
6768             } else if (thinkOutput[0] != NULLCHAR &&
6769                        strncmp(message, "    ", 4) == 0) {
6770                 unsigned message_len;
6771
6772                 p = message;
6773                 while (*p && *p == ' ') p++;
6774
6775                 message_len = strlen( p );
6776
6777                 /* [AS] Avoid buffer overflow */
6778                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6779                     strcat(thinkOutput, " ");
6780                     strcat(thinkOutput, p);
6781                 }
6782
6783                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6784                     strcat(programStats.movelist, " ");
6785                     strcat(programStats.movelist, p);
6786                 }
6787
6788                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6789                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6790                     DisplayMove(currentMove - 1);
6791                     DisplayAnalysis();
6792                 }
6793                 return;
6794             }
6795         }
6796         else {
6797             buf1[0] = NULLCHAR;
6798
6799             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6800                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6801             {
6802                 ChessProgramStats cpstats;
6803
6804                 if (plyext != ' ' && plyext != '\t') {
6805                     time *= 100;
6806                 }
6807
6808                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6809                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6810                     curscore = -curscore;
6811                 }
6812
6813                 cpstats.depth = plylev;
6814                 cpstats.nodes = nodes;
6815                 cpstats.time = time;
6816                 cpstats.score = curscore;
6817                 cpstats.got_only_move = 0;
6818                 cpstats.movelist[0] = '\0';
6819
6820                 if (buf1[0] != NULLCHAR) {
6821                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6822                 }
6823
6824                 cpstats.ok_to_send = 0;
6825                 cpstats.line_is_book = 0;
6826                 cpstats.nr_moves = 0;
6827                 cpstats.moves_left = 0;
6828
6829                 SendProgramStatsToFrontend( cps, &cpstats );
6830             }
6831         }
6832     }
6833 }
6834
6835
6836 /* Parse a game score from the character string "game", and
6837    record it as the history of the current game.  The game
6838    score is NOT assumed to start from the standard position. 
6839    The display is not updated in any way.
6840    */
6841 void
6842 ParseGameHistory(game)
6843      char *game;
6844 {
6845     ChessMove moveType;
6846     int fromX, fromY, toX, toY, boardIndex;
6847     char promoChar;
6848     char *p, *q;
6849     char buf[MSG_SIZ];
6850
6851     if (appData.debugMode)
6852       fprintf(debugFP, "Parsing game history: %s\n", game);
6853
6854     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6855     gameInfo.site = StrSave(appData.icsHost);
6856     gameInfo.date = PGNDate();
6857     gameInfo.round = StrSave("-");
6858
6859     /* Parse out names of players */
6860     while (*game == ' ') game++;
6861     p = buf;
6862     while (*game != ' ') *p++ = *game++;
6863     *p = NULLCHAR;
6864     gameInfo.white = StrSave(buf);
6865     while (*game == ' ') game++;
6866     p = buf;
6867     while (*game != ' ' && *game != '\n') *p++ = *game++;
6868     *p = NULLCHAR;
6869     gameInfo.black = StrSave(buf);
6870
6871     /* Parse moves */
6872     boardIndex = blackPlaysFirst ? 1 : 0;
6873     yynewstr(game);
6874     for (;;) {
6875         yyboardindex = boardIndex;
6876         moveType = (ChessMove) yylex();
6877         switch (moveType) {
6878           case IllegalMove:             /* maybe suicide chess, etc. */
6879   if (appData.debugMode) {
6880     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6881     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6882     setbuf(debugFP, NULL);
6883   }
6884           case WhitePromotionChancellor:
6885           case BlackPromotionChancellor:
6886           case WhitePromotionArchbishop:
6887           case BlackPromotionArchbishop:
6888           case WhitePromotionQueen:
6889           case BlackPromotionQueen:
6890           case WhitePromotionRook:
6891           case BlackPromotionRook:
6892           case WhitePromotionBishop:
6893           case BlackPromotionBishop:
6894           case WhitePromotionKnight:
6895           case BlackPromotionKnight:
6896           case WhitePromotionKing:
6897           case BlackPromotionKing:
6898           case NormalMove:
6899           case WhiteCapturesEnPassant:
6900           case BlackCapturesEnPassant:
6901           case WhiteKingSideCastle:
6902           case WhiteQueenSideCastle:
6903           case BlackKingSideCastle:
6904           case BlackQueenSideCastle:
6905           case WhiteKingSideCastleWild:
6906           case WhiteQueenSideCastleWild:
6907           case BlackKingSideCastleWild:
6908           case BlackQueenSideCastleWild:
6909           /* PUSH Fabien */
6910           case WhiteHSideCastleFR:
6911           case WhiteASideCastleFR:
6912           case BlackHSideCastleFR:
6913           case BlackASideCastleFR:
6914           /* POP Fabien */
6915             fromX = currentMoveString[0] - AAA;
6916             fromY = currentMoveString[1] - ONE;
6917             toX = currentMoveString[2] - AAA;
6918             toY = currentMoveString[3] - ONE;
6919             promoChar = currentMoveString[4];
6920             break;
6921           case WhiteDrop:
6922           case BlackDrop:
6923             fromX = moveType == WhiteDrop ?
6924               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6925             (int) CharToPiece(ToLower(currentMoveString[0]));
6926             fromY = DROP_RANK;
6927             toX = currentMoveString[2] - AAA;
6928             toY = currentMoveString[3] - ONE;
6929             promoChar = NULLCHAR;
6930             break;
6931           case AmbiguousMove:
6932             /* bug? */
6933             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6934   if (appData.debugMode) {
6935     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6936     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6937     setbuf(debugFP, NULL);
6938   }
6939             DisplayError(buf, 0);
6940             return;
6941           case ImpossibleMove:
6942             /* bug? */
6943             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6944   if (appData.debugMode) {
6945     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6946     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6947     setbuf(debugFP, NULL);
6948   }
6949             DisplayError(buf, 0);
6950             return;
6951           case (ChessMove) 0:   /* end of file */
6952             if (boardIndex < backwardMostMove) {
6953                 /* Oops, gap.  How did that happen? */
6954                 DisplayError(_("Gap in move list"), 0);
6955                 return;
6956             }
6957             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6958             if (boardIndex > forwardMostMove) {
6959                 forwardMostMove = boardIndex;
6960             }
6961             return;
6962           case ElapsedTime:
6963             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6964                 strcat(parseList[boardIndex-1], " ");
6965                 strcat(parseList[boardIndex-1], yy_text);
6966             }
6967             continue;
6968           case Comment:
6969           case PGNTag:
6970           case NAG:
6971           default:
6972             /* ignore */
6973             continue;
6974           case WhiteWins:
6975           case BlackWins:
6976           case GameIsDrawn:
6977           case GameUnfinished:
6978             if (gameMode == IcsExamining) {
6979                 if (boardIndex < backwardMostMove) {
6980                     /* Oops, gap.  How did that happen? */
6981                     return;
6982                 }
6983                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6984                 return;
6985             }
6986             gameInfo.result = moveType;
6987             p = strchr(yy_text, '{');
6988             if (p == NULL) p = strchr(yy_text, '(');
6989             if (p == NULL) {
6990                 p = yy_text;
6991                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6992             } else {
6993                 q = strchr(p, *p == '{' ? '}' : ')');
6994                 if (q != NULL) *q = NULLCHAR;
6995                 p++;
6996             }
6997             gameInfo.resultDetails = StrSave(p);
6998             continue;
6999         }
7000         if (boardIndex >= forwardMostMove &&
7001             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7002             backwardMostMove = blackPlaysFirst ? 1 : 0;
7003             return;
7004         }
7005         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7006                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7007                                  parseList[boardIndex]);
7008         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7009         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7010         /* currentMoveString is set as a side-effect of yylex */
7011         strcpy(moveList[boardIndex], currentMoveString);
7012         strcat(moveList[boardIndex], "\n");
7013         boardIndex++;
7014         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7015                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7016         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7017                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7018           case MT_NONE:
7019           case MT_STALEMATE:
7020           default:
7021             break;
7022           case MT_CHECK:
7023             if(gameInfo.variant != VariantShogi)
7024                 strcat(parseList[boardIndex - 1], "+");
7025             break;
7026           case MT_CHECKMATE:
7027           case MT_STAINMATE:
7028             strcat(parseList[boardIndex - 1], "#");
7029             break;
7030         }
7031     }
7032 }
7033
7034
7035 /* Apply a move to the given board  */
7036 void
7037 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7038      int fromX, fromY, toX, toY;
7039      int promoChar;
7040      Board board;
7041      char *castling;
7042      char *ep;
7043 {
7044   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7045
7046     /* [HGM] compute & store e.p. status and castling rights for new position */
7047     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7048     { int i;
7049
7050       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7051       oldEP = *ep;
7052       *ep = EP_NONE;
7053
7054       if( board[toY][toX] != EmptySquare ) 
7055            *ep = EP_CAPTURE;  
7056
7057       if( board[fromY][fromX] == WhitePawn ) {
7058            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7059                *ep = EP_PAWN_MOVE;
7060            if( toY-fromY==2) {
7061                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7062                         gameInfo.variant != VariantBerolina || toX < fromX)
7063                       *ep = toX | berolina;
7064                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7065                         gameInfo.variant != VariantBerolina || toX > fromX) 
7066                       *ep = toX;
7067            }
7068       } else 
7069       if( board[fromY][fromX] == BlackPawn ) {
7070            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7071                *ep = EP_PAWN_MOVE; 
7072            if( toY-fromY== -2) {
7073                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7074                         gameInfo.variant != VariantBerolina || toX < fromX)
7075                       *ep = toX | berolina;
7076                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7077                         gameInfo.variant != VariantBerolina || toX > fromX) 
7078                       *ep = toX;
7079            }
7080        }
7081
7082        for(i=0; i<nrCastlingRights; i++) {
7083            if(castling[i] == fromX && castlingRank[i] == fromY ||
7084               castling[i] == toX   && castlingRank[i] == toY   
7085              ) castling[i] = -1; // revoke for moved or captured piece
7086        }
7087
7088     }
7089
7090   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7091   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7092        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7093          
7094   if (fromX == toX && fromY == toY) return;
7095
7096   if (fromY == DROP_RANK) {
7097         /* must be first */
7098         piece = board[toY][toX] = (ChessSquare) fromX;
7099   } else {
7100      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7101      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7102      if(gameInfo.variant == VariantKnightmate)
7103          king += (int) WhiteUnicorn - (int) WhiteKing;
7104
7105     /* Code added by Tord: */
7106     /* FRC castling assumed when king captures friendly rook. */
7107     if (board[fromY][fromX] == WhiteKing &&
7108              board[toY][toX] == WhiteRook) {
7109       board[fromY][fromX] = EmptySquare;
7110       board[toY][toX] = EmptySquare;
7111       if(toX > fromX) {
7112         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7113       } else {
7114         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7115       }
7116     } else if (board[fromY][fromX] == BlackKing &&
7117                board[toY][toX] == BlackRook) {
7118       board[fromY][fromX] = EmptySquare;
7119       board[toY][toX] = EmptySquare;
7120       if(toX > fromX) {
7121         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7122       } else {
7123         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7124       }
7125     /* End of code added by Tord */
7126
7127     } else if (board[fromY][fromX] == king
7128         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7129         && toY == fromY && toX > fromX+1) {
7130         board[fromY][fromX] = EmptySquare;
7131         board[toY][toX] = king;
7132         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7133         board[fromY][BOARD_RGHT-1] = EmptySquare;
7134     } else if (board[fromY][fromX] == king
7135         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7136                && toY == fromY && toX < fromX-1) {
7137         board[fromY][fromX] = EmptySquare;
7138         board[toY][toX] = king;
7139         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7140         board[fromY][BOARD_LEFT] = EmptySquare;
7141     } else if (board[fromY][fromX] == WhitePawn
7142                && toY == BOARD_HEIGHT-1
7143                && gameInfo.variant != VariantXiangqi
7144                ) {
7145         /* white pawn promotion */
7146         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7147         if (board[toY][toX] == EmptySquare) {
7148             board[toY][toX] = WhiteQueen;
7149         }
7150         if(gameInfo.variant==VariantBughouse ||
7151            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7152             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7153         board[fromY][fromX] = EmptySquare;
7154     } else if ((fromY == BOARD_HEIGHT-4)
7155                && (toX != fromX)
7156                && gameInfo.variant != VariantXiangqi
7157                && gameInfo.variant != VariantBerolina
7158                && (board[fromY][fromX] == WhitePawn)
7159                && (board[toY][toX] == EmptySquare)) {
7160         board[fromY][fromX] = EmptySquare;
7161         board[toY][toX] = WhitePawn;
7162         captured = board[toY - 1][toX];
7163         board[toY - 1][toX] = EmptySquare;
7164     } else if ((fromY == BOARD_HEIGHT-4)
7165                && (toX == fromX)
7166                && gameInfo.variant == VariantBerolina
7167                && (board[fromY][fromX] == WhitePawn)
7168                && (board[toY][toX] == EmptySquare)) {
7169         board[fromY][fromX] = EmptySquare;
7170         board[toY][toX] = WhitePawn;
7171         if(oldEP & EP_BEROLIN_A) {
7172                 captured = board[fromY][fromX-1];
7173                 board[fromY][fromX-1] = EmptySquare;
7174         }else{  captured = board[fromY][fromX+1];
7175                 board[fromY][fromX+1] = EmptySquare;
7176         }
7177     } else if (board[fromY][fromX] == king
7178         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7179                && toY == fromY && toX > fromX+1) {
7180         board[fromY][fromX] = EmptySquare;
7181         board[toY][toX] = king;
7182         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7183         board[fromY][BOARD_RGHT-1] = EmptySquare;
7184     } else if (board[fromY][fromX] == king
7185         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7186                && toY == fromY && toX < fromX-1) {
7187         board[fromY][fromX] = EmptySquare;
7188         board[toY][toX] = king;
7189         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7190         board[fromY][BOARD_LEFT] = EmptySquare;
7191     } else if (fromY == 7 && fromX == 3
7192                && board[fromY][fromX] == BlackKing
7193                && toY == 7 && toX == 5) {
7194         board[fromY][fromX] = EmptySquare;
7195         board[toY][toX] = BlackKing;
7196         board[fromY][7] = EmptySquare;
7197         board[toY][4] = BlackRook;
7198     } else if (fromY == 7 && fromX == 3
7199                && board[fromY][fromX] == BlackKing
7200                && toY == 7 && toX == 1) {
7201         board[fromY][fromX] = EmptySquare;
7202         board[toY][toX] = BlackKing;
7203         board[fromY][0] = EmptySquare;
7204         board[toY][2] = BlackRook;
7205     } else if (board[fromY][fromX] == BlackPawn
7206                && toY == 0
7207                && gameInfo.variant != VariantXiangqi
7208                ) {
7209         /* black pawn promotion */
7210         board[0][toX] = CharToPiece(ToLower(promoChar));
7211         if (board[0][toX] == EmptySquare) {
7212             board[0][toX] = BlackQueen;
7213         }
7214         if(gameInfo.variant==VariantBughouse ||
7215            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7216             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7217         board[fromY][fromX] = EmptySquare;
7218     } else if ((fromY == 3)
7219                && (toX != fromX)
7220                && gameInfo.variant != VariantXiangqi
7221                && gameInfo.variant != VariantBerolina
7222                && (board[fromY][fromX] == BlackPawn)
7223                && (board[toY][toX] == EmptySquare)) {
7224         board[fromY][fromX] = EmptySquare;
7225         board[toY][toX] = BlackPawn;
7226         captured = board[toY + 1][toX];
7227         board[toY + 1][toX] = EmptySquare;
7228     } else if ((fromY == 3)
7229                && (toX == fromX)
7230                && gameInfo.variant == VariantBerolina
7231                && (board[fromY][fromX] == BlackPawn)
7232                && (board[toY][toX] == EmptySquare)) {
7233         board[fromY][fromX] = EmptySquare;
7234         board[toY][toX] = BlackPawn;
7235         if(oldEP & EP_BEROLIN_A) {
7236                 captured = board[fromY][fromX-1];
7237                 board[fromY][fromX-1] = EmptySquare;
7238         }else{  captured = board[fromY][fromX+1];
7239                 board[fromY][fromX+1] = EmptySquare;
7240         }
7241     } else {
7242         board[toY][toX] = board[fromY][fromX];
7243         board[fromY][fromX] = EmptySquare;
7244     }
7245
7246     /* [HGM] now we promote for Shogi, if needed */
7247     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7248         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7249   }
7250
7251     if (gameInfo.holdingsWidth != 0) {
7252
7253       /* !!A lot more code needs to be written to support holdings  */
7254       /* [HGM] OK, so I have written it. Holdings are stored in the */
7255       /* penultimate board files, so they are automaticlly stored   */
7256       /* in the game history.                                       */
7257       if (fromY == DROP_RANK) {
7258         /* Delete from holdings, by decreasing count */
7259         /* and erasing image if necessary            */
7260         p = (int) fromX;
7261         if(p < (int) BlackPawn) { /* white drop */
7262              p -= (int)WhitePawn;
7263              if(p >= gameInfo.holdingsSize) p = 0;
7264              if(--board[p][BOARD_WIDTH-2] == 0)
7265                   board[p][BOARD_WIDTH-1] = EmptySquare;
7266         } else {                  /* black drop */
7267              p -= (int)BlackPawn;
7268              if(p >= gameInfo.holdingsSize) p = 0;
7269              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7270                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7271         }
7272       }
7273       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7274           && gameInfo.variant != VariantBughouse        ) {
7275         /* [HGM] holdings: Add to holdings, if holdings exist */
7276         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7277                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7278                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7279         }
7280         p = (int) captured;
7281         if (p >= (int) BlackPawn) {
7282           p -= (int)BlackPawn;
7283           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7284                   /* in Shogi restore piece to its original  first */
7285                   captured = (ChessSquare) (DEMOTED captured);
7286                   p = DEMOTED p;
7287           }
7288           p = PieceToNumber((ChessSquare)p);
7289           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7290           board[p][BOARD_WIDTH-2]++;
7291           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7292         } else {
7293           p -= (int)WhitePawn;
7294           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7295                   captured = (ChessSquare) (DEMOTED captured);
7296                   p = DEMOTED p;
7297           }
7298           p = PieceToNumber((ChessSquare)p);
7299           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7300           board[BOARD_HEIGHT-1-p][1]++;
7301           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7302         }
7303       }
7304
7305     } else if (gameInfo.variant == VariantAtomic) {
7306       if (captured != EmptySquare) {
7307         int y, x;
7308         for (y = toY-1; y <= toY+1; y++) {
7309           for (x = toX-1; x <= toX+1; x++) {
7310             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7311                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7312               board[y][x] = EmptySquare;
7313             }
7314           }
7315         }
7316         board[toY][toX] = EmptySquare;
7317       }
7318     }
7319     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7320         /* [HGM] Shogi promotions */
7321         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7322     }
7323
7324     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7325                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7326         // [HGM] superchess: take promotion piece out of holdings
7327         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7328         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7329             if(!--board[k][BOARD_WIDTH-2])
7330                 board[k][BOARD_WIDTH-1] = EmptySquare;
7331         } else {
7332             if(!--board[BOARD_HEIGHT-1-k][1])
7333                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7334         }
7335     }
7336
7337 }
7338
7339 /* Updates forwardMostMove */
7340 void
7341 MakeMove(fromX, fromY, toX, toY, promoChar)
7342      int fromX, fromY, toX, toY;
7343      int promoChar;
7344 {
7345 //    forwardMostMove++; // [HGM] bare: moved downstream
7346
7347     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7348         int timeLeft; static int lastLoadFlag=0; int king, piece;
7349         piece = boards[forwardMostMove][fromY][fromX];
7350         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7351         if(gameInfo.variant == VariantKnightmate)
7352             king += (int) WhiteUnicorn - (int) WhiteKing;
7353         if(forwardMostMove == 0) {
7354             if(blackPlaysFirst) 
7355                 fprintf(serverMoves, "%s;", second.tidy);
7356             fprintf(serverMoves, "%s;", first.tidy);
7357             if(!blackPlaysFirst) 
7358                 fprintf(serverMoves, "%s;", second.tidy);
7359         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7360         lastLoadFlag = loadFlag;
7361         // print base move
7362         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7363         // print castling suffix
7364         if( toY == fromY && piece == king ) {
7365             if(toX-fromX > 1)
7366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7367             if(fromX-toX >1)
7368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7369         }
7370         // e.p. suffix
7371         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7372              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7373              boards[forwardMostMove][toY][toX] == EmptySquare
7374              && fromX != toX )
7375                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7376         // promotion suffix
7377         if(promoChar != NULLCHAR)
7378                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7379         if(!loadFlag) {
7380             fprintf(serverMoves, "/%d/%d",
7381                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7382             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7383             else                      timeLeft = blackTimeRemaining/1000;
7384             fprintf(serverMoves, "/%d", timeLeft);
7385         }
7386         fflush(serverMoves);
7387     }
7388
7389     if (forwardMostMove+1 >= MAX_MOVES) {
7390       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7391                         0, 1);
7392       return;
7393     }
7394     SwitchClocks();
7395     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7396     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7397     if (commentList[forwardMostMove+1] != NULL) {
7398         free(commentList[forwardMostMove+1]);
7399         commentList[forwardMostMove+1] = NULL;
7400     }
7401     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7402     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7403     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7404                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7405     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7406     gameInfo.result = GameUnfinished;
7407     if (gameInfo.resultDetails != NULL) {
7408         free(gameInfo.resultDetails);
7409         gameInfo.resultDetails = NULL;
7410     }
7411     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7412                               moveList[forwardMostMove - 1]);
7413     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7414                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7415                              fromY, fromX, toY, toX, promoChar,
7416                              parseList[forwardMostMove - 1]);
7417     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7418                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7419                             castlingRights[forwardMostMove]) ) {
7420       case MT_NONE:
7421       case MT_STALEMATE:
7422       default:
7423         break;
7424       case MT_CHECK:
7425         if(gameInfo.variant != VariantShogi)
7426             strcat(parseList[forwardMostMove - 1], "+");
7427         break;
7428       case MT_CHECKMATE:
7429       case MT_STAINMATE:
7430         strcat(parseList[forwardMostMove - 1], "#");
7431         break;
7432     }
7433     if (appData.debugMode) {
7434         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7435     }
7436
7437 }
7438
7439 /* Updates currentMove if not pausing */
7440 void
7441 ShowMove(fromX, fromY, toX, toY)
7442 {
7443     int instant = (gameMode == PlayFromGameFile) ?
7444         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7445     if(appData.noGUI) return;
7446     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7447         if (!instant) {
7448             if (forwardMostMove == currentMove + 1) {
7449                 AnimateMove(boards[forwardMostMove - 1],
7450                             fromX, fromY, toX, toY);
7451             }
7452             if (appData.highlightLastMove) {
7453                 SetHighlights(fromX, fromY, toX, toY);
7454             }
7455         }
7456         currentMove = forwardMostMove;
7457     }
7458
7459     if (instant) return;
7460
7461     DisplayMove(currentMove - 1);
7462     DrawPosition(FALSE, boards[currentMove]);
7463     DisplayBothClocks();
7464     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7465 }
7466
7467 void SendEgtPath(ChessProgramState *cps)
7468 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7469         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7470
7471         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7472
7473         while(*p) {
7474             char c, *q = name+1, *r, *s;
7475
7476             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7477             while(*p && *p != ',') *q++ = *p++;
7478             *q++ = ':'; *q = 0;
7479             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7480                 strcmp(name, ",nalimov:") == 0 ) {
7481                 // take nalimov path from the menu-changeable option first, if it is defined
7482                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7483                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7484             } else
7485             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7486                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7487                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7488                 s = r = StrStr(s, ":") + 1; // beginning of path info
7489                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7490                 c = *r; *r = 0;             // temporarily null-terminate path info
7491                     *--q = 0;               // strip of trailig ':' from name
7492                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7493                 *r = c;
7494                 SendToProgram(buf,cps);     // send egtbpath command for this format
7495             }
7496             if(*p == ',') p++; // read away comma to position for next format name
7497         }
7498 }
7499
7500 void
7501 InitChessProgram(cps, setup)
7502      ChessProgramState *cps;
7503      int setup; /* [HGM] needed to setup FRC opening position */
7504 {
7505     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7506     if (appData.noChessProgram) return;
7507     hintRequested = FALSE;
7508     bookRequested = FALSE;
7509
7510     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7511     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7512     if(cps->memSize) { /* [HGM] memory */
7513         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7514         SendToProgram(buf, cps);
7515     }
7516     SendEgtPath(cps); /* [HGM] EGT */
7517     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7518         sprintf(buf, "cores %d\n", appData.smpCores);
7519         SendToProgram(buf, cps);
7520     }
7521
7522     SendToProgram(cps->initString, cps);
7523     if (gameInfo.variant != VariantNormal &&
7524         gameInfo.variant != VariantLoadable
7525         /* [HGM] also send variant if board size non-standard */
7526         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7527                                             ) {
7528       char *v = VariantName(gameInfo.variant);
7529       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7530         /* [HGM] in protocol 1 we have to assume all variants valid */
7531         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7532         DisplayFatalError(buf, 0, 1);
7533         return;
7534       }
7535
7536       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7537       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7538       if( gameInfo.variant == VariantXiangqi )
7539            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7540       if( gameInfo.variant == VariantShogi )
7541            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7542       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7543            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7544       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7545                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7546            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7547       if( gameInfo.variant == VariantCourier )
7548            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7549       if( gameInfo.variant == VariantSuper )
7550            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7551       if( gameInfo.variant == VariantGreat )
7552            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7553
7554       if(overruled) {
7555            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7556                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7557            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7558            if(StrStr(cps->variants, b) == NULL) { 
7559                // specific sized variant not known, check if general sizing allowed
7560                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7561                    if(StrStr(cps->variants, "boardsize") == NULL) {
7562                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7563                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7564                        DisplayFatalError(buf, 0, 1);
7565                        return;
7566                    }
7567                    /* [HGM] here we really should compare with the maximum supported board size */
7568                }
7569            }
7570       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7571       sprintf(buf, "variant %s\n", b);
7572       SendToProgram(buf, cps);
7573     }
7574     currentlyInitializedVariant = gameInfo.variant;
7575
7576     /* [HGM] send opening position in FRC to first engine */
7577     if(setup) {
7578           SendToProgram("force\n", cps);
7579           SendBoard(cps, 0);
7580           /* engine is now in force mode! Set flag to wake it up after first move. */
7581           setboardSpoiledMachineBlack = 1;
7582     }
7583
7584     if (cps->sendICS) {
7585       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7586       SendToProgram(buf, cps);
7587     }
7588     cps->maybeThinking = FALSE;
7589     cps->offeredDraw = 0;
7590     if (!appData.icsActive) {
7591         SendTimeControl(cps, movesPerSession, timeControl,
7592                         timeIncrement, appData.searchDepth,
7593                         searchTime);
7594     }
7595     if (appData.showThinking 
7596         // [HGM] thinking: four options require thinking output to be sent
7597         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7598                                 ) {
7599         SendToProgram("post\n", cps);
7600     }
7601     SendToProgram("hard\n", cps);
7602     if (!appData.ponderNextMove) {
7603         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7604            it without being sure what state we are in first.  "hard"
7605            is not a toggle, so that one is OK.
7606          */
7607         SendToProgram("easy\n", cps);
7608     }
7609     if (cps->usePing) {
7610       sprintf(buf, "ping %d\n", ++cps->lastPing);
7611       SendToProgram(buf, cps);
7612     }
7613     cps->initDone = TRUE;
7614 }   
7615
7616
7617 void
7618 StartChessProgram(cps)
7619      ChessProgramState *cps;
7620 {
7621     char buf[MSG_SIZ];
7622     int err;
7623
7624     if (appData.noChessProgram) return;
7625     cps->initDone = FALSE;
7626
7627     if (strcmp(cps->host, "localhost") == 0) {
7628         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7629     } else if (*appData.remoteShell == NULLCHAR) {
7630         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7631     } else {
7632         if (*appData.remoteUser == NULLCHAR) {
7633           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7634                     cps->program);
7635         } else {
7636           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7637                     cps->host, appData.remoteUser, cps->program);
7638         }
7639         err = StartChildProcess(buf, "", &cps->pr);
7640     }
7641     
7642     if (err != 0) {
7643         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7644         DisplayFatalError(buf, err, 1);
7645         cps->pr = NoProc;
7646         cps->isr = NULL;
7647         return;
7648     }
7649     
7650     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7651     if (cps->protocolVersion > 1) {
7652       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7653       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7654       cps->comboCnt = 0;  //                and values of combo boxes
7655       SendToProgram(buf, cps);
7656     } else {
7657       SendToProgram("xboard\n", cps);
7658     }
7659 }
7660
7661
7662 void
7663 TwoMachinesEventIfReady P((void))
7664 {
7665   if (first.lastPing != first.lastPong) {
7666     DisplayMessage("", _("Waiting for first chess program"));
7667     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7668     return;
7669   }
7670   if (second.lastPing != second.lastPong) {
7671     DisplayMessage("", _("Waiting for second chess program"));
7672     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7673     return;
7674   }
7675   ThawUI();
7676   TwoMachinesEvent();
7677 }
7678
7679 void
7680 NextMatchGame P((void))
7681 {
7682     int index; /* [HGM] autoinc: step lod index during match */
7683     Reset(FALSE, TRUE);
7684     if (*appData.loadGameFile != NULLCHAR) {
7685         index = appData.loadGameIndex;
7686         if(index < 0) { // [HGM] autoinc
7687             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7688             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7689         } 
7690         LoadGameFromFile(appData.loadGameFile,
7691                          index,
7692                          appData.loadGameFile, FALSE);
7693     } else if (*appData.loadPositionFile != NULLCHAR) {
7694         index = appData.loadPositionIndex;
7695         if(index < 0) { // [HGM] autoinc
7696             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7697             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7698         } 
7699         LoadPositionFromFile(appData.loadPositionFile,
7700                              index,
7701                              appData.loadPositionFile);
7702     }
7703     TwoMachinesEventIfReady();
7704 }
7705
7706 void UserAdjudicationEvent( int result )
7707 {
7708     ChessMove gameResult = GameIsDrawn;
7709
7710     if( result > 0 ) {
7711         gameResult = WhiteWins;
7712     }
7713     else if( result < 0 ) {
7714         gameResult = BlackWins;
7715     }
7716
7717     if( gameMode == TwoMachinesPlay ) {
7718         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7719     }
7720 }
7721
7722
7723 // [HGM] save: calculate checksum of game to make games easily identifiable
7724 int StringCheckSum(char *s)
7725 {
7726         int i = 0;
7727         if(s==NULL) return 0;
7728         while(*s) i = i*259 + *s++;
7729         return i;
7730 }
7731
7732 int GameCheckSum()
7733 {
7734         int i, sum=0;
7735         for(i=backwardMostMove; i<forwardMostMove; i++) {
7736                 sum += pvInfoList[i].depth;
7737                 sum += StringCheckSum(parseList[i]);
7738                 sum += StringCheckSum(commentList[i]);
7739                 sum *= 261;
7740         }
7741         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7742         return sum + StringCheckSum(commentList[i]);
7743 } // end of save patch
7744
7745 void
7746 GameEnds(result, resultDetails, whosays)
7747      ChessMove result;
7748      char *resultDetails;
7749      int whosays;
7750 {
7751     GameMode nextGameMode;
7752     int isIcsGame;
7753     char buf[MSG_SIZ];
7754
7755     if(endingGame) return; /* [HGM] crash: forbid recursion */
7756     endingGame = 1;
7757
7758     if (appData.debugMode) {
7759       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7760               result, resultDetails ? resultDetails : "(null)", whosays);
7761     }
7762
7763     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7764         /* If we are playing on ICS, the server decides when the
7765            game is over, but the engine can offer to draw, claim 
7766            a draw, or resign. 
7767          */
7768 #if ZIPPY
7769         if (appData.zippyPlay && first.initDone) {
7770             if (result == GameIsDrawn) {
7771                 /* In case draw still needs to be claimed */
7772                 SendToICS(ics_prefix);
7773                 SendToICS("draw\n");
7774             } else if (StrCaseStr(resultDetails, "resign")) {
7775                 SendToICS(ics_prefix);
7776                 SendToICS("resign\n");
7777             }
7778         }
7779 #endif
7780         endingGame = 0; /* [HGM] crash */
7781         return;
7782     }
7783
7784     /* If we're loading the game from a file, stop */
7785     if (whosays == GE_FILE) {
7786       (void) StopLoadGameTimer();
7787       gameFileFP = NULL;
7788     }
7789
7790     /* Cancel draw offers */
7791     first.offeredDraw = second.offeredDraw = 0;
7792
7793     /* If this is an ICS game, only ICS can really say it's done;
7794        if not, anyone can. */
7795     isIcsGame = (gameMode == IcsPlayingWhite || 
7796                  gameMode == IcsPlayingBlack || 
7797                  gameMode == IcsObserving    || 
7798                  gameMode == IcsExamining);
7799
7800     if (!isIcsGame || whosays == GE_ICS) {
7801         /* OK -- not an ICS game, or ICS said it was done */
7802         StopClocks();
7803         if (!isIcsGame && !appData.noChessProgram) 
7804           SetUserThinkingEnables();
7805     
7806         /* [HGM] if a machine claims the game end we verify this claim */
7807         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7808             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7809                 char claimer;
7810                 ChessMove trueResult = (ChessMove) -1;
7811
7812                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7813                                             first.twoMachinesColor[0] :
7814                                             second.twoMachinesColor[0] ;
7815
7816                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7817                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7818                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7819                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7820                 } else
7821                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7822                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7823                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7824                 } else
7825                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7826                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7827                 }
7828
7829                 // now verify win claims, but not in drop games, as we don't understand those yet
7830                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7831                                                  || gameInfo.variant == VariantGreat) &&
7832                     (result == WhiteWins && claimer == 'w' ||
7833                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7834                       if (appData.debugMode) {
7835                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7836                                 result, epStatus[forwardMostMove], forwardMostMove);
7837                       }
7838                       if(result != trueResult) {
7839                               sprintf(buf, "False win claim: '%s'", resultDetails);
7840                               result = claimer == 'w' ? BlackWins : WhiteWins;
7841                               resultDetails = buf;
7842                       }
7843                 } else
7844                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7845                     && (forwardMostMove <= backwardMostMove ||
7846                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7847                         (claimer=='b')==(forwardMostMove&1))
7848                                                                                   ) {
7849                       /* [HGM] verify: draws that were not flagged are false claims */
7850                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7851                       result = claimer == 'w' ? BlackWins : WhiteWins;
7852                       resultDetails = buf;
7853                 }
7854                 /* (Claiming a loss is accepted no questions asked!) */
7855             }
7856             /* [HGM] bare: don't allow bare King to win */
7857             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7858                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7859                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7860                && result != GameIsDrawn)
7861             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7862                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7863                         int p = (int)boards[forwardMostMove][i][j] - color;
7864                         if(p >= 0 && p <= (int)WhiteKing) k++;
7865                 }
7866                 if (appData.debugMode) {
7867                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7868                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7869                 }
7870                 if(k <= 1) {
7871                         result = GameIsDrawn;
7872                         sprintf(buf, "%s but bare king", resultDetails);
7873                         resultDetails = buf;
7874                 }
7875             }
7876         }
7877
7878
7879         if(serverMoves != NULL && !loadFlag) { char c = '=';
7880             if(result==WhiteWins) c = '+';
7881             if(result==BlackWins) c = '-';
7882             if(resultDetails != NULL)
7883                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7884         }
7885         if (resultDetails != NULL) {
7886             gameInfo.result = result;
7887             gameInfo.resultDetails = StrSave(resultDetails);
7888
7889             /* display last move only if game was not loaded from file */
7890             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7891                 DisplayMove(currentMove - 1);
7892     
7893             if (forwardMostMove != 0) {
7894                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7895                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7896                                                                 ) {
7897                     if (*appData.saveGameFile != NULLCHAR) {
7898                         SaveGameToFile(appData.saveGameFile, TRUE);
7899                     } else if (appData.autoSaveGames) {
7900                         AutoSaveGame();
7901                     }
7902                     if (*appData.savePositionFile != NULLCHAR) {
7903                         SavePositionToFile(appData.savePositionFile);
7904                     }
7905                 }
7906             }
7907
7908             /* Tell program how game ended in case it is learning */
7909             /* [HGM] Moved this to after saving the PGN, just in case */
7910             /* engine died and we got here through time loss. In that */
7911             /* case we will get a fatal error writing the pipe, which */
7912             /* would otherwise lose us the PGN.                       */
7913             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7914             /* output during GameEnds should never be fatal anymore   */
7915             if (gameMode == MachinePlaysWhite ||
7916                 gameMode == MachinePlaysBlack ||
7917                 gameMode == TwoMachinesPlay ||
7918                 gameMode == IcsPlayingWhite ||
7919                 gameMode == IcsPlayingBlack ||
7920                 gameMode == BeginningOfGame) {
7921                 char buf[MSG_SIZ];
7922                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7923                         resultDetails);
7924                 if (first.pr != NoProc) {
7925                     SendToProgram(buf, &first);
7926                 }
7927                 if (second.pr != NoProc &&
7928                     gameMode == TwoMachinesPlay) {
7929                     SendToProgram(buf, &second);
7930                 }
7931             }
7932         }
7933
7934         if (appData.icsActive) {
7935             if (appData.quietPlay &&
7936                 (gameMode == IcsPlayingWhite ||
7937                  gameMode == IcsPlayingBlack)) {
7938                 SendToICS(ics_prefix);
7939                 SendToICS("set shout 1\n");
7940             }
7941             nextGameMode = IcsIdle;
7942             ics_user_moved = FALSE;
7943             /* clean up premove.  It's ugly when the game has ended and the
7944              * premove highlights are still on the board.
7945              */
7946             if (gotPremove) {
7947               gotPremove = FALSE;
7948               ClearPremoveHighlights();
7949               DrawPosition(FALSE, boards[currentMove]);
7950             }
7951             if (whosays == GE_ICS) {
7952                 switch (result) {
7953                 case WhiteWins:
7954                     if (gameMode == IcsPlayingWhite)
7955                         PlayIcsWinSound();
7956                     else if(gameMode == IcsPlayingBlack)
7957                         PlayIcsLossSound();
7958                     break;
7959                 case BlackWins:
7960                     if (gameMode == IcsPlayingBlack)
7961                         PlayIcsWinSound();
7962                     else if(gameMode == IcsPlayingWhite)
7963                         PlayIcsLossSound();
7964                     break;
7965                 case GameIsDrawn:
7966                     PlayIcsDrawSound();
7967                     break;
7968                 default:
7969                     PlayIcsUnfinishedSound();
7970                 }
7971             }
7972         } else if (gameMode == EditGame ||
7973                    gameMode == PlayFromGameFile || 
7974                    gameMode == AnalyzeMode || 
7975                    gameMode == AnalyzeFile) {
7976             nextGameMode = gameMode;
7977         } else {
7978             nextGameMode = EndOfGame;
7979         }
7980         pausing = FALSE;
7981         ModeHighlight();
7982     } else {
7983         nextGameMode = gameMode;
7984     }
7985
7986     if (appData.noChessProgram) {
7987         gameMode = nextGameMode;
7988         ModeHighlight();
7989         endingGame = 0; /* [HGM] crash */
7990         return;
7991     }
7992
7993     if (first.reuse) {
7994         /* Put first chess program into idle state */
7995         if (first.pr != NoProc &&
7996             (gameMode == MachinePlaysWhite ||
7997              gameMode == MachinePlaysBlack ||
7998              gameMode == TwoMachinesPlay ||
7999              gameMode == IcsPlayingWhite ||
8000              gameMode == IcsPlayingBlack ||
8001              gameMode == BeginningOfGame)) {
8002             SendToProgram("force\n", &first);
8003             if (first.usePing) {
8004               char buf[MSG_SIZ];
8005               sprintf(buf, "ping %d\n", ++first.lastPing);
8006               SendToProgram(buf, &first);
8007             }
8008         }
8009     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8010         /* Kill off first chess program */
8011         if (first.isr != NULL)
8012           RemoveInputSource(first.isr);
8013         first.isr = NULL;
8014     
8015         if (first.pr != NoProc) {
8016             ExitAnalyzeMode();
8017             DoSleep( appData.delayBeforeQuit );
8018             SendToProgram("quit\n", &first);
8019             DoSleep( appData.delayAfterQuit );
8020             DestroyChildProcess(first.pr, first.useSigterm);
8021         }
8022         first.pr = NoProc;
8023     }
8024     if (second.reuse) {
8025         /* Put second chess program into idle state */
8026         if (second.pr != NoProc &&
8027             gameMode == TwoMachinesPlay) {
8028             SendToProgram("force\n", &second);
8029             if (second.usePing) {
8030               char buf[MSG_SIZ];
8031               sprintf(buf, "ping %d\n", ++second.lastPing);
8032               SendToProgram(buf, &second);
8033             }
8034         }
8035     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8036         /* Kill off second chess program */
8037         if (second.isr != NULL)
8038           RemoveInputSource(second.isr);
8039         second.isr = NULL;
8040     
8041         if (second.pr != NoProc) {
8042             DoSleep( appData.delayBeforeQuit );
8043             SendToProgram("quit\n", &second);
8044             DoSleep( appData.delayAfterQuit );
8045             DestroyChildProcess(second.pr, second.useSigterm);
8046         }
8047         second.pr = NoProc;
8048     }
8049
8050     if (matchMode && gameMode == TwoMachinesPlay) {
8051         switch (result) {
8052         case WhiteWins:
8053           if (first.twoMachinesColor[0] == 'w') {
8054             first.matchWins++;
8055           } else {
8056             second.matchWins++;
8057           }
8058           break;
8059         case BlackWins:
8060           if (first.twoMachinesColor[0] == 'b') {
8061             first.matchWins++;
8062           } else {
8063             second.matchWins++;
8064           }
8065           break;
8066         default:
8067           break;
8068         }
8069         if (matchGame < appData.matchGames) {
8070             char *tmp;
8071             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8072                 tmp = first.twoMachinesColor;
8073                 first.twoMachinesColor = second.twoMachinesColor;
8074                 second.twoMachinesColor = tmp;
8075             }
8076             gameMode = nextGameMode;
8077             matchGame++;
8078             if(appData.matchPause>10000 || appData.matchPause<10)
8079                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8080             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8081             endingGame = 0; /* [HGM] crash */
8082             return;
8083         } else {
8084             char buf[MSG_SIZ];
8085             gameMode = nextGameMode;
8086             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8087                     first.tidy, second.tidy,
8088                     first.matchWins, second.matchWins,
8089                     appData.matchGames - (first.matchWins + second.matchWins));
8090             DisplayFatalError(buf, 0, 0);
8091         }
8092     }
8093     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8094         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8095       ExitAnalyzeMode();
8096     gameMode = nextGameMode;
8097     ModeHighlight();
8098     endingGame = 0;  /* [HGM] crash */
8099 }
8100
8101 /* Assumes program was just initialized (initString sent).
8102    Leaves program in force mode. */
8103 void
8104 FeedMovesToProgram(cps, upto) 
8105      ChessProgramState *cps;
8106      int upto;
8107 {
8108     int i;
8109     
8110     if (appData.debugMode)
8111       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8112               startedFromSetupPosition ? "position and " : "",
8113               backwardMostMove, upto, cps->which);
8114     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8115         // [HGM] variantswitch: make engine aware of new variant
8116         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8117                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8118         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8119         SendToProgram(buf, cps);
8120         currentlyInitializedVariant = gameInfo.variant;
8121     }
8122     SendToProgram("force\n", cps);
8123     if (startedFromSetupPosition) {
8124         SendBoard(cps, backwardMostMove);
8125     if (appData.debugMode) {
8126         fprintf(debugFP, "feedMoves\n");
8127     }
8128     }
8129     for (i = backwardMostMove; i < upto; i++) {
8130         SendMoveToProgram(i, cps);
8131     }
8132 }
8133
8134
8135 void
8136 ResurrectChessProgram()
8137 {
8138      /* The chess program may have exited.
8139         If so, restart it and feed it all the moves made so far. */
8140
8141     if (appData.noChessProgram || first.pr != NoProc) return;
8142     
8143     StartChessProgram(&first);
8144     InitChessProgram(&first, FALSE);
8145     FeedMovesToProgram(&first, currentMove);
8146
8147     if (!first.sendTime) {
8148         /* can't tell gnuchess what its clock should read,
8149            so we bow to its notion. */
8150         ResetClocks();
8151         timeRemaining[0][currentMove] = whiteTimeRemaining;
8152         timeRemaining[1][currentMove] = blackTimeRemaining;
8153     }
8154
8155     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8156                 appData.icsEngineAnalyze) && first.analysisSupport) {
8157       SendToProgram("analyze\n", &first);
8158       first.analyzing = TRUE;
8159     }
8160 }
8161
8162 /*
8163  * Button procedures
8164  */
8165 void
8166 Reset(redraw, init)
8167      int redraw, init;
8168 {
8169     int i;
8170
8171     if (appData.debugMode) {
8172         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8173                 redraw, init, gameMode);
8174     }
8175     pausing = pauseExamInvalid = FALSE;
8176     startedFromSetupPosition = blackPlaysFirst = FALSE;
8177     firstMove = TRUE;
8178     whiteFlag = blackFlag = FALSE;
8179     userOfferedDraw = FALSE;
8180     hintRequested = bookRequested = FALSE;
8181     first.maybeThinking = FALSE;
8182     second.maybeThinking = FALSE;
8183     first.bookSuspend = FALSE; // [HGM] book
8184     second.bookSuspend = FALSE;
8185     thinkOutput[0] = NULLCHAR;
8186     lastHint[0] = NULLCHAR;
8187     ClearGameInfo(&gameInfo);
8188     gameInfo.variant = StringToVariant(appData.variant);
8189     ics_user_moved = ics_clock_paused = FALSE;
8190     ics_getting_history = H_FALSE;
8191     ics_gamenum = -1;
8192     white_holding[0] = black_holding[0] = NULLCHAR;
8193     ClearProgramStats();
8194     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8195     
8196     ResetFrontEnd();
8197     ClearHighlights();
8198     flipView = appData.flipView;
8199     ClearPremoveHighlights();
8200     gotPremove = FALSE;
8201     alarmSounded = FALSE;
8202
8203     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8204     if(appData.serverMovesName != NULL) {
8205         /* [HGM] prepare to make moves file for broadcasting */
8206         clock_t t = clock();
8207         if(serverMoves != NULL) fclose(serverMoves);
8208         serverMoves = fopen(appData.serverMovesName, "r");
8209         if(serverMoves != NULL) {
8210             fclose(serverMoves);
8211             /* delay 15 sec before overwriting, so all clients can see end */
8212             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8213         }
8214         serverMoves = fopen(appData.serverMovesName, "w");
8215     }
8216
8217     ExitAnalyzeMode();
8218     gameMode = BeginningOfGame;
8219     ModeHighlight();
8220     if(appData.icsActive) gameInfo.variant = VariantNormal;
8221     InitPosition(redraw);
8222     for (i = 0; i < MAX_MOVES; i++) {
8223         if (commentList[i] != NULL) {
8224             free(commentList[i]);
8225             commentList[i] = NULL;
8226         }
8227     }
8228     ResetClocks();
8229     timeRemaining[0][0] = whiteTimeRemaining;
8230     timeRemaining[1][0] = blackTimeRemaining;
8231     if (first.pr == NULL) {
8232         StartChessProgram(&first);
8233     }
8234     if (init) {
8235             InitChessProgram(&first, startedFromSetupPosition);
8236     }
8237     DisplayTitle("");
8238     DisplayMessage("", "");
8239     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8240     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8241 }
8242
8243 void
8244 AutoPlayGameLoop()
8245 {
8246     for (;;) {
8247         if (!AutoPlayOneMove())
8248           return;
8249         if (matchMode || appData.timeDelay == 0)
8250           continue;
8251         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8252           return;
8253         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8254         break;
8255     }
8256 }
8257
8258
8259 int
8260 AutoPlayOneMove()
8261 {
8262     int fromX, fromY, toX, toY;
8263
8264     if (appData.debugMode) {
8265       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8266     }
8267
8268     if (gameMode != PlayFromGameFile)
8269       return FALSE;
8270
8271     if (currentMove >= forwardMostMove) {
8272       gameMode = EditGame;
8273       ModeHighlight();
8274
8275       /* [AS] Clear current move marker at the end of a game */
8276       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8277
8278       return FALSE;
8279     }
8280     
8281     toX = moveList[currentMove][2] - AAA;
8282     toY = moveList[currentMove][3] - ONE;
8283
8284     if (moveList[currentMove][1] == '@') {
8285         if (appData.highlightLastMove) {
8286             SetHighlights(-1, -1, toX, toY);
8287         }
8288     } else {
8289         fromX = moveList[currentMove][0] - AAA;
8290         fromY = moveList[currentMove][1] - ONE;
8291
8292         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8293
8294         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8295
8296         if (appData.highlightLastMove) {
8297             SetHighlights(fromX, fromY, toX, toY);
8298         }
8299     }
8300     DisplayMove(currentMove);
8301     SendMoveToProgram(currentMove++, &first);
8302     DisplayBothClocks();
8303     DrawPosition(FALSE, boards[currentMove]);
8304     // [HGM] PV info: always display, routine tests if empty
8305     DisplayComment(currentMove - 1, commentList[currentMove]);
8306     return TRUE;
8307 }
8308
8309
8310 int
8311 LoadGameOneMove(readAhead)
8312      ChessMove readAhead;
8313 {
8314     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8315     char promoChar = NULLCHAR;
8316     ChessMove moveType;
8317     char move[MSG_SIZ];
8318     char *p, *q;
8319     
8320     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8321         gameMode != AnalyzeMode && gameMode != Training) {
8322         gameFileFP = NULL;
8323         return FALSE;
8324     }
8325     
8326     yyboardindex = forwardMostMove;
8327     if (readAhead != (ChessMove)0) {
8328       moveType = readAhead;
8329     } else {
8330       if (gameFileFP == NULL)
8331           return FALSE;
8332       moveType = (ChessMove) yylex();
8333     }
8334     
8335     done = FALSE;
8336     switch (moveType) {
8337       case Comment:
8338         if (appData.debugMode) 
8339           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8340         p = yy_text;
8341         if (*p == '{' || *p == '[' || *p == '(') {
8342             p[strlen(p) - 1] = NULLCHAR;
8343             p++;
8344         }
8345
8346         /* append the comment but don't display it */
8347         while (*p == '\n') p++;
8348         AppendComment(currentMove, p);
8349         return TRUE;
8350
8351       case WhiteCapturesEnPassant:
8352       case BlackCapturesEnPassant:
8353       case WhitePromotionChancellor:
8354       case BlackPromotionChancellor:
8355       case WhitePromotionArchbishop:
8356       case BlackPromotionArchbishop:
8357       case WhitePromotionCentaur:
8358       case BlackPromotionCentaur:
8359       case WhitePromotionQueen:
8360       case BlackPromotionQueen:
8361       case WhitePromotionRook:
8362       case BlackPromotionRook:
8363       case WhitePromotionBishop:
8364       case BlackPromotionBishop:
8365       case WhitePromotionKnight:
8366       case BlackPromotionKnight:
8367       case WhitePromotionKing:
8368       case BlackPromotionKing:
8369       case NormalMove:
8370       case WhiteKingSideCastle:
8371       case WhiteQueenSideCastle:
8372       case BlackKingSideCastle:
8373       case BlackQueenSideCastle:
8374       case WhiteKingSideCastleWild:
8375       case WhiteQueenSideCastleWild:
8376       case BlackKingSideCastleWild:
8377       case BlackQueenSideCastleWild:
8378       /* PUSH Fabien */
8379       case WhiteHSideCastleFR:
8380       case WhiteASideCastleFR:
8381       case BlackHSideCastleFR:
8382       case BlackASideCastleFR:
8383       /* POP Fabien */
8384         if (appData.debugMode)
8385           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8386         fromX = currentMoveString[0] - AAA;
8387         fromY = currentMoveString[1] - ONE;
8388         toX = currentMoveString[2] - AAA;
8389         toY = currentMoveString[3] - ONE;
8390         promoChar = currentMoveString[4];
8391         break;
8392
8393       case WhiteDrop:
8394       case BlackDrop:
8395         if (appData.debugMode)
8396           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8397         fromX = moveType == WhiteDrop ?
8398           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8399         (int) CharToPiece(ToLower(currentMoveString[0]));
8400         fromY = DROP_RANK;
8401         toX = currentMoveString[2] - AAA;
8402         toY = currentMoveString[3] - ONE;
8403         break;
8404
8405       case WhiteWins:
8406       case BlackWins:
8407       case GameIsDrawn:
8408       case GameUnfinished:
8409         if (appData.debugMode)
8410           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8411         p = strchr(yy_text, '{');
8412         if (p == NULL) p = strchr(yy_text, '(');
8413         if (p == NULL) {
8414             p = yy_text;
8415             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8416         } else {
8417             q = strchr(p, *p == '{' ? '}' : ')');
8418             if (q != NULL) *q = NULLCHAR;
8419             p++;
8420         }
8421         GameEnds(moveType, p, GE_FILE);
8422         done = TRUE;
8423         if (cmailMsgLoaded) {
8424             ClearHighlights();
8425             flipView = WhiteOnMove(currentMove);
8426             if (moveType == GameUnfinished) flipView = !flipView;
8427             if (appData.debugMode)
8428               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8429         }
8430         break;
8431
8432       case (ChessMove) 0:       /* end of file */
8433         if (appData.debugMode)
8434           fprintf(debugFP, "Parser hit end of file\n");
8435         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8436                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8437           case MT_NONE:
8438           case MT_CHECK:
8439             break;
8440           case MT_CHECKMATE:
8441           case MT_STAINMATE:
8442             if (WhiteOnMove(currentMove)) {
8443                 GameEnds(BlackWins, "Black mates", GE_FILE);
8444             } else {
8445                 GameEnds(WhiteWins, "White mates", GE_FILE);
8446             }
8447             break;
8448           case MT_STALEMATE:
8449             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8450             break;
8451         }
8452         done = TRUE;
8453         break;
8454
8455       case MoveNumberOne:
8456         if (lastLoadGameStart == GNUChessGame) {
8457             /* GNUChessGames have numbers, but they aren't move numbers */
8458             if (appData.debugMode)
8459               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8460                       yy_text, (int) moveType);
8461             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8462         }
8463         /* else fall thru */
8464
8465       case XBoardGame:
8466       case GNUChessGame:
8467       case PGNTag:
8468         /* Reached start of next game in file */
8469         if (appData.debugMode)
8470           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8471         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8472                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8473           case MT_NONE:
8474           case MT_CHECK:
8475             break;
8476           case MT_CHECKMATE:
8477           case MT_STAINMATE:
8478             if (WhiteOnMove(currentMove)) {
8479                 GameEnds(BlackWins, "Black mates", GE_FILE);
8480             } else {
8481                 GameEnds(WhiteWins, "White mates", GE_FILE);
8482             }
8483             break;
8484           case MT_STALEMATE:
8485             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8486             break;
8487         }
8488         done = TRUE;
8489         break;
8490
8491       case PositionDiagram:     /* should not happen; ignore */
8492       case ElapsedTime:         /* ignore */
8493       case NAG:                 /* ignore */
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8496                   yy_text, (int) moveType);
8497         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8498
8499       case IllegalMove:
8500         if (appData.testLegality) {
8501             if (appData.debugMode)
8502               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8503             sprintf(move, _("Illegal move: %d.%s%s"),
8504                     (forwardMostMove / 2) + 1,
8505                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8506             DisplayError(move, 0);
8507             done = TRUE;
8508         } else {
8509             if (appData.debugMode)
8510               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8511                       yy_text, currentMoveString);
8512             fromX = currentMoveString[0] - AAA;
8513             fromY = currentMoveString[1] - ONE;
8514             toX = currentMoveString[2] - AAA;
8515             toY = currentMoveString[3] - ONE;
8516             promoChar = currentMoveString[4];
8517         }
8518         break;
8519
8520       case AmbiguousMove:
8521         if (appData.debugMode)
8522           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8523         sprintf(move, _("Ambiguous move: %d.%s%s"),
8524                 (forwardMostMove / 2) + 1,
8525                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8526         DisplayError(move, 0);
8527         done = TRUE;
8528         break;
8529
8530       default:
8531       case ImpossibleMove:
8532         if (appData.debugMode)
8533           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8534         sprintf(move, _("Illegal move: %d.%s%s"),
8535                 (forwardMostMove / 2) + 1,
8536                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8537         DisplayError(move, 0);
8538         done = TRUE;
8539         break;
8540     }
8541
8542     if (done) {
8543         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8544             DrawPosition(FALSE, boards[currentMove]);
8545             DisplayBothClocks();
8546             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8547               DisplayComment(currentMove - 1, commentList[currentMove]);
8548         }
8549         (void) StopLoadGameTimer();
8550         gameFileFP = NULL;
8551         cmailOldMove = forwardMostMove;
8552         return FALSE;
8553     } else {
8554         /* currentMoveString is set as a side-effect of yylex */
8555         strcat(currentMoveString, "\n");
8556         strcpy(moveList[forwardMostMove], currentMoveString);
8557         
8558         thinkOutput[0] = NULLCHAR;
8559         MakeMove(fromX, fromY, toX, toY, promoChar);
8560         currentMove = forwardMostMove;
8561         return TRUE;
8562     }
8563 }
8564
8565 /* Load the nth game from the given file */
8566 int
8567 LoadGameFromFile(filename, n, title, useList)
8568      char *filename;
8569      int n;
8570      char *title;
8571      /*Boolean*/ int useList;
8572 {
8573     FILE *f;
8574     char buf[MSG_SIZ];
8575
8576     if (strcmp(filename, "-") == 0) {
8577         f = stdin;
8578         title = "stdin";
8579     } else {
8580         f = fopen(filename, "rb");
8581         if (f == NULL) {
8582           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8583             DisplayError(buf, errno);
8584             return FALSE;
8585         }
8586     }
8587     if (fseek(f, 0, 0) == -1) {
8588         /* f is not seekable; probably a pipe */
8589         useList = FALSE;
8590     }
8591     if (useList && n == 0) {
8592         int error = GameListBuild(f);
8593         if (error) {
8594             DisplayError(_("Cannot build game list"), error);
8595         } else if (!ListEmpty(&gameList) &&
8596                    ((ListGame *) gameList.tailPred)->number > 1) {
8597             GameListPopUp(f, title);
8598             return TRUE;
8599         }
8600         GameListDestroy();
8601         n = 1;
8602     }
8603     if (n == 0) n = 1;
8604     return LoadGame(f, n, title, FALSE);
8605 }
8606
8607
8608 void
8609 MakeRegisteredMove()
8610 {
8611     int fromX, fromY, toX, toY;
8612     char promoChar;
8613     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8614         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8615           case CMAIL_MOVE:
8616           case CMAIL_DRAW:
8617             if (appData.debugMode)
8618               fprintf(debugFP, "Restoring %s for game %d\n",
8619                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8620     
8621             thinkOutput[0] = NULLCHAR;
8622             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8623             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8624             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8625             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8626             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8627             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8628             MakeMove(fromX, fromY, toX, toY, promoChar);
8629             ShowMove(fromX, fromY, toX, toY);
8630               
8631             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8632                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8633               case MT_NONE:
8634               case MT_CHECK:
8635                 break;
8636                 
8637               case MT_CHECKMATE:
8638               case MT_STAINMATE:
8639                 if (WhiteOnMove(currentMove)) {
8640                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8641                 } else {
8642                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8643                 }
8644                 break;
8645                 
8646               case MT_STALEMATE:
8647                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8648                 break;
8649             }
8650
8651             break;
8652             
8653           case CMAIL_RESIGN:
8654             if (WhiteOnMove(currentMove)) {
8655                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8656             } else {
8657                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8658             }
8659             break;
8660             
8661           case CMAIL_ACCEPT:
8662             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8663             break;
8664               
8665           default:
8666             break;
8667         }
8668     }
8669
8670     return;
8671 }
8672
8673 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8674 int
8675 CmailLoadGame(f, gameNumber, title, useList)
8676      FILE *f;
8677      int gameNumber;
8678      char *title;
8679      int useList;
8680 {
8681     int retVal;
8682
8683     if (gameNumber > nCmailGames) {
8684         DisplayError(_("No more games in this message"), 0);
8685         return FALSE;
8686     }
8687     if (f == lastLoadGameFP) {
8688         int offset = gameNumber - lastLoadGameNumber;
8689         if (offset == 0) {
8690             cmailMsg[0] = NULLCHAR;
8691             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8692                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8693                 nCmailMovesRegistered--;
8694             }
8695             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8696             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8697                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8698             }
8699         } else {
8700             if (! RegisterMove()) return FALSE;
8701         }
8702     }
8703
8704     retVal = LoadGame(f, gameNumber, title, useList);
8705
8706     /* Make move registered during previous look at this game, if any */
8707     MakeRegisteredMove();
8708
8709     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8710         commentList[currentMove]
8711           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8712         DisplayComment(currentMove - 1, commentList[currentMove]);
8713     }
8714
8715     return retVal;
8716 }
8717
8718 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8719 int
8720 ReloadGame(offset)
8721      int offset;
8722 {
8723     int gameNumber = lastLoadGameNumber + offset;
8724     if (lastLoadGameFP == NULL) {
8725         DisplayError(_("No game has been loaded yet"), 0);
8726         return FALSE;
8727     }
8728     if (gameNumber <= 0) {
8729         DisplayError(_("Can't back up any further"), 0);
8730         return FALSE;
8731     }
8732     if (cmailMsgLoaded) {
8733         return CmailLoadGame(lastLoadGameFP, gameNumber,
8734                              lastLoadGameTitle, lastLoadGameUseList);
8735     } else {
8736         return LoadGame(lastLoadGameFP, gameNumber,
8737                         lastLoadGameTitle, lastLoadGameUseList);
8738     }
8739 }
8740
8741
8742
8743 /* Load the nth game from open file f */
8744 int
8745 LoadGame(f, gameNumber, title, useList)
8746      FILE *f;
8747      int gameNumber;
8748      char *title;
8749      int useList;
8750 {
8751     ChessMove cm;
8752     char buf[MSG_SIZ];
8753     int gn = gameNumber;
8754     ListGame *lg = NULL;
8755     int numPGNTags = 0;
8756     int err;
8757     GameMode oldGameMode;
8758     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8759
8760     if (appData.debugMode) 
8761         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8762
8763     if (gameMode == Training )
8764         SetTrainingModeOff();
8765
8766     oldGameMode = gameMode;
8767     if (gameMode != BeginningOfGame) {
8768       Reset(FALSE, TRUE);
8769     }
8770
8771     gameFileFP = f;
8772     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8773         fclose(lastLoadGameFP);
8774     }
8775
8776     if (useList) {
8777         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8778         
8779         if (lg) {
8780             fseek(f, lg->offset, 0);
8781             GameListHighlight(gameNumber);
8782             gn = 1;
8783         }
8784         else {
8785             DisplayError(_("Game number out of range"), 0);
8786             return FALSE;
8787         }
8788     } else {
8789         GameListDestroy();
8790         if (fseek(f, 0, 0) == -1) {
8791             if (f == lastLoadGameFP ?
8792                 gameNumber == lastLoadGameNumber + 1 :
8793                 gameNumber == 1) {
8794                 gn = 1;
8795             } else {
8796                 DisplayError(_("Can't seek on game file"), 0);
8797                 return FALSE;
8798             }
8799         }
8800     }
8801     lastLoadGameFP = f;
8802     lastLoadGameNumber = gameNumber;
8803     strcpy(lastLoadGameTitle, title);
8804     lastLoadGameUseList = useList;
8805
8806     yynewfile(f);
8807
8808     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8809       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8810                 lg->gameInfo.black);
8811             DisplayTitle(buf);
8812     } else if (*title != NULLCHAR) {
8813         if (gameNumber > 1) {
8814             sprintf(buf, "%s %d", title, gameNumber);
8815             DisplayTitle(buf);
8816         } else {
8817             DisplayTitle(title);
8818         }
8819     }
8820
8821     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8822         gameMode = PlayFromGameFile;
8823         ModeHighlight();
8824     }
8825
8826     currentMove = forwardMostMove = backwardMostMove = 0;
8827     CopyBoard(boards[0], initialPosition);
8828     StopClocks();
8829
8830     /*
8831      * Skip the first gn-1 games in the file.
8832      * Also skip over anything that precedes an identifiable 
8833      * start of game marker, to avoid being confused by 
8834      * garbage at the start of the file.  Currently 
8835      * recognized start of game markers are the move number "1",
8836      * the pattern "gnuchess .* game", the pattern
8837      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8838      * A game that starts with one of the latter two patterns
8839      * will also have a move number 1, possibly
8840      * following a position diagram.
8841      * 5-4-02: Let's try being more lenient and allowing a game to
8842      * start with an unnumbered move.  Does that break anything?
8843      */
8844     cm = lastLoadGameStart = (ChessMove) 0;
8845     while (gn > 0) {
8846         yyboardindex = forwardMostMove;
8847         cm = (ChessMove) yylex();
8848         switch (cm) {
8849           case (ChessMove) 0:
8850             if (cmailMsgLoaded) {
8851                 nCmailGames = CMAIL_MAX_GAMES - gn;
8852             } else {
8853                 Reset(TRUE, TRUE);
8854                 DisplayError(_("Game not found in file"), 0);
8855             }
8856             return FALSE;
8857
8858           case GNUChessGame:
8859           case XBoardGame:
8860             gn--;
8861             lastLoadGameStart = cm;
8862             break;
8863             
8864           case MoveNumberOne:
8865             switch (lastLoadGameStart) {
8866               case GNUChessGame:
8867               case XBoardGame:
8868               case PGNTag:
8869                 break;
8870               case MoveNumberOne:
8871               case (ChessMove) 0:
8872                 gn--;           /* count this game */
8873                 lastLoadGameStart = cm;
8874                 break;
8875               default:
8876                 /* impossible */
8877                 break;
8878             }
8879             break;
8880
8881           case PGNTag:
8882             switch (lastLoadGameStart) {
8883               case GNUChessGame:
8884               case PGNTag:
8885               case MoveNumberOne:
8886               case (ChessMove) 0:
8887                 gn--;           /* count this game */
8888                 lastLoadGameStart = cm;
8889                 break;
8890               case XBoardGame:
8891                 lastLoadGameStart = cm; /* game counted already */
8892                 break;
8893               default:
8894                 /* impossible */
8895                 break;
8896             }
8897             if (gn > 0) {
8898                 do {
8899                     yyboardindex = forwardMostMove;
8900                     cm = (ChessMove) yylex();
8901                 } while (cm == PGNTag || cm == Comment);
8902             }
8903             break;
8904
8905           case WhiteWins:
8906           case BlackWins:
8907           case GameIsDrawn:
8908             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8909                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8910                     != CMAIL_OLD_RESULT) {
8911                     nCmailResults ++ ;
8912                     cmailResult[  CMAIL_MAX_GAMES
8913                                 - gn - 1] = CMAIL_OLD_RESULT;
8914                 }
8915             }
8916             break;
8917
8918           case NormalMove:
8919             /* Only a NormalMove can be at the start of a game
8920              * without a position diagram. */
8921             if (lastLoadGameStart == (ChessMove) 0) {
8922               gn--;
8923               lastLoadGameStart = MoveNumberOne;
8924             }
8925             break;
8926
8927           default:
8928             break;
8929         }
8930     }
8931     
8932     if (appData.debugMode)
8933       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8934
8935     if (cm == XBoardGame) {
8936         /* Skip any header junk before position diagram and/or move 1 */
8937         for (;;) {
8938             yyboardindex = forwardMostMove;
8939             cm = (ChessMove) yylex();
8940
8941             if (cm == (ChessMove) 0 ||
8942                 cm == GNUChessGame || cm == XBoardGame) {
8943                 /* Empty game; pretend end-of-file and handle later */
8944                 cm = (ChessMove) 0;
8945                 break;
8946             }
8947
8948             if (cm == MoveNumberOne || cm == PositionDiagram ||
8949                 cm == PGNTag || cm == Comment)
8950               break;
8951         }
8952     } else if (cm == GNUChessGame) {
8953         if (gameInfo.event != NULL) {
8954             free(gameInfo.event);
8955         }
8956         gameInfo.event = StrSave(yy_text);
8957     }   
8958
8959     startedFromSetupPosition = FALSE;
8960     while (cm == PGNTag) {
8961         if (appData.debugMode) 
8962           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8963         err = ParsePGNTag(yy_text, &gameInfo);
8964         if (!err) numPGNTags++;
8965
8966         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8967         if(gameInfo.variant != oldVariant) {
8968             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8969             InitPosition(TRUE);
8970             oldVariant = gameInfo.variant;
8971             if (appData.debugMode) 
8972               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8973         }
8974
8975
8976         if (gameInfo.fen != NULL) {
8977           Board initial_position;
8978           startedFromSetupPosition = TRUE;
8979           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8980             Reset(TRUE, TRUE);
8981             DisplayError(_("Bad FEN position in file"), 0);
8982             return FALSE;
8983           }
8984           CopyBoard(boards[0], initial_position);
8985           if (blackPlaysFirst) {
8986             currentMove = forwardMostMove = backwardMostMove = 1;
8987             CopyBoard(boards[1], initial_position);
8988             strcpy(moveList[0], "");
8989             strcpy(parseList[0], "");
8990             timeRemaining[0][1] = whiteTimeRemaining;
8991             timeRemaining[1][1] = blackTimeRemaining;
8992             if (commentList[0] != NULL) {
8993               commentList[1] = commentList[0];
8994               commentList[0] = NULL;
8995             }
8996           } else {
8997             currentMove = forwardMostMove = backwardMostMove = 0;
8998           }
8999           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9000           {   int i;
9001               initialRulePlies = FENrulePlies;
9002               epStatus[forwardMostMove] = FENepStatus;
9003               for( i=0; i< nrCastlingRights; i++ )
9004                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9005           }
9006           yyboardindex = forwardMostMove;
9007           free(gameInfo.fen);
9008           gameInfo.fen = NULL;
9009         }
9010
9011         yyboardindex = forwardMostMove;
9012         cm = (ChessMove) yylex();
9013
9014         /* Handle comments interspersed among the tags */
9015         while (cm == Comment) {
9016             char *p;
9017             if (appData.debugMode) 
9018               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9019             p = yy_text;
9020             if (*p == '{' || *p == '[' || *p == '(') {
9021                 p[strlen(p) - 1] = NULLCHAR;
9022                 p++;
9023             }
9024             while (*p == '\n') p++;
9025             AppendComment(currentMove, p);
9026             yyboardindex = forwardMostMove;
9027             cm = (ChessMove) yylex();
9028         }
9029     }
9030
9031     /* don't rely on existence of Event tag since if game was
9032      * pasted from clipboard the Event tag may not exist
9033      */
9034     if (numPGNTags > 0){
9035         char *tags;
9036         if (gameInfo.variant == VariantNormal) {
9037           gameInfo.variant = StringToVariant(gameInfo.event);
9038         }
9039         if (!matchMode) {
9040           if( appData.autoDisplayTags ) {
9041             tags = PGNTags(&gameInfo);
9042             TagsPopUp(tags, CmailMsg());
9043             free(tags);
9044           }
9045         }
9046     } else {
9047         /* Make something up, but don't display it now */
9048         SetGameInfo();
9049         TagsPopDown();
9050     }
9051
9052     if (cm == PositionDiagram) {
9053         int i, j;
9054         char *p;
9055         Board initial_position;
9056
9057         if (appData.debugMode)
9058           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9059
9060         if (!startedFromSetupPosition) {
9061             p = yy_text;
9062             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9063               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9064                 switch (*p) {
9065                   case '[':
9066                   case '-':
9067                   case ' ':
9068                   case '\t':
9069                   case '\n':
9070                   case '\r':
9071                     break;
9072                   default:
9073                     initial_position[i][j++] = CharToPiece(*p);
9074                     break;
9075                 }
9076             while (*p == ' ' || *p == '\t' ||
9077                    *p == '\n' || *p == '\r') p++;
9078         
9079             if (strncmp(p, "black", strlen("black"))==0)
9080               blackPlaysFirst = TRUE;
9081             else
9082               blackPlaysFirst = FALSE;
9083             startedFromSetupPosition = TRUE;
9084         
9085             CopyBoard(boards[0], initial_position);
9086             if (blackPlaysFirst) {
9087                 currentMove = forwardMostMove = backwardMostMove = 1;
9088                 CopyBoard(boards[1], initial_position);
9089                 strcpy(moveList[0], "");
9090                 strcpy(parseList[0], "");
9091                 timeRemaining[0][1] = whiteTimeRemaining;
9092                 timeRemaining[1][1] = blackTimeRemaining;
9093                 if (commentList[0] != NULL) {
9094                     commentList[1] = commentList[0];
9095                     commentList[0] = NULL;
9096                 }
9097             } else {
9098                 currentMove = forwardMostMove = backwardMostMove = 0;
9099             }
9100         }
9101         yyboardindex = forwardMostMove;
9102         cm = (ChessMove) yylex();
9103     }
9104
9105     if (first.pr == NoProc) {
9106         StartChessProgram(&first);
9107     }
9108     InitChessProgram(&first, FALSE);
9109     SendToProgram("force\n", &first);
9110     if (startedFromSetupPosition) {
9111         SendBoard(&first, forwardMostMove);
9112     if (appData.debugMode) {
9113         fprintf(debugFP, "Load Game\n");
9114     }
9115         DisplayBothClocks();
9116     }      
9117
9118     /* [HGM] server: flag to write setup moves in broadcast file as one */
9119     loadFlag = appData.suppressLoadMoves;
9120
9121     while (cm == Comment) {
9122         char *p;
9123         if (appData.debugMode) 
9124           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9125         p = yy_text;
9126         if (*p == '{' || *p == '[' || *p == '(') {
9127             p[strlen(p) - 1] = NULLCHAR;
9128             p++;
9129         }
9130         while (*p == '\n') p++;
9131         AppendComment(currentMove, p);
9132         yyboardindex = forwardMostMove;
9133         cm = (ChessMove) yylex();
9134     }
9135
9136     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9137         cm == WhiteWins || cm == BlackWins ||
9138         cm == GameIsDrawn || cm == GameUnfinished) {
9139         DisplayMessage("", _("No moves in game"));
9140         if (cmailMsgLoaded) {
9141             if (appData.debugMode)
9142               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9143             ClearHighlights();
9144             flipView = FALSE;
9145         }
9146         DrawPosition(FALSE, boards[currentMove]);
9147         DisplayBothClocks();
9148         gameMode = EditGame;
9149         ModeHighlight();
9150         gameFileFP = NULL;
9151         cmailOldMove = 0;
9152         return TRUE;
9153     }
9154
9155     // [HGM] PV info: routine tests if comment empty
9156     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9157         DisplayComment(currentMove - 1, commentList[currentMove]);
9158     }
9159     if (!matchMode && appData.timeDelay != 0) 
9160       DrawPosition(FALSE, boards[currentMove]);
9161
9162     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9163       programStats.ok_to_send = 1;
9164     }
9165
9166     /* if the first token after the PGN tags is a move
9167      * and not move number 1, retrieve it from the parser 
9168      */
9169     if (cm != MoveNumberOne)
9170         LoadGameOneMove(cm);
9171
9172     /* load the remaining moves from the file */
9173     while (LoadGameOneMove((ChessMove)0)) {
9174       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9175       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9176     }
9177
9178     /* rewind to the start of the game */
9179     currentMove = backwardMostMove;
9180
9181     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9182
9183     if (oldGameMode == AnalyzeFile ||
9184         oldGameMode == AnalyzeMode) {
9185       AnalyzeFileEvent();
9186     }
9187
9188     if (matchMode || appData.timeDelay == 0) {
9189       ToEndEvent();
9190       gameMode = EditGame;
9191       ModeHighlight();
9192     } else if (appData.timeDelay > 0) {
9193       AutoPlayGameLoop();
9194     }
9195
9196     if (appData.debugMode) 
9197         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9198
9199     loadFlag = 0; /* [HGM] true game starts */
9200     return TRUE;
9201 }
9202
9203 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9204 int
9205 ReloadPosition(offset)
9206      int offset;
9207 {
9208     int positionNumber = lastLoadPositionNumber + offset;
9209     if (lastLoadPositionFP == NULL) {
9210         DisplayError(_("No position has been loaded yet"), 0);
9211         return FALSE;
9212     }
9213     if (positionNumber <= 0) {
9214         DisplayError(_("Can't back up any further"), 0);
9215         return FALSE;
9216     }
9217     return LoadPosition(lastLoadPositionFP, positionNumber,
9218                         lastLoadPositionTitle);
9219 }
9220
9221 /* Load the nth position from the given file */
9222 int
9223 LoadPositionFromFile(filename, n, title)
9224      char *filename;
9225      int n;
9226      char *title;
9227 {
9228     FILE *f;
9229     char buf[MSG_SIZ];
9230
9231     if (strcmp(filename, "-") == 0) {
9232         return LoadPosition(stdin, n, "stdin");
9233     } else {
9234         f = fopen(filename, "rb");
9235         if (f == NULL) {
9236             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9237             DisplayError(buf, errno);
9238             return FALSE;
9239         } else {
9240             return LoadPosition(f, n, title);
9241         }
9242     }
9243 }
9244
9245 /* Load the nth position from the given open file, and close it */
9246 int
9247 LoadPosition(f, positionNumber, title)
9248      FILE *f;
9249      int positionNumber;
9250      char *title;
9251 {
9252     char *p, line[MSG_SIZ];
9253     Board initial_position;
9254     int i, j, fenMode, pn;
9255     
9256     if (gameMode == Training )
9257         SetTrainingModeOff();
9258
9259     if (gameMode != BeginningOfGame) {
9260         Reset(FALSE, TRUE);
9261     }
9262     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9263         fclose(lastLoadPositionFP);
9264     }
9265     if (positionNumber == 0) positionNumber = 1;
9266     lastLoadPositionFP = f;
9267     lastLoadPositionNumber = positionNumber;
9268     strcpy(lastLoadPositionTitle, title);
9269     if (first.pr == NoProc) {
9270       StartChessProgram(&first);
9271       InitChessProgram(&first, FALSE);
9272     }    
9273     pn = positionNumber;
9274     if (positionNumber < 0) {
9275         /* Negative position number means to seek to that byte offset */
9276         if (fseek(f, -positionNumber, 0) == -1) {
9277             DisplayError(_("Can't seek on position file"), 0);
9278             return FALSE;
9279         };
9280         pn = 1;
9281     } else {
9282         if (fseek(f, 0, 0) == -1) {
9283             if (f == lastLoadPositionFP ?
9284                 positionNumber == lastLoadPositionNumber + 1 :
9285                 positionNumber == 1) {
9286                 pn = 1;
9287             } else {
9288                 DisplayError(_("Can't seek on position file"), 0);
9289                 return FALSE;
9290             }
9291         }
9292     }
9293     /* See if this file is FEN or old-style xboard */
9294     if (fgets(line, MSG_SIZ, f) == NULL) {
9295         DisplayError(_("Position not found in file"), 0);
9296         return FALSE;
9297     }
9298 #if 0
9299     switch (line[0]) {
9300       case '#':  case 'x':
9301       default:
9302         fenMode = FALSE;
9303         break;
9304       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9305       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9306       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9307       case '7':  case '8':  case '9':
9308       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9309       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9310       case 'C':  case 'W':             case 'c':  case 'w': 
9311         fenMode = TRUE;
9312         break;
9313     }
9314 #else
9315     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9316     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9317 #endif
9318
9319     if (pn >= 2) {
9320         if (fenMode || line[0] == '#') pn--;
9321         while (pn > 0) {
9322             /* skip positions before number pn */
9323             if (fgets(line, MSG_SIZ, f) == NULL) {
9324                 Reset(TRUE, TRUE);
9325                 DisplayError(_("Position not found in file"), 0);
9326                 return FALSE;
9327             }
9328             if (fenMode || line[0] == '#') pn--;
9329         }
9330     }
9331
9332     if (fenMode) {
9333         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9334             DisplayError(_("Bad FEN position in file"), 0);
9335             return FALSE;
9336         }
9337     } else {
9338         (void) fgets(line, MSG_SIZ, f);
9339         (void) fgets(line, MSG_SIZ, f);
9340     
9341         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9342             (void) fgets(line, MSG_SIZ, f);
9343             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9344                 if (*p == ' ')
9345                   continue;
9346                 initial_position[i][j++] = CharToPiece(*p);
9347             }
9348         }
9349     
9350         blackPlaysFirst = FALSE;
9351         if (!feof(f)) {
9352             (void) fgets(line, MSG_SIZ, f);
9353             if (strncmp(line, "black", strlen("black"))==0)
9354               blackPlaysFirst = TRUE;
9355         }
9356     }
9357     startedFromSetupPosition = TRUE;
9358     
9359     SendToProgram("force\n", &first);
9360     CopyBoard(boards[0], initial_position);
9361     if (blackPlaysFirst) {
9362         currentMove = forwardMostMove = backwardMostMove = 1;
9363         strcpy(moveList[0], "");
9364         strcpy(parseList[0], "");
9365         CopyBoard(boards[1], initial_position);
9366         DisplayMessage("", _("Black to play"));
9367     } else {
9368         currentMove = forwardMostMove = backwardMostMove = 0;
9369         DisplayMessage("", _("White to play"));
9370     }
9371           /* [HGM] copy FEN attributes as well */
9372           {   int i;
9373               initialRulePlies = FENrulePlies;
9374               epStatus[forwardMostMove] = FENepStatus;
9375               for( i=0; i< nrCastlingRights; i++ )
9376                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9377           }
9378     SendBoard(&first, forwardMostMove);
9379     if (appData.debugMode) {
9380 int i, j;
9381   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9382   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9383         fprintf(debugFP, "Load Position\n");
9384     }
9385
9386     if (positionNumber > 1) {
9387         sprintf(line, "%s %d", title, positionNumber);
9388         DisplayTitle(line);
9389     } else {
9390         DisplayTitle(title);
9391     }
9392     gameMode = EditGame;
9393     ModeHighlight();
9394     ResetClocks();
9395     timeRemaining[0][1] = whiteTimeRemaining;
9396     timeRemaining[1][1] = blackTimeRemaining;
9397     DrawPosition(FALSE, boards[currentMove]);
9398    
9399     return TRUE;
9400 }
9401
9402
9403 void
9404 CopyPlayerNameIntoFileName(dest, src)
9405      char **dest, *src;
9406 {
9407     while (*src != NULLCHAR && *src != ',') {
9408         if (*src == ' ') {
9409             *(*dest)++ = '_';
9410             src++;
9411         } else {
9412             *(*dest)++ = *src++;
9413         }
9414     }
9415 }
9416
9417 char *DefaultFileName(ext)
9418      char *ext;
9419 {
9420     static char def[MSG_SIZ];
9421     char *p;
9422
9423     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9424         p = def;
9425         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9426         *p++ = '-';
9427         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9428         *p++ = '.';
9429         strcpy(p, ext);
9430     } else {
9431         def[0] = NULLCHAR;
9432     }
9433     return def;
9434 }
9435
9436 /* Save the current game to the given file */
9437 int
9438 SaveGameToFile(filename, append)
9439      char *filename;
9440      int append;
9441 {
9442     FILE *f;
9443     char buf[MSG_SIZ];
9444
9445     if (strcmp(filename, "-") == 0) {
9446         return SaveGame(stdout, 0, NULL);
9447     } else {
9448         f = fopen(filename, append ? "a" : "w");
9449         if (f == NULL) {
9450             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9451             DisplayError(buf, errno);
9452             return FALSE;
9453         } else {
9454             return SaveGame(f, 0, NULL);
9455         }
9456     }
9457 }
9458
9459 char *
9460 SavePart(str)
9461      char *str;
9462 {
9463     static char buf[MSG_SIZ];
9464     char *p;
9465     
9466     p = strchr(str, ' ');
9467     if (p == NULL) return str;
9468     strncpy(buf, str, p - str);
9469     buf[p - str] = NULLCHAR;
9470     return buf;
9471 }
9472
9473 #define PGN_MAX_LINE 75
9474
9475 #define PGN_SIDE_WHITE  0
9476 #define PGN_SIDE_BLACK  1
9477
9478 /* [AS] */
9479 static int FindFirstMoveOutOfBook( int side )
9480 {
9481     int result = -1;
9482
9483     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9484         int index = backwardMostMove;
9485         int has_book_hit = 0;
9486
9487         if( (index % 2) != side ) {
9488             index++;
9489         }
9490
9491         while( index < forwardMostMove ) {
9492             /* Check to see if engine is in book */
9493             int depth = pvInfoList[index].depth;
9494             int score = pvInfoList[index].score;
9495             int in_book = 0;
9496
9497             if( depth <= 2 ) {
9498                 in_book = 1;
9499             }
9500             else if( score == 0 && depth == 63 ) {
9501                 in_book = 1; /* Zappa */
9502             }
9503             else if( score == 2 && depth == 99 ) {
9504                 in_book = 1; /* Abrok */
9505             }
9506
9507             has_book_hit += in_book;
9508
9509             if( ! in_book ) {
9510                 result = index;
9511
9512                 break;
9513             }
9514
9515             index += 2;
9516         }
9517     }
9518
9519     return result;
9520 }
9521
9522 /* [AS] */
9523 void GetOutOfBookInfo( char * buf )
9524 {
9525     int oob[2];
9526     int i;
9527     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9528
9529     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9530     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9531
9532     *buf = '\0';
9533
9534     if( oob[0] >= 0 || oob[1] >= 0 ) {
9535         for( i=0; i<2; i++ ) {
9536             int idx = oob[i];
9537
9538             if( idx >= 0 ) {
9539                 if( i > 0 && oob[0] >= 0 ) {
9540                     strcat( buf, "   " );
9541                 }
9542
9543                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9544                 sprintf( buf+strlen(buf), "%s%.2f", 
9545                     pvInfoList[idx].score >= 0 ? "+" : "",
9546                     pvInfoList[idx].score / 100.0 );
9547             }
9548         }
9549     }
9550 }
9551
9552 /* Save game in PGN style and close the file */
9553 int
9554 SaveGamePGN(f)
9555      FILE *f;
9556 {
9557     int i, offset, linelen, newblock;
9558     time_t tm;
9559 //    char *movetext;
9560     char numtext[32];
9561     int movelen, numlen, blank;
9562     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9563
9564     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9565     
9566     tm = time((time_t *) NULL);
9567     
9568     PrintPGNTags(f, &gameInfo);
9569     
9570     if (backwardMostMove > 0 || startedFromSetupPosition) {
9571         char *fen = PositionToFEN(backwardMostMove, NULL);
9572         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9573         fprintf(f, "\n{--------------\n");
9574         PrintPosition(f, backwardMostMove);
9575         fprintf(f, "--------------}\n");
9576         free(fen);
9577     }
9578     else {
9579         /* [AS] Out of book annotation */
9580         if( appData.saveOutOfBookInfo ) {
9581             char buf[64];
9582
9583             GetOutOfBookInfo( buf );
9584
9585             if( buf[0] != '\0' ) {
9586                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9587             }
9588         }
9589
9590         fprintf(f, "\n");
9591     }
9592
9593     i = backwardMostMove;
9594     linelen = 0;
9595     newblock = TRUE;
9596
9597     while (i < forwardMostMove) {
9598         /* Print comments preceding this move */
9599         if (commentList[i] != NULL) {
9600             if (linelen > 0) fprintf(f, "\n");
9601             fprintf(f, "{\n%s}\n", commentList[i]);
9602             linelen = 0;
9603             newblock = TRUE;
9604         }
9605
9606         /* Format move number */
9607         if ((i % 2) == 0) {
9608             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9609         } else {
9610             if (newblock) {
9611                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9612             } else {
9613                 numtext[0] = NULLCHAR;
9614             }
9615         }
9616         numlen = strlen(numtext);
9617         newblock = FALSE;
9618
9619         /* Print move number */
9620         blank = linelen > 0 && numlen > 0;
9621         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9622             fprintf(f, "\n");
9623             linelen = 0;
9624             blank = 0;
9625         }
9626         if (blank) {
9627             fprintf(f, " ");
9628             linelen++;
9629         }
9630         fprintf(f, numtext);
9631         linelen += numlen;
9632
9633         /* Get move */
9634         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9635         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9636 #if 0
9637         // SavePart already does this!
9638         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9639                 int p = movelen - 1;
9640                 if(move_buffer[p] == ' ') p--;
9641                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9642                     while(p && move_buffer[--p] != '(');
9643                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9644                 }
9645         }
9646 #endif
9647         /* Print move */
9648         blank = linelen > 0 && movelen > 0;
9649         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9650             fprintf(f, "\n");
9651             linelen = 0;
9652             blank = 0;
9653         }
9654         if (blank) {
9655             fprintf(f, " ");
9656             linelen++;
9657         }
9658         fprintf(f, move_buffer);
9659         linelen += movelen;
9660
9661         /* [AS] Add PV info if present */
9662         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9663             /* [HGM] add time */
9664             char buf[MSG_SIZ]; int seconds = 0;
9665
9666 #if 1
9667             if(i >= backwardMostMove) {
9668                 if(WhiteOnMove(i))
9669                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9670                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9671                 else
9672                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9673                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9674             }
9675             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9676 #else
9677             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9678 #endif
9679
9680             if( seconds <= 0) buf[0] = 0; else
9681             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9682                 seconds = (seconds + 4)/10; // round to full seconds
9683                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9684                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9685             }
9686
9687             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9688                 pvInfoList[i].score >= 0 ? "+" : "",
9689                 pvInfoList[i].score / 100.0,
9690                 pvInfoList[i].depth,
9691                 buf );
9692
9693             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9694
9695             /* Print score/depth */
9696             blank = linelen > 0 && movelen > 0;
9697             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9698                 fprintf(f, "\n");
9699                 linelen = 0;
9700                 blank = 0;
9701             }
9702             if (blank) {
9703                 fprintf(f, " ");
9704                 linelen++;
9705             }
9706             fprintf(f, move_buffer);
9707             linelen += movelen;
9708         }
9709
9710         i++;
9711     }
9712     
9713     /* Start a new line */
9714     if (linelen > 0) fprintf(f, "\n");
9715
9716     /* Print comments after last move */
9717     if (commentList[i] != NULL) {
9718         fprintf(f, "{\n%s}\n", commentList[i]);
9719     }
9720
9721     /* Print result */
9722     if (gameInfo.resultDetails != NULL &&
9723         gameInfo.resultDetails[0] != NULLCHAR) {
9724         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9725                 PGNResult(gameInfo.result));
9726     } else {
9727         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9728     }
9729
9730     fclose(f);
9731     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9732     return TRUE;
9733 }
9734
9735 /* Save game in old style and close the file */
9736 int
9737 SaveGameOldStyle(f)
9738      FILE *f;
9739 {
9740     int i, offset;
9741     time_t tm;
9742     
9743     tm = time((time_t *) NULL);
9744     
9745     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9746     PrintOpponents(f);
9747     
9748     if (backwardMostMove > 0 || startedFromSetupPosition) {
9749         fprintf(f, "\n[--------------\n");
9750         PrintPosition(f, backwardMostMove);
9751         fprintf(f, "--------------]\n");
9752     } else {
9753         fprintf(f, "\n");
9754     }
9755
9756     i = backwardMostMove;
9757     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9758
9759     while (i < forwardMostMove) {
9760         if (commentList[i] != NULL) {
9761             fprintf(f, "[%s]\n", commentList[i]);
9762         }
9763
9764         if ((i % 2) == 1) {
9765             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9766             i++;
9767         } else {
9768             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9769             i++;
9770             if (commentList[i] != NULL) {
9771                 fprintf(f, "\n");
9772                 continue;
9773             }
9774             if (i >= forwardMostMove) {
9775                 fprintf(f, "\n");
9776                 break;
9777             }
9778             fprintf(f, "%s\n", parseList[i]);
9779             i++;
9780         }
9781     }
9782     
9783     if (commentList[i] != NULL) {
9784         fprintf(f, "[%s]\n", commentList[i]);
9785     }
9786
9787     /* This isn't really the old style, but it's close enough */
9788     if (gameInfo.resultDetails != NULL &&
9789         gameInfo.resultDetails[0] != NULLCHAR) {
9790         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9791                 gameInfo.resultDetails);
9792     } else {
9793         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9794     }
9795
9796     fclose(f);
9797     return TRUE;
9798 }
9799
9800 /* Save the current game to open file f and close the file */
9801 int
9802 SaveGame(f, dummy, dummy2)
9803      FILE *f;
9804      int dummy;
9805      char *dummy2;
9806 {
9807     if (gameMode == EditPosition) EditPositionDone();
9808     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9809     if (appData.oldSaveStyle)
9810       return SaveGameOldStyle(f);
9811     else
9812       return SaveGamePGN(f);
9813 }
9814
9815 /* Save the current position to the given file */
9816 int
9817 SavePositionToFile(filename)
9818      char *filename;
9819 {
9820     FILE *f;
9821     char buf[MSG_SIZ];
9822
9823     if (strcmp(filename, "-") == 0) {
9824         return SavePosition(stdout, 0, NULL);
9825     } else {
9826         f = fopen(filename, "a");
9827         if (f == NULL) {
9828             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9829             DisplayError(buf, errno);
9830             return FALSE;
9831         } else {
9832             SavePosition(f, 0, NULL);
9833             return TRUE;
9834         }
9835     }
9836 }
9837
9838 /* Save the current position to the given open file and close the file */
9839 int
9840 SavePosition(f, dummy, dummy2)
9841      FILE *f;
9842      int dummy;
9843      char *dummy2;
9844 {
9845     time_t tm;
9846     char *fen;
9847     
9848     if (appData.oldSaveStyle) {
9849         tm = time((time_t *) NULL);
9850     
9851         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9852         PrintOpponents(f);
9853         fprintf(f, "[--------------\n");
9854         PrintPosition(f, currentMove);
9855         fprintf(f, "--------------]\n");
9856     } else {
9857         fen = PositionToFEN(currentMove, NULL);
9858         fprintf(f, "%s\n", fen);
9859         free(fen);
9860     }
9861     fclose(f);
9862     return TRUE;
9863 }
9864
9865 void
9866 ReloadCmailMsgEvent(unregister)
9867      int unregister;
9868 {
9869 #if !WIN32
9870     static char *inFilename = NULL;
9871     static char *outFilename;
9872     int i;
9873     struct stat inbuf, outbuf;
9874     int status;
9875     
9876     /* Any registered moves are unregistered if unregister is set, */
9877     /* i.e. invoked by the signal handler */
9878     if (unregister) {
9879         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9880             cmailMoveRegistered[i] = FALSE;
9881             if (cmailCommentList[i] != NULL) {
9882                 free(cmailCommentList[i]);
9883                 cmailCommentList[i] = NULL;
9884             }
9885         }
9886         nCmailMovesRegistered = 0;
9887     }
9888
9889     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9890         cmailResult[i] = CMAIL_NOT_RESULT;
9891     }
9892     nCmailResults = 0;
9893
9894     if (inFilename == NULL) {
9895         /* Because the filenames are static they only get malloced once  */
9896         /* and they never get freed                                      */
9897         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9898         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9899
9900         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9901         sprintf(outFilename, "%s.out", appData.cmailGameName);
9902     }
9903     
9904     status = stat(outFilename, &outbuf);
9905     if (status < 0) {
9906         cmailMailedMove = FALSE;
9907     } else {
9908         status = stat(inFilename, &inbuf);
9909         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9910     }
9911     
9912     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9913        counts the games, notes how each one terminated, etc.
9914        
9915        It would be nice to remove this kludge and instead gather all
9916        the information while building the game list.  (And to keep it
9917        in the game list nodes instead of having a bunch of fixed-size
9918        parallel arrays.)  Note this will require getting each game's
9919        termination from the PGN tags, as the game list builder does
9920        not process the game moves.  --mann
9921        */
9922     cmailMsgLoaded = TRUE;
9923     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9924     
9925     /* Load first game in the file or popup game menu */
9926     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9927
9928 #endif /* !WIN32 */
9929     return;
9930 }
9931
9932 int
9933 RegisterMove()
9934 {
9935     FILE *f;
9936     char string[MSG_SIZ];
9937
9938     if (   cmailMailedMove
9939         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9940         return TRUE;            /* Allow free viewing  */
9941     }
9942
9943     /* Unregister move to ensure that we don't leave RegisterMove        */
9944     /* with the move registered when the conditions for registering no   */
9945     /* longer hold                                                       */
9946     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9947         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9948         nCmailMovesRegistered --;
9949
9950         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9951           {
9952               free(cmailCommentList[lastLoadGameNumber - 1]);
9953               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9954           }
9955     }
9956
9957     if (cmailOldMove == -1) {
9958         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9959         return FALSE;
9960     }
9961
9962     if (currentMove > cmailOldMove + 1) {
9963         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9964         return FALSE;
9965     }
9966
9967     if (currentMove < cmailOldMove) {
9968         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9969         return FALSE;
9970     }
9971
9972     if (forwardMostMove > currentMove) {
9973         /* Silently truncate extra moves */
9974         TruncateGame();
9975     }
9976
9977     if (   (currentMove == cmailOldMove + 1)
9978         || (   (currentMove == cmailOldMove)
9979             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9980                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9981         if (gameInfo.result != GameUnfinished) {
9982             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9983         }
9984
9985         if (commentList[currentMove] != NULL) {
9986             cmailCommentList[lastLoadGameNumber - 1]
9987               = StrSave(commentList[currentMove]);
9988         }
9989         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9990
9991         if (appData.debugMode)
9992           fprintf(debugFP, "Saving %s for game %d\n",
9993                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9994
9995         sprintf(string,
9996                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9997         
9998         f = fopen(string, "w");
9999         if (appData.oldSaveStyle) {
10000             SaveGameOldStyle(f); /* also closes the file */
10001             
10002             sprintf(string, "%s.pos.out", appData.cmailGameName);
10003             f = fopen(string, "w");
10004             SavePosition(f, 0, NULL); /* also closes the file */
10005         } else {
10006             fprintf(f, "{--------------\n");
10007             PrintPosition(f, currentMove);
10008             fprintf(f, "--------------}\n\n");
10009             
10010             SaveGame(f, 0, NULL); /* also closes the file*/
10011         }
10012         
10013         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10014         nCmailMovesRegistered ++;
10015     } else if (nCmailGames == 1) {
10016         DisplayError(_("You have not made a move yet"), 0);
10017         return FALSE;
10018     }
10019
10020     return TRUE;
10021 }
10022
10023 void
10024 MailMoveEvent()
10025 {
10026 #if !WIN32
10027     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10028     FILE *commandOutput;
10029     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10030     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10031     int nBuffers;
10032     int i;
10033     int archived;
10034     char *arcDir;
10035
10036     if (! cmailMsgLoaded) {
10037         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10038         return;
10039     }
10040
10041     if (nCmailGames == nCmailResults) {
10042         DisplayError(_("No unfinished games"), 0);
10043         return;
10044     }
10045
10046 #if CMAIL_PROHIBIT_REMAIL
10047     if (cmailMailedMove) {
10048         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);
10049         DisplayError(msg, 0);
10050         return;
10051     }
10052 #endif
10053
10054     if (! (cmailMailedMove || RegisterMove())) return;
10055     
10056     if (   cmailMailedMove
10057         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10058         sprintf(string, partCommandString,
10059                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10060         commandOutput = popen(string, "r");
10061
10062         if (commandOutput == NULL) {
10063             DisplayError(_("Failed to invoke cmail"), 0);
10064         } else {
10065             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10066                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10067             }
10068             if (nBuffers > 1) {
10069                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10070                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10071                 nBytes = MSG_SIZ - 1;
10072             } else {
10073                 (void) memcpy(msg, buffer, nBytes);
10074             }
10075             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10076
10077             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10078                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10079
10080                 archived = TRUE;
10081                 for (i = 0; i < nCmailGames; i ++) {
10082                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10083                         archived = FALSE;
10084                     }
10085                 }
10086                 if (   archived
10087                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10088                         != NULL)) {
10089                     sprintf(buffer, "%s/%s.%s.archive",
10090                             arcDir,
10091                             appData.cmailGameName,
10092                             gameInfo.date);
10093                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10094                     cmailMsgLoaded = FALSE;
10095                 }
10096             }
10097
10098             DisplayInformation(msg);
10099             pclose(commandOutput);
10100         }
10101     } else {
10102         if ((*cmailMsg) != '\0') {
10103             DisplayInformation(cmailMsg);
10104         }
10105     }
10106
10107     return;
10108 #endif /* !WIN32 */
10109 }
10110
10111 char *
10112 CmailMsg()
10113 {
10114 #if WIN32
10115     return NULL;
10116 #else
10117     int  prependComma = 0;
10118     char number[5];
10119     char string[MSG_SIZ];       /* Space for game-list */
10120     int  i;
10121     
10122     if (!cmailMsgLoaded) return "";
10123
10124     if (cmailMailedMove) {
10125         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10126     } else {
10127         /* Create a list of games left */
10128         sprintf(string, "[");
10129         for (i = 0; i < nCmailGames; i ++) {
10130             if (! (   cmailMoveRegistered[i]
10131                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10132                 if (prependComma) {
10133                     sprintf(number, ",%d", i + 1);
10134                 } else {
10135                     sprintf(number, "%d", i + 1);
10136                     prependComma = 1;
10137                 }
10138                 
10139                 strcat(string, number);
10140             }
10141         }
10142         strcat(string, "]");
10143
10144         if (nCmailMovesRegistered + nCmailResults == 0) {
10145             switch (nCmailGames) {
10146               case 1:
10147                 sprintf(cmailMsg,
10148                         _("Still need to make move for game\n"));
10149                 break;
10150                 
10151               case 2:
10152                 sprintf(cmailMsg,
10153                         _("Still need to make moves for both games\n"));
10154                 break;
10155                 
10156               default:
10157                 sprintf(cmailMsg,
10158                         _("Still need to make moves for all %d games\n"),
10159                         nCmailGames);
10160                 break;
10161             }
10162         } else {
10163             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10164               case 1:
10165                 sprintf(cmailMsg,
10166                         _("Still need to make a move for game %s\n"),
10167                         string);
10168                 break;
10169                 
10170               case 0:
10171                 if (nCmailResults == nCmailGames) {
10172                     sprintf(cmailMsg, _("No unfinished games\n"));
10173                 } else {
10174                     sprintf(cmailMsg, _("Ready to send mail\n"));
10175                 }
10176                 break;
10177                 
10178               default:
10179                 sprintf(cmailMsg,
10180                         _("Still need to make moves for games %s\n"),
10181                         string);
10182             }
10183         }
10184     }
10185     return cmailMsg;
10186 #endif /* WIN32 */
10187 }
10188
10189 void
10190 ResetGameEvent()
10191 {
10192     if (gameMode == Training)
10193       SetTrainingModeOff();
10194
10195     Reset(TRUE, TRUE);
10196     cmailMsgLoaded = FALSE;
10197     if (appData.icsActive) {
10198       SendToICS(ics_prefix);
10199       SendToICS("refresh\n");
10200     }
10201 }
10202
10203 void
10204 ExitEvent(status)
10205      int status;
10206 {
10207     exiting++;
10208     if (exiting > 2) {
10209       /* Give up on clean exit */
10210       exit(status);
10211     }
10212     if (exiting > 1) {
10213       /* Keep trying for clean exit */
10214       return;
10215     }
10216
10217     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10218
10219     if (telnetISR != NULL) {
10220       RemoveInputSource(telnetISR);
10221     }
10222     if (icsPR != NoProc) {
10223       DestroyChildProcess(icsPR, TRUE);
10224     }
10225 #if 0
10226     /* Save game if resource set and not already saved by GameEnds() */
10227     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10228                              && forwardMostMove > 0) {
10229       if (*appData.saveGameFile != NULLCHAR) {
10230         SaveGameToFile(appData.saveGameFile, TRUE);
10231       } else if (appData.autoSaveGames) {
10232         AutoSaveGame();
10233       }
10234       if (*appData.savePositionFile != NULLCHAR) {
10235         SavePositionToFile(appData.savePositionFile);
10236       }
10237     }
10238     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10239 #else
10240     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10241     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10242 #endif
10243     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10244     /* make sure this other one finishes before killing it!                  */
10245     if(endingGame) { int count = 0;
10246         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10247         while(endingGame && count++ < 10) DoSleep(1);
10248         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10249     }
10250
10251     /* Kill off chess programs */
10252     if (first.pr != NoProc) {
10253         ExitAnalyzeMode();
10254         
10255         DoSleep( appData.delayBeforeQuit );
10256         SendToProgram("quit\n", &first);
10257         DoSleep( appData.delayAfterQuit );
10258         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10259     }
10260     if (second.pr != NoProc) {
10261         DoSleep( appData.delayBeforeQuit );
10262         SendToProgram("quit\n", &second);
10263         DoSleep( appData.delayAfterQuit );
10264         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10265     }
10266     if (first.isr != NULL) {
10267         RemoveInputSource(first.isr);
10268     }
10269     if (second.isr != NULL) {
10270         RemoveInputSource(second.isr);
10271     }
10272
10273     ShutDownFrontEnd();
10274     exit(status);
10275 }
10276
10277 void
10278 PauseEvent()
10279 {
10280     if (appData.debugMode)
10281         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10282     if (pausing) {
10283         pausing = FALSE;
10284         ModeHighlight();
10285         if (gameMode == MachinePlaysWhite ||
10286             gameMode == MachinePlaysBlack) {
10287             StartClocks();
10288         } else {
10289             DisplayBothClocks();
10290         }
10291         if (gameMode == PlayFromGameFile) {
10292             if (appData.timeDelay >= 0) 
10293                 AutoPlayGameLoop();
10294         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10295             Reset(FALSE, TRUE);
10296             SendToICS(ics_prefix);
10297             SendToICS("refresh\n");
10298         } else if (currentMove < forwardMostMove) {
10299             ForwardInner(forwardMostMove);
10300         }
10301         pauseExamInvalid = FALSE;
10302     } else {
10303         switch (gameMode) {
10304           default:
10305             return;
10306           case IcsExamining:
10307             pauseExamForwardMostMove = forwardMostMove;
10308             pauseExamInvalid = FALSE;
10309             /* fall through */
10310           case IcsObserving:
10311           case IcsPlayingWhite:
10312           case IcsPlayingBlack:
10313             pausing = TRUE;
10314             ModeHighlight();
10315             return;
10316           case PlayFromGameFile:
10317             (void) StopLoadGameTimer();
10318             pausing = TRUE;
10319             ModeHighlight();
10320             break;
10321           case BeginningOfGame:
10322             if (appData.icsActive) return;
10323             /* else fall through */
10324           case MachinePlaysWhite:
10325           case MachinePlaysBlack:
10326           case TwoMachinesPlay:
10327             if (forwardMostMove == 0)
10328               return;           /* don't pause if no one has moved */
10329             if ((gameMode == MachinePlaysWhite &&
10330                  !WhiteOnMove(forwardMostMove)) ||
10331                 (gameMode == MachinePlaysBlack &&
10332                  WhiteOnMove(forwardMostMove))) {
10333                 StopClocks();
10334             }
10335             pausing = TRUE;
10336             ModeHighlight();
10337             break;
10338         }
10339     }
10340 }
10341
10342 void
10343 EditCommentEvent()
10344 {
10345     char title[MSG_SIZ];
10346
10347     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10348         strcpy(title, _("Edit comment"));
10349     } else {
10350         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10351                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10352                 parseList[currentMove - 1]);
10353     }
10354
10355     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10356 }
10357
10358
10359 void
10360 EditTagsEvent()
10361 {
10362     char *tags = PGNTags(&gameInfo);
10363     EditTagsPopUp(tags);
10364     free(tags);
10365 }
10366
10367 void
10368 AnalyzeModeEvent()
10369 {
10370     if (appData.noChessProgram || gameMode == AnalyzeMode)
10371       return;
10372
10373     if (gameMode != AnalyzeFile) {
10374         if (!appData.icsEngineAnalyze) {
10375                EditGameEvent();
10376                if (gameMode != EditGame) return;
10377         }
10378         ResurrectChessProgram();
10379         SendToProgram("analyze\n", &first);
10380         first.analyzing = TRUE;
10381         /*first.maybeThinking = TRUE;*/
10382         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10383         AnalysisPopUp(_("Analysis"),
10384                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10385     }
10386     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10387     pausing = FALSE;
10388     ModeHighlight();
10389     SetGameInfo();
10390
10391     StartAnalysisClock();
10392     GetTimeMark(&lastNodeCountTime);
10393     lastNodeCount = 0;
10394 }
10395
10396 void
10397 AnalyzeFileEvent()
10398 {
10399     if (appData.noChessProgram || gameMode == AnalyzeFile)
10400       return;
10401
10402     if (gameMode != AnalyzeMode) {
10403         EditGameEvent();
10404         if (gameMode != EditGame) return;
10405         ResurrectChessProgram();
10406         SendToProgram("analyze\n", &first);
10407         first.analyzing = TRUE;
10408         /*first.maybeThinking = TRUE;*/
10409         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10410         AnalysisPopUp(_("Analysis"),
10411                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10412     }
10413     gameMode = AnalyzeFile;
10414     pausing = FALSE;
10415     ModeHighlight();
10416     SetGameInfo();
10417
10418     StartAnalysisClock();
10419     GetTimeMark(&lastNodeCountTime);
10420     lastNodeCount = 0;
10421 }
10422
10423 void
10424 MachineWhiteEvent()
10425 {
10426     char buf[MSG_SIZ];
10427     char *bookHit = NULL;
10428
10429     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10430       return;
10431
10432
10433     if (gameMode == PlayFromGameFile || 
10434         gameMode == TwoMachinesPlay  || 
10435         gameMode == Training         || 
10436         gameMode == AnalyzeMode      || 
10437         gameMode == EndOfGame)
10438         EditGameEvent();
10439
10440     if (gameMode == EditPosition) 
10441         EditPositionDone();
10442
10443     if (!WhiteOnMove(currentMove)) {
10444         DisplayError(_("It is not White's turn"), 0);
10445         return;
10446     }
10447   
10448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10449       ExitAnalyzeMode();
10450
10451     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10452         gameMode == AnalyzeFile)
10453         TruncateGame();
10454
10455     ResurrectChessProgram();    /* in case it isn't running */
10456     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10457         gameMode = MachinePlaysWhite;
10458         ResetClocks();
10459     } else
10460     gameMode = MachinePlaysWhite;
10461     pausing = FALSE;
10462     ModeHighlight();
10463     SetGameInfo();
10464     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10465     DisplayTitle(buf);
10466     if (first.sendName) {
10467       sprintf(buf, "name %s\n", gameInfo.black);
10468       SendToProgram(buf, &first);
10469     }
10470     if (first.sendTime) {
10471       if (first.useColors) {
10472         SendToProgram("black\n", &first); /*gnu kludge*/
10473       }
10474       SendTimeRemaining(&first, TRUE);
10475     }
10476     if (first.useColors) {
10477       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10478     }
10479     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10480     SetMachineThinkingEnables();
10481     first.maybeThinking = TRUE;
10482     StartClocks();
10483
10484     if (appData.autoFlipView && !flipView) {
10485       flipView = !flipView;
10486       DrawPosition(FALSE, NULL);
10487       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10488     }
10489
10490     if(bookHit) { // [HGM] book: simulate book reply
10491         static char bookMove[MSG_SIZ]; // a bit generous?
10492
10493         programStats.nodes = programStats.depth = programStats.time = 
10494         programStats.score = programStats.got_only_move = 0;
10495         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10496
10497         strcpy(bookMove, "move ");
10498         strcat(bookMove, bookHit);
10499         HandleMachineMove(bookMove, &first);
10500     }
10501 }
10502
10503 void
10504 MachineBlackEvent()
10505 {
10506     char buf[MSG_SIZ];
10507    char *bookHit = NULL;
10508
10509     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10510         return;
10511
10512
10513     if (gameMode == PlayFromGameFile || 
10514         gameMode == TwoMachinesPlay  || 
10515         gameMode == Training         || 
10516         gameMode == AnalyzeMode      || 
10517         gameMode == EndOfGame)
10518         EditGameEvent();
10519
10520     if (gameMode == EditPosition) 
10521         EditPositionDone();
10522
10523     if (WhiteOnMove(currentMove)) {
10524         DisplayError(_("It is not Black's turn"), 0);
10525         return;
10526     }
10527     
10528     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10529       ExitAnalyzeMode();
10530
10531     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10532         gameMode == AnalyzeFile)
10533         TruncateGame();
10534
10535     ResurrectChessProgram();    /* in case it isn't running */
10536     gameMode = MachinePlaysBlack;
10537     pausing = FALSE;
10538     ModeHighlight();
10539     SetGameInfo();
10540     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10541     DisplayTitle(buf);
10542     if (first.sendName) {
10543       sprintf(buf, "name %s\n", gameInfo.white);
10544       SendToProgram(buf, &first);
10545     }
10546     if (first.sendTime) {
10547       if (first.useColors) {
10548         SendToProgram("white\n", &first); /*gnu kludge*/
10549       }
10550       SendTimeRemaining(&first, FALSE);
10551     }
10552     if (first.useColors) {
10553       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10554     }
10555     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10556     SetMachineThinkingEnables();
10557     first.maybeThinking = TRUE;
10558     StartClocks();
10559
10560     if (appData.autoFlipView && flipView) {
10561       flipView = !flipView;
10562       DrawPosition(FALSE, NULL);
10563       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10564     }
10565     if(bookHit) { // [HGM] book: simulate book reply
10566         static char bookMove[MSG_SIZ]; // a bit generous?
10567
10568         programStats.nodes = programStats.depth = programStats.time = 
10569         programStats.score = programStats.got_only_move = 0;
10570         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10571
10572         strcpy(bookMove, "move ");
10573         strcat(bookMove, bookHit);
10574         HandleMachineMove(bookMove, &first);
10575     }
10576 }
10577
10578
10579 void
10580 DisplayTwoMachinesTitle()
10581 {
10582     char buf[MSG_SIZ];
10583     if (appData.matchGames > 0) {
10584         if (first.twoMachinesColor[0] == 'w') {
10585             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10586                     gameInfo.white, gameInfo.black,
10587                     first.matchWins, second.matchWins,
10588                     matchGame - 1 - (first.matchWins + second.matchWins));
10589         } else {
10590             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10591                     gameInfo.white, gameInfo.black,
10592                     second.matchWins, first.matchWins,
10593                     matchGame - 1 - (first.matchWins + second.matchWins));
10594         }
10595     } else {
10596         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10597     }
10598     DisplayTitle(buf);
10599 }
10600
10601 void
10602 TwoMachinesEvent P((void))
10603 {
10604     int i;
10605     char buf[MSG_SIZ];
10606     ChessProgramState *onmove;
10607     char *bookHit = NULL;
10608     
10609     if (appData.noChessProgram) return;
10610
10611     switch (gameMode) {
10612       case TwoMachinesPlay:
10613         return;
10614       case MachinePlaysWhite:
10615       case MachinePlaysBlack:
10616         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10617             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10618             return;
10619         }
10620         /* fall through */
10621       case BeginningOfGame:
10622       case PlayFromGameFile:
10623       case EndOfGame:
10624         EditGameEvent();
10625         if (gameMode != EditGame) return;
10626         break;
10627       case EditPosition:
10628         EditPositionDone();
10629         break;
10630       case AnalyzeMode:
10631       case AnalyzeFile:
10632         ExitAnalyzeMode();
10633         break;
10634       case EditGame:
10635       default:
10636         break;
10637     }
10638
10639     forwardMostMove = currentMove;
10640     ResurrectChessProgram();    /* in case first program isn't running */
10641
10642     if (second.pr == NULL) {
10643         StartChessProgram(&second);
10644         if (second.protocolVersion == 1) {
10645           TwoMachinesEventIfReady();
10646         } else {
10647           /* kludge: allow timeout for initial "feature" command */
10648           FreezeUI();
10649           DisplayMessage("", _("Starting second chess program"));
10650           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10651         }
10652         return;
10653     }
10654     DisplayMessage("", "");
10655     InitChessProgram(&second, FALSE);
10656     SendToProgram("force\n", &second);
10657     if (startedFromSetupPosition) {
10658         SendBoard(&second, backwardMostMove);
10659     if (appData.debugMode) {
10660         fprintf(debugFP, "Two Machines\n");
10661     }
10662     }
10663     for (i = backwardMostMove; i < forwardMostMove; i++) {
10664         SendMoveToProgram(i, &second);
10665     }
10666
10667     gameMode = TwoMachinesPlay;
10668     pausing = FALSE;
10669     ModeHighlight();
10670     SetGameInfo();
10671     DisplayTwoMachinesTitle();
10672     firstMove = TRUE;
10673     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10674         onmove = &first;
10675     } else {
10676         onmove = &second;
10677     }
10678
10679     SendToProgram(first.computerString, &first);
10680     if (first.sendName) {
10681       sprintf(buf, "name %s\n", second.tidy);
10682       SendToProgram(buf, &first);
10683     }
10684     SendToProgram(second.computerString, &second);
10685     if (second.sendName) {
10686       sprintf(buf, "name %s\n", first.tidy);
10687       SendToProgram(buf, &second);
10688     }
10689
10690     ResetClocks();
10691     if (!first.sendTime || !second.sendTime) {
10692         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10693         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10694     }
10695     if (onmove->sendTime) {
10696       if (onmove->useColors) {
10697         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10698       }
10699       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10700     }
10701     if (onmove->useColors) {
10702       SendToProgram(onmove->twoMachinesColor, onmove);
10703     }
10704     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10705 //    SendToProgram("go\n", onmove);
10706     onmove->maybeThinking = TRUE;
10707     SetMachineThinkingEnables();
10708
10709     StartClocks();
10710
10711     if(bookHit) { // [HGM] book: simulate book reply
10712         static char bookMove[MSG_SIZ]; // a bit generous?
10713
10714         programStats.nodes = programStats.depth = programStats.time = 
10715         programStats.score = programStats.got_only_move = 0;
10716         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10717
10718         strcpy(bookMove, "move ");
10719         strcat(bookMove, bookHit);
10720         HandleMachineMove(bookMove, &first);
10721     }
10722 }
10723
10724 void
10725 TrainingEvent()
10726 {
10727     if (gameMode == Training) {
10728       SetTrainingModeOff();
10729       gameMode = PlayFromGameFile;
10730       DisplayMessage("", _("Training mode off"));
10731     } else {
10732       gameMode = Training;
10733       animateTraining = appData.animate;
10734
10735       /* make sure we are not already at the end of the game */
10736       if (currentMove < forwardMostMove) {
10737         SetTrainingModeOn();
10738         DisplayMessage("", _("Training mode on"));
10739       } else {
10740         gameMode = PlayFromGameFile;
10741         DisplayError(_("Already at end of game"), 0);
10742       }
10743     }
10744     ModeHighlight();
10745 }
10746
10747 void
10748 IcsClientEvent()
10749 {
10750     if (!appData.icsActive) return;
10751     switch (gameMode) {
10752       case IcsPlayingWhite:
10753       case IcsPlayingBlack:
10754       case IcsObserving:
10755       case IcsIdle:
10756       case BeginningOfGame:
10757       case IcsExamining:
10758         return;
10759
10760       case EditGame:
10761         break;
10762
10763       case EditPosition:
10764         EditPositionDone();
10765         break;
10766
10767       case AnalyzeMode:
10768       case AnalyzeFile:
10769         ExitAnalyzeMode();
10770         break;
10771         
10772       default:
10773         EditGameEvent();
10774         break;
10775     }
10776
10777     gameMode = IcsIdle;
10778     ModeHighlight();
10779     return;
10780 }
10781
10782
10783 void
10784 EditGameEvent()
10785 {
10786     int i;
10787
10788     switch (gameMode) {
10789       case Training:
10790         SetTrainingModeOff();
10791         break;
10792       case MachinePlaysWhite:
10793       case MachinePlaysBlack:
10794       case BeginningOfGame:
10795         SendToProgram("force\n", &first);
10796         SetUserThinkingEnables();
10797         break;
10798       case PlayFromGameFile:
10799         (void) StopLoadGameTimer();
10800         if (gameFileFP != NULL) {
10801             gameFileFP = NULL;
10802         }
10803         break;
10804       case EditPosition:
10805         EditPositionDone();
10806         break;
10807       case AnalyzeMode:
10808       case AnalyzeFile:
10809         ExitAnalyzeMode();
10810         SendToProgram("force\n", &first);
10811         break;
10812       case TwoMachinesPlay:
10813         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10814         ResurrectChessProgram();
10815         SetUserThinkingEnables();
10816         break;
10817       case EndOfGame:
10818         ResurrectChessProgram();
10819         break;
10820       case IcsPlayingBlack:
10821       case IcsPlayingWhite:
10822         DisplayError(_("Warning: You are still playing a game"), 0);
10823         break;
10824       case IcsObserving:
10825         DisplayError(_("Warning: You are still observing a game"), 0);
10826         break;
10827       case IcsExamining:
10828         DisplayError(_("Warning: You are still examining a game"), 0);
10829         break;
10830       case IcsIdle:
10831         break;
10832       case EditGame:
10833       default:
10834         return;
10835     }
10836     
10837     pausing = FALSE;
10838     StopClocks();
10839     first.offeredDraw = second.offeredDraw = 0;
10840
10841     if (gameMode == PlayFromGameFile) {
10842         whiteTimeRemaining = timeRemaining[0][currentMove];
10843         blackTimeRemaining = timeRemaining[1][currentMove];
10844         DisplayTitle("");
10845     }
10846
10847     if (gameMode == MachinePlaysWhite ||
10848         gameMode == MachinePlaysBlack ||
10849         gameMode == TwoMachinesPlay ||
10850         gameMode == EndOfGame) {
10851         i = forwardMostMove;
10852         while (i > currentMove) {
10853             SendToProgram("undo\n", &first);
10854             i--;
10855         }
10856         whiteTimeRemaining = timeRemaining[0][currentMove];
10857         blackTimeRemaining = timeRemaining[1][currentMove];
10858         DisplayBothClocks();
10859         if (whiteFlag || blackFlag) {
10860             whiteFlag = blackFlag = 0;
10861         }
10862         DisplayTitle("");
10863     }           
10864     
10865     gameMode = EditGame;
10866     ModeHighlight();
10867     SetGameInfo();
10868 }
10869
10870
10871 void
10872 EditPositionEvent()
10873 {
10874     if (gameMode == EditPosition) {
10875         EditGameEvent();
10876         return;
10877     }
10878     
10879     EditGameEvent();
10880     if (gameMode != EditGame) return;
10881     
10882     gameMode = EditPosition;
10883     ModeHighlight();
10884     SetGameInfo();
10885     if (currentMove > 0)
10886       CopyBoard(boards[0], boards[currentMove]);
10887     
10888     blackPlaysFirst = !WhiteOnMove(currentMove);
10889     ResetClocks();
10890     currentMove = forwardMostMove = backwardMostMove = 0;
10891     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10892     DisplayMove(-1);
10893 }
10894
10895 void
10896 ExitAnalyzeMode()
10897 {
10898     /* [DM] icsEngineAnalyze - possible call from other functions */
10899     if (appData.icsEngineAnalyze) {
10900         appData.icsEngineAnalyze = FALSE;
10901
10902         DisplayMessage("",_("Close ICS engine analyze..."));
10903     }
10904     if (first.analysisSupport && first.analyzing) {
10905       SendToProgram("exit\n", &first);
10906       first.analyzing = FALSE;
10907     }
10908     AnalysisPopDown();
10909     thinkOutput[0] = NULLCHAR;
10910 }
10911
10912 void
10913 EditPositionDone()
10914 {
10915     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10916
10917     startedFromSetupPosition = TRUE;
10918     InitChessProgram(&first, FALSE);
10919     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10920     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10921         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10922         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10923     } else castlingRights[0][2] = -1;
10924     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10925         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10926         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10927     } else castlingRights[0][5] = -1;
10928     SendToProgram("force\n", &first);
10929     if (blackPlaysFirst) {
10930         strcpy(moveList[0], "");
10931         strcpy(parseList[0], "");
10932         currentMove = forwardMostMove = backwardMostMove = 1;
10933         CopyBoard(boards[1], boards[0]);
10934         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10935         { int i;
10936           epStatus[1] = epStatus[0];
10937           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10938         }
10939     } else {
10940         currentMove = forwardMostMove = backwardMostMove = 0;
10941     }
10942     SendBoard(&first, forwardMostMove);
10943     if (appData.debugMode) {
10944         fprintf(debugFP, "EditPosDone\n");
10945     }
10946     DisplayTitle("");
10947     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10948     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10949     gameMode = EditGame;
10950     ModeHighlight();
10951     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10952     ClearHighlights(); /* [AS] */
10953 }
10954
10955 /* Pause for `ms' milliseconds */
10956 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10957 void
10958 TimeDelay(ms)
10959      long ms;
10960 {
10961     TimeMark m1, m2;
10962
10963     GetTimeMark(&m1);
10964     do {
10965         GetTimeMark(&m2);
10966     } while (SubtractTimeMarks(&m2, &m1) < ms);
10967 }
10968
10969 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10970 void
10971 SendMultiLineToICS(buf)
10972      char *buf;
10973 {
10974     char temp[MSG_SIZ+1], *p;
10975     int len;
10976
10977     len = strlen(buf);
10978     if (len > MSG_SIZ)
10979       len = MSG_SIZ;
10980   
10981     strncpy(temp, buf, len);
10982     temp[len] = 0;
10983
10984     p = temp;
10985     while (*p) {
10986         if (*p == '\n' || *p == '\r')
10987           *p = ' ';
10988         ++p;
10989     }
10990
10991     strcat(temp, "\n");
10992     SendToICS(temp);
10993     SendToPlayer(temp, strlen(temp));
10994 }
10995
10996 void
10997 SetWhiteToPlayEvent()
10998 {
10999     if (gameMode == EditPosition) {
11000         blackPlaysFirst = FALSE;
11001         DisplayBothClocks();    /* works because currentMove is 0 */
11002     } else if (gameMode == IcsExamining) {
11003         SendToICS(ics_prefix);
11004         SendToICS("tomove white\n");
11005     }
11006 }
11007
11008 void
11009 SetBlackToPlayEvent()
11010 {
11011     if (gameMode == EditPosition) {
11012         blackPlaysFirst = TRUE;
11013         currentMove = 1;        /* kludge */
11014         DisplayBothClocks();
11015         currentMove = 0;
11016     } else if (gameMode == IcsExamining) {
11017         SendToICS(ics_prefix);
11018         SendToICS("tomove black\n");
11019     }
11020 }
11021
11022 void
11023 EditPositionMenuEvent(selection, x, y)
11024      ChessSquare selection;
11025      int x, y;
11026 {
11027     char buf[MSG_SIZ];
11028     ChessSquare piece = boards[0][y][x];
11029
11030     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11031
11032     switch (selection) {
11033       case ClearBoard:
11034         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11035             SendToICS(ics_prefix);
11036             SendToICS("bsetup clear\n");
11037         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11038             SendToICS(ics_prefix);
11039             SendToICS("clearboard\n");
11040         } else {
11041             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11042                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11043                 for (y = 0; y < BOARD_HEIGHT; y++) {
11044                     if (gameMode == IcsExamining) {
11045                         if (boards[currentMove][y][x] != EmptySquare) {
11046                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11047                                     AAA + x, ONE + y);
11048                             SendToICS(buf);
11049                         }
11050                     } else {
11051                         boards[0][y][x] = p;
11052                     }
11053                 }
11054             }
11055         }
11056         if (gameMode == EditPosition) {
11057             DrawPosition(FALSE, boards[0]);
11058         }
11059         break;
11060
11061       case WhitePlay:
11062         SetWhiteToPlayEvent();
11063         break;
11064
11065       case BlackPlay:
11066         SetBlackToPlayEvent();
11067         break;
11068
11069       case EmptySquare:
11070         if (gameMode == IcsExamining) {
11071             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11072             SendToICS(buf);
11073         } else {
11074             boards[0][y][x] = EmptySquare;
11075             DrawPosition(FALSE, boards[0]);
11076         }
11077         break;
11078
11079       case PromotePiece:
11080         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11081            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11082             selection = (ChessSquare) (PROMOTED piece);
11083         } else if(piece == EmptySquare) selection = WhiteSilver;
11084         else selection = (ChessSquare)((int)piece - 1);
11085         goto defaultlabel;
11086
11087       case DemotePiece:
11088         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11089            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11090             selection = (ChessSquare) (DEMOTED piece);
11091         } else if(piece == EmptySquare) selection = BlackSilver;
11092         else selection = (ChessSquare)((int)piece + 1);       
11093         goto defaultlabel;
11094
11095       case WhiteQueen:
11096       case BlackQueen:
11097         if(gameInfo.variant == VariantShatranj ||
11098            gameInfo.variant == VariantXiangqi  ||
11099            gameInfo.variant == VariantCourier    )
11100             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11101         goto defaultlabel;
11102
11103       case WhiteKing:
11104       case BlackKing:
11105         if(gameInfo.variant == VariantXiangqi)
11106             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11107         if(gameInfo.variant == VariantKnightmate)
11108             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11109       default:
11110         defaultlabel:
11111         if (gameMode == IcsExamining) {
11112             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11113                     PieceToChar(selection), AAA + x, ONE + y);
11114             SendToICS(buf);
11115         } else {
11116             boards[0][y][x] = selection;
11117             DrawPosition(FALSE, boards[0]);
11118         }
11119         break;
11120     }
11121 }
11122
11123
11124 void
11125 DropMenuEvent(selection, x, y)
11126      ChessSquare selection;
11127      int x, y;
11128 {
11129     ChessMove moveType;
11130
11131     switch (gameMode) {
11132       case IcsPlayingWhite:
11133       case MachinePlaysBlack:
11134         if (!WhiteOnMove(currentMove)) {
11135             DisplayMoveError(_("It is Black's turn"));
11136             return;
11137         }
11138         moveType = WhiteDrop;
11139         break;
11140       case IcsPlayingBlack:
11141       case MachinePlaysWhite:
11142         if (WhiteOnMove(currentMove)) {
11143             DisplayMoveError(_("It is White's turn"));
11144             return;
11145         }
11146         moveType = BlackDrop;
11147         break;
11148       case EditGame:
11149         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11150         break;
11151       default:
11152         return;
11153     }
11154
11155     if (moveType == BlackDrop && selection < BlackPawn) {
11156       selection = (ChessSquare) ((int) selection
11157                                  + (int) BlackPawn - (int) WhitePawn);
11158     }
11159     if (boards[currentMove][y][x] != EmptySquare) {
11160         DisplayMoveError(_("That square is occupied"));
11161         return;
11162     }
11163
11164     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11165 }
11166
11167 void
11168 AcceptEvent()
11169 {
11170     /* Accept a pending offer of any kind from opponent */
11171     
11172     if (appData.icsActive) {
11173         SendToICS(ics_prefix);
11174         SendToICS("accept\n");
11175     } else if (cmailMsgLoaded) {
11176         if (currentMove == cmailOldMove &&
11177             commentList[cmailOldMove] != NULL &&
11178             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11179                    "Black offers a draw" : "White offers a draw")) {
11180             TruncateGame();
11181             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11182             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11183         } else {
11184             DisplayError(_("There is no pending offer on this move"), 0);
11185             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11186         }
11187     } else {
11188         /* Not used for offers from chess program */
11189     }
11190 }
11191
11192 void
11193 DeclineEvent()
11194 {
11195     /* Decline a pending offer of any kind from opponent */
11196     
11197     if (appData.icsActive) {
11198         SendToICS(ics_prefix);
11199         SendToICS("decline\n");
11200     } else if (cmailMsgLoaded) {
11201         if (currentMove == cmailOldMove &&
11202             commentList[cmailOldMove] != NULL &&
11203             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11204                    "Black offers a draw" : "White offers a draw")) {
11205 #ifdef NOTDEF
11206             AppendComment(cmailOldMove, "Draw declined");
11207             DisplayComment(cmailOldMove - 1, "Draw declined");
11208 #endif /*NOTDEF*/
11209         } else {
11210             DisplayError(_("There is no pending offer on this move"), 0);
11211         }
11212     } else {
11213         /* Not used for offers from chess program */
11214     }
11215 }
11216
11217 void
11218 RematchEvent()
11219 {
11220     /* Issue ICS rematch command */
11221     if (appData.icsActive) {
11222         SendToICS(ics_prefix);
11223         SendToICS("rematch\n");
11224     }
11225 }
11226
11227 void
11228 CallFlagEvent()
11229 {
11230     /* Call your opponent's flag (claim a win on time) */
11231     if (appData.icsActive) {
11232         SendToICS(ics_prefix);
11233         SendToICS("flag\n");
11234     } else {
11235         switch (gameMode) {
11236           default:
11237             return;
11238           case MachinePlaysWhite:
11239             if (whiteFlag) {
11240                 if (blackFlag)
11241                   GameEnds(GameIsDrawn, "Both players ran out of time",
11242                            GE_PLAYER);
11243                 else
11244                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11245             } else {
11246                 DisplayError(_("Your opponent is not out of time"), 0);
11247             }
11248             break;
11249           case MachinePlaysBlack:
11250             if (blackFlag) {
11251                 if (whiteFlag)
11252                   GameEnds(GameIsDrawn, "Both players ran out of time",
11253                            GE_PLAYER);
11254                 else
11255                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11256             } else {
11257                 DisplayError(_("Your opponent is not out of time"), 0);
11258             }
11259             break;
11260         }
11261     }
11262 }
11263
11264 void
11265 DrawEvent()
11266 {
11267     /* Offer draw or accept pending draw offer from opponent */
11268     
11269     if (appData.icsActive) {
11270         /* Note: tournament rules require draw offers to be
11271            made after you make your move but before you punch
11272            your clock.  Currently ICS doesn't let you do that;
11273            instead, you immediately punch your clock after making
11274            a move, but you can offer a draw at any time. */
11275         
11276         SendToICS(ics_prefix);
11277         SendToICS("draw\n");
11278     } else if (cmailMsgLoaded) {
11279         if (currentMove == cmailOldMove &&
11280             commentList[cmailOldMove] != NULL &&
11281             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11282                    "Black offers a draw" : "White offers a draw")) {
11283             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11284             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11285         } else if (currentMove == cmailOldMove + 1) {
11286             char *offer = WhiteOnMove(cmailOldMove) ?
11287               "White offers a draw" : "Black offers a draw";
11288             AppendComment(currentMove, offer);
11289             DisplayComment(currentMove - 1, offer);
11290             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11291         } else {
11292             DisplayError(_("You must make your move before offering a draw"), 0);
11293             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11294         }
11295     } else if (first.offeredDraw) {
11296         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11297     } else {
11298         if (first.sendDrawOffers) {
11299             SendToProgram("draw\n", &first);
11300             userOfferedDraw = TRUE;
11301         }
11302     }
11303 }
11304
11305 void
11306 AdjournEvent()
11307 {
11308     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11309     
11310     if (appData.icsActive) {
11311         SendToICS(ics_prefix);
11312         SendToICS("adjourn\n");
11313     } else {
11314         /* Currently GNU Chess doesn't offer or accept Adjourns */
11315     }
11316 }
11317
11318
11319 void
11320 AbortEvent()
11321 {
11322     /* Offer Abort or accept pending Abort offer from opponent */
11323     
11324     if (appData.icsActive) {
11325         SendToICS(ics_prefix);
11326         SendToICS("abort\n");
11327     } else {
11328         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11329     }
11330 }
11331
11332 void
11333 ResignEvent()
11334 {
11335     /* Resign.  You can do this even if it's not your turn. */
11336     
11337     if (appData.icsActive) {
11338         SendToICS(ics_prefix);
11339         SendToICS("resign\n");
11340     } else {
11341         switch (gameMode) {
11342           case MachinePlaysWhite:
11343             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11344             break;
11345           case MachinePlaysBlack:
11346             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11347             break;
11348           case EditGame:
11349             if (cmailMsgLoaded) {
11350                 TruncateGame();
11351                 if (WhiteOnMove(cmailOldMove)) {
11352                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11353                 } else {
11354                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11355                 }
11356                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11357             }
11358             break;
11359           default:
11360             break;
11361         }
11362     }
11363 }
11364
11365
11366 void
11367 StopObservingEvent()
11368 {
11369     /* Stop observing current games */
11370     SendToICS(ics_prefix);
11371     SendToICS("unobserve\n");
11372 }
11373
11374 void
11375 StopExaminingEvent()
11376 {
11377     /* Stop observing current game */
11378     SendToICS(ics_prefix);
11379     SendToICS("unexamine\n");
11380 }
11381
11382 void
11383 ForwardInner(target)
11384      int target;
11385 {
11386     int limit;
11387
11388     if (appData.debugMode)
11389         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11390                 target, currentMove, forwardMostMove);
11391
11392     if (gameMode == EditPosition)
11393       return;
11394
11395     if (gameMode == PlayFromGameFile && !pausing)
11396       PauseEvent();
11397     
11398     if (gameMode == IcsExamining && pausing)
11399       limit = pauseExamForwardMostMove;
11400     else
11401       limit = forwardMostMove;
11402     
11403     if (target > limit) target = limit;
11404
11405     if (target > 0 && moveList[target - 1][0]) {
11406         int fromX, fromY, toX, toY;
11407         toX = moveList[target - 1][2] - AAA;
11408         toY = moveList[target - 1][3] - ONE;
11409         if (moveList[target - 1][1] == '@') {
11410             if (appData.highlightLastMove) {
11411                 SetHighlights(-1, -1, toX, toY);
11412             }
11413         } else {
11414             fromX = moveList[target - 1][0] - AAA;
11415             fromY = moveList[target - 1][1] - ONE;
11416             if (target == currentMove + 1) {
11417                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11418             }
11419             if (appData.highlightLastMove) {
11420                 SetHighlights(fromX, fromY, toX, toY);
11421             }
11422         }
11423     }
11424     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11425         gameMode == Training || gameMode == PlayFromGameFile || 
11426         gameMode == AnalyzeFile) {
11427         while (currentMove < target) {
11428             SendMoveToProgram(currentMove++, &first);
11429         }
11430     } else {
11431         currentMove = target;
11432     }
11433     
11434     if (gameMode == EditGame || gameMode == EndOfGame) {
11435         whiteTimeRemaining = timeRemaining[0][currentMove];
11436         blackTimeRemaining = timeRemaining[1][currentMove];
11437     }
11438     DisplayBothClocks();
11439     DisplayMove(currentMove - 1);
11440     DrawPosition(FALSE, boards[currentMove]);
11441     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11442     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11443         DisplayComment(currentMove - 1, commentList[currentMove]);
11444     }
11445 }
11446
11447
11448 void
11449 ForwardEvent()
11450 {
11451     if (gameMode == IcsExamining && !pausing) {
11452         SendToICS(ics_prefix);
11453         SendToICS("forward\n");
11454     } else {
11455         ForwardInner(currentMove + 1);
11456     }
11457 }
11458
11459 void
11460 ToEndEvent()
11461 {
11462     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11463         /* to optimze, we temporarily turn off analysis mode while we feed
11464          * the remaining moves to the engine. Otherwise we get analysis output
11465          * after each move.
11466          */ 
11467         if (first.analysisSupport) {
11468           SendToProgram("exit\nforce\n", &first);
11469           first.analyzing = FALSE;
11470         }
11471     }
11472         
11473     if (gameMode == IcsExamining && !pausing) {
11474         SendToICS(ics_prefix);
11475         SendToICS("forward 999999\n");
11476     } else {
11477         ForwardInner(forwardMostMove);
11478     }
11479
11480     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11481         /* we have fed all the moves, so reactivate analysis mode */
11482         SendToProgram("analyze\n", &first);
11483         first.analyzing = TRUE;
11484         /*first.maybeThinking = TRUE;*/
11485         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11486     }
11487 }
11488
11489 void
11490 BackwardInner(target)
11491      int target;
11492 {
11493     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11494
11495     if (appData.debugMode)
11496         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11497                 target, currentMove, forwardMostMove);
11498
11499     if (gameMode == EditPosition) return;
11500     if (currentMove <= backwardMostMove) {
11501         ClearHighlights();
11502         DrawPosition(full_redraw, boards[currentMove]);
11503         return;
11504     }
11505     if (gameMode == PlayFromGameFile && !pausing)
11506       PauseEvent();
11507     
11508     if (moveList[target][0]) {
11509         int fromX, fromY, toX, toY;
11510         toX = moveList[target][2] - AAA;
11511         toY = moveList[target][3] - ONE;
11512         if (moveList[target][1] == '@') {
11513             if (appData.highlightLastMove) {
11514                 SetHighlights(-1, -1, toX, toY);
11515             }
11516         } else {
11517             fromX = moveList[target][0] - AAA;
11518             fromY = moveList[target][1] - ONE;
11519             if (target == currentMove - 1) {
11520                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11521             }
11522             if (appData.highlightLastMove) {
11523                 SetHighlights(fromX, fromY, toX, toY);
11524             }
11525         }
11526     }
11527     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11528         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11529         while (currentMove > target) {
11530             SendToProgram("undo\n", &first);
11531             currentMove--;
11532         }
11533     } else {
11534         currentMove = target;
11535     }
11536     
11537     if (gameMode == EditGame || gameMode == EndOfGame) {
11538         whiteTimeRemaining = timeRemaining[0][currentMove];
11539         blackTimeRemaining = timeRemaining[1][currentMove];
11540     }
11541     DisplayBothClocks();
11542     DisplayMove(currentMove - 1);
11543     DrawPosition(full_redraw, boards[currentMove]);
11544     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11545     // [HGM] PV info: routine tests if comment empty
11546     DisplayComment(currentMove - 1, commentList[currentMove]);
11547 }
11548
11549 void
11550 BackwardEvent()
11551 {
11552     if (gameMode == IcsExamining && !pausing) {
11553         SendToICS(ics_prefix);
11554         SendToICS("backward\n");
11555     } else {
11556         BackwardInner(currentMove - 1);
11557     }
11558 }
11559
11560 void
11561 ToStartEvent()
11562 {
11563     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11564         /* to optimze, we temporarily turn off analysis mode while we undo
11565          * all the moves. Otherwise we get analysis output after each undo.
11566          */ 
11567         if (first.analysisSupport) {
11568           SendToProgram("exit\nforce\n", &first);
11569           first.analyzing = FALSE;
11570         }
11571     }
11572
11573     if (gameMode == IcsExamining && !pausing) {
11574         SendToICS(ics_prefix);
11575         SendToICS("backward 999999\n");
11576     } else {
11577         BackwardInner(backwardMostMove);
11578     }
11579
11580     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11581         /* we have fed all the moves, so reactivate analysis mode */
11582         SendToProgram("analyze\n", &first);
11583         first.analyzing = TRUE;
11584         /*first.maybeThinking = TRUE;*/
11585         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11586     }
11587 }
11588
11589 void
11590 ToNrEvent(int to)
11591 {
11592   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11593   if (to >= forwardMostMove) to = forwardMostMove;
11594   if (to <= backwardMostMove) to = backwardMostMove;
11595   if (to < currentMove) {
11596     BackwardInner(to);
11597   } else {
11598     ForwardInner(to);
11599   }
11600 }
11601
11602 void
11603 RevertEvent()
11604 {
11605     if (gameMode != IcsExamining) {
11606         DisplayError(_("You are not examining a game"), 0);
11607         return;
11608     }
11609     if (pausing) {
11610         DisplayError(_("You can't revert while pausing"), 0);
11611         return;
11612     }
11613     SendToICS(ics_prefix);
11614     SendToICS("revert\n");
11615 }
11616
11617 void
11618 RetractMoveEvent()
11619 {
11620     switch (gameMode) {
11621       case MachinePlaysWhite:
11622       case MachinePlaysBlack:
11623         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11624             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11625             return;
11626         }
11627         if (forwardMostMove < 2) return;
11628         currentMove = forwardMostMove = forwardMostMove - 2;
11629         whiteTimeRemaining = timeRemaining[0][currentMove];
11630         blackTimeRemaining = timeRemaining[1][currentMove];
11631         DisplayBothClocks();
11632         DisplayMove(currentMove - 1);
11633         ClearHighlights();/*!! could figure this out*/
11634         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11635         SendToProgram("remove\n", &first);
11636         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11637         break;
11638
11639       case BeginningOfGame:
11640       default:
11641         break;
11642
11643       case IcsPlayingWhite:
11644       case IcsPlayingBlack:
11645         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11646             SendToICS(ics_prefix);
11647             SendToICS("takeback 2\n");
11648         } else {
11649             SendToICS(ics_prefix);
11650             SendToICS("takeback 1\n");
11651         }
11652         break;
11653     }
11654 }
11655
11656 void
11657 MoveNowEvent()
11658 {
11659     ChessProgramState *cps;
11660
11661     switch (gameMode) {
11662       case MachinePlaysWhite:
11663         if (!WhiteOnMove(forwardMostMove)) {
11664             DisplayError(_("It is your turn"), 0);
11665             return;
11666         }
11667         cps = &first;
11668         break;
11669       case MachinePlaysBlack:
11670         if (WhiteOnMove(forwardMostMove)) {
11671             DisplayError(_("It is your turn"), 0);
11672             return;
11673         }
11674         cps = &first;
11675         break;
11676       case TwoMachinesPlay:
11677         if (WhiteOnMove(forwardMostMove) ==
11678             (first.twoMachinesColor[0] == 'w')) {
11679             cps = &first;
11680         } else {
11681             cps = &second;
11682         }
11683         break;
11684       case BeginningOfGame:
11685       default:
11686         return;
11687     }
11688     SendToProgram("?\n", cps);
11689 }
11690
11691 void
11692 TruncateGameEvent()
11693 {
11694     EditGameEvent();
11695     if (gameMode != EditGame) return;
11696     TruncateGame();
11697 }
11698
11699 void
11700 TruncateGame()
11701 {
11702     if (forwardMostMove > currentMove) {
11703         if (gameInfo.resultDetails != NULL) {
11704             free(gameInfo.resultDetails);
11705             gameInfo.resultDetails = NULL;
11706             gameInfo.result = GameUnfinished;
11707         }
11708         forwardMostMove = currentMove;
11709         HistorySet(parseList, backwardMostMove, forwardMostMove,
11710                    currentMove-1);
11711     }
11712 }
11713
11714 void
11715 HintEvent()
11716 {
11717     if (appData.noChessProgram) return;
11718     switch (gameMode) {
11719       case MachinePlaysWhite:
11720         if (WhiteOnMove(forwardMostMove)) {
11721             DisplayError(_("Wait until your turn"), 0);
11722             return;
11723         }
11724         break;
11725       case BeginningOfGame:
11726       case MachinePlaysBlack:
11727         if (!WhiteOnMove(forwardMostMove)) {
11728             DisplayError(_("Wait until your turn"), 0);
11729             return;
11730         }
11731         break;
11732       default:
11733         DisplayError(_("No hint available"), 0);
11734         return;
11735     }
11736     SendToProgram("hint\n", &first);
11737     hintRequested = TRUE;
11738 }
11739
11740 void
11741 BookEvent()
11742 {
11743     if (appData.noChessProgram) return;
11744     switch (gameMode) {
11745       case MachinePlaysWhite:
11746         if (WhiteOnMove(forwardMostMove)) {
11747             DisplayError(_("Wait until your turn"), 0);
11748             return;
11749         }
11750         break;
11751       case BeginningOfGame:
11752       case MachinePlaysBlack:
11753         if (!WhiteOnMove(forwardMostMove)) {
11754             DisplayError(_("Wait until your turn"), 0);
11755             return;
11756         }
11757         break;
11758       case EditPosition:
11759         EditPositionDone();
11760         break;
11761       case TwoMachinesPlay:
11762         return;
11763       default:
11764         break;
11765     }
11766     SendToProgram("bk\n", &first);
11767     bookOutput[0] = NULLCHAR;
11768     bookRequested = TRUE;
11769 }
11770
11771 void
11772 AboutGameEvent()
11773 {
11774     char *tags = PGNTags(&gameInfo);
11775     TagsPopUp(tags, CmailMsg());
11776     free(tags);
11777 }
11778
11779 /* end button procedures */
11780
11781 void
11782 PrintPosition(fp, move)
11783      FILE *fp;
11784      int move;
11785 {
11786     int i, j;
11787     
11788     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11789         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11790             char c = PieceToChar(boards[move][i][j]);
11791             fputc(c == 'x' ? '.' : c, fp);
11792             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11793         }
11794     }
11795     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11796       fprintf(fp, "white to play\n");
11797     else
11798       fprintf(fp, "black to play\n");
11799 }
11800
11801 void
11802 PrintOpponents(fp)
11803      FILE *fp;
11804 {
11805     if (gameInfo.white != NULL) {
11806         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11807     } else {
11808         fprintf(fp, "\n");
11809     }
11810 }
11811
11812 /* Find last component of program's own name, using some heuristics */
11813 void
11814 TidyProgramName(prog, host, buf)
11815      char *prog, *host, buf[MSG_SIZ];
11816 {
11817     char *p, *q;
11818     int local = (strcmp(host, "localhost") == 0);
11819     while (!local && (p = strchr(prog, ';')) != NULL) {
11820         p++;
11821         while (*p == ' ') p++;
11822         prog = p;
11823     }
11824     if (*prog == '"' || *prog == '\'') {
11825         q = strchr(prog + 1, *prog);
11826     } else {
11827         q = strchr(prog, ' ');
11828     }
11829     if (q == NULL) q = prog + strlen(prog);
11830     p = q;
11831     while (p >= prog && *p != '/' && *p != '\\') p--;
11832     p++;
11833     if(p == prog && *p == '"') p++;
11834     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11835     memcpy(buf, p, q - p);
11836     buf[q - p] = NULLCHAR;
11837     if (!local) {
11838         strcat(buf, "@");
11839         strcat(buf, host);
11840     }
11841 }
11842
11843 char *
11844 TimeControlTagValue()
11845 {
11846     char buf[MSG_SIZ];
11847     if (!appData.clockMode) {
11848         strcpy(buf, "-");
11849     } else if (movesPerSession > 0) {
11850         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11851     } else if (timeIncrement == 0) {
11852         sprintf(buf, "%ld", timeControl/1000);
11853     } else {
11854         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11855     }
11856     return StrSave(buf);
11857 }
11858
11859 void
11860 SetGameInfo()
11861 {
11862     /* This routine is used only for certain modes */
11863     VariantClass v = gameInfo.variant;
11864     ClearGameInfo(&gameInfo);
11865     gameInfo.variant = v;
11866
11867     switch (gameMode) {
11868       case MachinePlaysWhite:
11869         gameInfo.event = StrSave( appData.pgnEventHeader );
11870         gameInfo.site = StrSave(HostName());
11871         gameInfo.date = PGNDate();
11872         gameInfo.round = StrSave("-");
11873         gameInfo.white = StrSave(first.tidy);
11874         gameInfo.black = StrSave(UserName());
11875         gameInfo.timeControl = TimeControlTagValue();
11876         break;
11877
11878       case MachinePlaysBlack:
11879         gameInfo.event = StrSave( appData.pgnEventHeader );
11880         gameInfo.site = StrSave(HostName());
11881         gameInfo.date = PGNDate();
11882         gameInfo.round = StrSave("-");
11883         gameInfo.white = StrSave(UserName());
11884         gameInfo.black = StrSave(first.tidy);
11885         gameInfo.timeControl = TimeControlTagValue();
11886         break;
11887
11888       case TwoMachinesPlay:
11889         gameInfo.event = StrSave( appData.pgnEventHeader );
11890         gameInfo.site = StrSave(HostName());
11891         gameInfo.date = PGNDate();
11892         if (matchGame > 0) {
11893             char buf[MSG_SIZ];
11894             sprintf(buf, "%d", matchGame);
11895             gameInfo.round = StrSave(buf);
11896         } else {
11897             gameInfo.round = StrSave("-");
11898         }
11899         if (first.twoMachinesColor[0] == 'w') {
11900             gameInfo.white = StrSave(first.tidy);
11901             gameInfo.black = StrSave(second.tidy);
11902         } else {
11903             gameInfo.white = StrSave(second.tidy);
11904             gameInfo.black = StrSave(first.tidy);
11905         }
11906         gameInfo.timeControl = TimeControlTagValue();
11907         break;
11908
11909       case EditGame:
11910         gameInfo.event = StrSave("Edited game");
11911         gameInfo.site = StrSave(HostName());
11912         gameInfo.date = PGNDate();
11913         gameInfo.round = StrSave("-");
11914         gameInfo.white = StrSave("-");
11915         gameInfo.black = StrSave("-");
11916         break;
11917
11918       case EditPosition:
11919         gameInfo.event = StrSave("Edited position");
11920         gameInfo.site = StrSave(HostName());
11921         gameInfo.date = PGNDate();
11922         gameInfo.round = StrSave("-");
11923         gameInfo.white = StrSave("-");
11924         gameInfo.black = StrSave("-");
11925         break;
11926
11927       case IcsPlayingWhite:
11928       case IcsPlayingBlack:
11929       case IcsObserving:
11930       case IcsExamining:
11931         break;
11932
11933       case PlayFromGameFile:
11934         gameInfo.event = StrSave("Game from non-PGN file");
11935         gameInfo.site = StrSave(HostName());
11936         gameInfo.date = PGNDate();
11937         gameInfo.round = StrSave("-");
11938         gameInfo.white = StrSave("?");
11939         gameInfo.black = StrSave("?");
11940         break;
11941
11942       default:
11943         break;
11944     }
11945 }
11946
11947 void
11948 ReplaceComment(index, text)
11949      int index;
11950      char *text;
11951 {
11952     int len;
11953
11954     while (*text == '\n') text++;
11955     len = strlen(text);
11956     while (len > 0 && text[len - 1] == '\n') len--;
11957
11958     if (commentList[index] != NULL)
11959       free(commentList[index]);
11960
11961     if (len == 0) {
11962         commentList[index] = NULL;
11963         return;
11964     }
11965     commentList[index] = (char *) malloc(len + 2);
11966     strncpy(commentList[index], text, len);
11967     commentList[index][len] = '\n';
11968     commentList[index][len + 1] = NULLCHAR;
11969 }
11970
11971 void
11972 CrushCRs(text)
11973      char *text;
11974 {
11975   char *p = text;
11976   char *q = text;
11977   char ch;
11978
11979   do {
11980     ch = *p++;
11981     if (ch == '\r') continue;
11982     *q++ = ch;
11983   } while (ch != '\0');
11984 }
11985
11986 void
11987 AppendComment(index, text)
11988      int index;
11989      char *text;
11990 {
11991     int oldlen, len;
11992     char *old;
11993
11994     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11995
11996     CrushCRs(text);
11997     while (*text == '\n') text++;
11998     len = strlen(text);
11999     while (len > 0 && text[len - 1] == '\n') len--;
12000
12001     if (len == 0) return;
12002
12003     if (commentList[index] != NULL) {
12004         old = commentList[index];
12005         oldlen = strlen(old);
12006         commentList[index] = (char *) malloc(oldlen + len + 2);
12007         strcpy(commentList[index], old);
12008         free(old);
12009         strncpy(&commentList[index][oldlen], text, len);
12010         commentList[index][oldlen + len] = '\n';
12011         commentList[index][oldlen + len + 1] = NULLCHAR;
12012     } else {
12013         commentList[index] = (char *) malloc(len + 2);
12014         strncpy(commentList[index], text, len);
12015         commentList[index][len] = '\n';
12016         commentList[index][len + 1] = NULLCHAR;
12017     }
12018 }
12019
12020 static char * FindStr( char * text, char * sub_text )
12021 {
12022     char * result = strstr( text, sub_text );
12023
12024     if( result != NULL ) {
12025         result += strlen( sub_text );
12026     }
12027
12028     return result;
12029 }
12030
12031 /* [AS] Try to extract PV info from PGN comment */
12032 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12033 char *GetInfoFromComment( int index, char * text )
12034 {
12035     char * sep = text;
12036
12037     if( text != NULL && index > 0 ) {
12038         int score = 0;
12039         int depth = 0;
12040         int time = -1, sec = 0, deci;
12041         char * s_eval = FindStr( text, "[%eval " );
12042         char * s_emt = FindStr( text, "[%emt " );
12043
12044         if( s_eval != NULL || s_emt != NULL ) {
12045             /* New style */
12046             char delim;
12047
12048             if( s_eval != NULL ) {
12049                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12050                     return text;
12051                 }
12052
12053                 if( delim != ']' ) {
12054                     return text;
12055                 }
12056             }
12057
12058             if( s_emt != NULL ) {
12059             }
12060         }
12061         else {
12062             /* We expect something like: [+|-]nnn.nn/dd */
12063             int score_lo = 0;
12064
12065             sep = strchr( text, '/' );
12066             if( sep == NULL || sep < (text+4) ) {
12067                 return text;
12068             }
12069
12070             time = -1; sec = -1; deci = -1;
12071             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12072                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12073                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12074                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12075                 return text;
12076             }
12077
12078             if( score_lo < 0 || score_lo >= 100 ) {
12079                 return text;
12080             }
12081
12082             if(sec >= 0) time = 600*time + 10*sec; else
12083             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12084
12085             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12086
12087             /* [HGM] PV time: now locate end of PV info */
12088             while( *++sep >= '0' && *sep <= '9'); // strip depth
12089             if(time >= 0)
12090             while( *++sep >= '0' && *sep <= '9'); // strip time
12091             if(sec >= 0)
12092             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12093             if(deci >= 0)
12094             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12095             while(*sep == ' ') sep++;
12096         }
12097
12098         if( depth <= 0 ) {
12099             return text;
12100         }
12101
12102         if( time < 0 ) {
12103             time = -1;
12104         }
12105
12106         pvInfoList[index-1].depth = depth;
12107         pvInfoList[index-1].score = score;
12108         pvInfoList[index-1].time  = 10*time; // centi-sec
12109     }
12110     return sep;
12111 }
12112
12113 void
12114 SendToProgram(message, cps)
12115      char *message;
12116      ChessProgramState *cps;
12117 {
12118     int count, outCount, error;
12119     char buf[MSG_SIZ];
12120
12121     if (cps->pr == NULL) return;
12122     Attention(cps);
12123     
12124     if (appData.debugMode) {
12125         TimeMark now;
12126         GetTimeMark(&now);
12127         fprintf(debugFP, "%ld >%-6s: %s", 
12128                 SubtractTimeMarks(&now, &programStartTime),
12129                 cps->which, message);
12130     }
12131     
12132     count = strlen(message);
12133     outCount = OutputToProcess(cps->pr, message, count, &error);
12134     if (outCount < count && !exiting 
12135                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12136         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12137         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12138             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12139                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12140                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12141             } else {
12142                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12143             }
12144             gameInfo.resultDetails = buf;
12145         }
12146         DisplayFatalError(buf, error, 1);
12147     }
12148 }
12149
12150 void
12151 ReceiveFromProgram(isr, closure, message, count, error)
12152      InputSourceRef isr;
12153      VOIDSTAR closure;
12154      char *message;
12155      int count;
12156      int error;
12157 {
12158     char *end_str;
12159     char buf[MSG_SIZ];
12160     ChessProgramState *cps = (ChessProgramState *)closure;
12161
12162     if (isr != cps->isr) return; /* Killed intentionally */
12163     if (count <= 0) {
12164         if (count == 0) {
12165             sprintf(buf,
12166                     _("Error: %s chess program (%s) exited unexpectedly"),
12167                     cps->which, cps->program);
12168         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12169                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12170                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12171                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12172                 } else {
12173                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12174                 }
12175                 gameInfo.resultDetails = buf;
12176             }
12177             RemoveInputSource(cps->isr);
12178             DisplayFatalError(buf, 0, 1);
12179         } else {
12180             sprintf(buf,
12181                     _("Error reading from %s chess program (%s)"),
12182                     cps->which, cps->program);
12183             RemoveInputSource(cps->isr);
12184
12185             /* [AS] Program is misbehaving badly... kill it */
12186             if( count == -2 ) {
12187                 DestroyChildProcess( cps->pr, 9 );
12188                 cps->pr = NoProc;
12189             }
12190
12191             DisplayFatalError(buf, error, 1);
12192         }
12193         return;
12194     }
12195     
12196     if ((end_str = strchr(message, '\r')) != NULL)
12197       *end_str = NULLCHAR;
12198     if ((end_str = strchr(message, '\n')) != NULL)
12199       *end_str = NULLCHAR;
12200     
12201     if (appData.debugMode) {
12202         TimeMark now; int print = 1;
12203         char *quote = ""; char c; int i;
12204
12205         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12206                 char start = message[0];
12207                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12208                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12209                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12210                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12211                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12212                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12213                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12214                         { quote = "# "; print = (appData.engineComments == 2); }
12215                 message[0] = start; // restore original message
12216         }
12217         if(print) {
12218                 GetTimeMark(&now);
12219                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12220                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12221                         quote,
12222                         message);
12223         }
12224     }
12225
12226     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12227     if (appData.icsEngineAnalyze) {
12228         if (strstr(message, "whisper") != NULL ||
12229              strstr(message, "kibitz") != NULL || 
12230             strstr(message, "tellics") != NULL) return;
12231     }
12232
12233     HandleMachineMove(message, cps);
12234 }
12235
12236
12237 void
12238 SendTimeControl(cps, mps, tc, inc, sd, st)
12239      ChessProgramState *cps;
12240      int mps, inc, sd, st;
12241      long tc;
12242 {
12243     char buf[MSG_SIZ];
12244     int seconds;
12245
12246     if( timeControl_2 > 0 ) {
12247         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12248             tc = timeControl_2;
12249         }
12250     }
12251     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12252     inc /= cps->timeOdds;
12253     st  /= cps->timeOdds;
12254
12255     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12256
12257     if (st > 0) {
12258       /* Set exact time per move, normally using st command */
12259       if (cps->stKludge) {
12260         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12261         seconds = st % 60;
12262         if (seconds == 0) {
12263           sprintf(buf, "level 1 %d\n", st/60);
12264         } else {
12265           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12266         }
12267       } else {
12268         sprintf(buf, "st %d\n", st);
12269       }
12270     } else {
12271       /* Set conventional or incremental time control, using level command */
12272       if (seconds == 0) {
12273         /* Note old gnuchess bug -- minutes:seconds used to not work.
12274            Fixed in later versions, but still avoid :seconds
12275            when seconds is 0. */
12276         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12277       } else {
12278         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12279                 seconds, inc/1000);
12280       }
12281     }
12282     SendToProgram(buf, cps);
12283
12284     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12285     /* Orthogonally, limit search to given depth */
12286     if (sd > 0) {
12287       if (cps->sdKludge) {
12288         sprintf(buf, "depth\n%d\n", sd);
12289       } else {
12290         sprintf(buf, "sd %d\n", sd);
12291       }
12292       SendToProgram(buf, cps);
12293     }
12294
12295     if(cps->nps > 0) { /* [HGM] nps */
12296         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12297         else {
12298                 sprintf(buf, "nps %d\n", cps->nps);
12299               SendToProgram(buf, cps);
12300         }
12301     }
12302 }
12303
12304 ChessProgramState *WhitePlayer()
12305 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12306 {
12307     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12308        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12309         return &second;
12310     return &first;
12311 }
12312
12313 void
12314 SendTimeRemaining(cps, machineWhite)
12315      ChessProgramState *cps;
12316      int /*boolean*/ machineWhite;
12317 {
12318     char message[MSG_SIZ];
12319     long time, otime;
12320
12321     /* Note: this routine must be called when the clocks are stopped
12322        or when they have *just* been set or switched; otherwise
12323        it will be off by the time since the current tick started.
12324     */
12325     if (machineWhite) {
12326         time = whiteTimeRemaining / 10;
12327         otime = blackTimeRemaining / 10;
12328     } else {
12329         time = blackTimeRemaining / 10;
12330         otime = whiteTimeRemaining / 10;
12331     }
12332     /* [HGM] translate opponent's time by time-odds factor */
12333     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12334     if (appData.debugMode) {
12335         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12336     }
12337
12338     if (time <= 0) time = 1;
12339     if (otime <= 0) otime = 1;
12340     
12341     sprintf(message, "time %ld\n", time);
12342     SendToProgram(message, cps);
12343
12344     sprintf(message, "otim %ld\n", otime);
12345     SendToProgram(message, cps);
12346 }
12347
12348 int
12349 BoolFeature(p, name, loc, cps)
12350      char **p;
12351      char *name;
12352      int *loc;
12353      ChessProgramState *cps;
12354 {
12355   char buf[MSG_SIZ];
12356   int len = strlen(name);
12357   int val;
12358   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12359     (*p) += len + 1;
12360     sscanf(*p, "%d", &val);
12361     *loc = (val != 0);
12362     while (**p && **p != ' ') (*p)++;
12363     sprintf(buf, "accepted %s\n", name);
12364     SendToProgram(buf, cps);
12365     return TRUE;
12366   }
12367   return FALSE;
12368 }
12369
12370 int
12371 IntFeature(p, name, loc, cps)
12372      char **p;
12373      char *name;
12374      int *loc;
12375      ChessProgramState *cps;
12376 {
12377   char buf[MSG_SIZ];
12378   int len = strlen(name);
12379   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12380     (*p) += len + 1;
12381     sscanf(*p, "%d", loc);
12382     while (**p && **p != ' ') (*p)++;
12383     sprintf(buf, "accepted %s\n", name);
12384     SendToProgram(buf, cps);
12385     return TRUE;
12386   }
12387   return FALSE;
12388 }
12389
12390 int
12391 StringFeature(p, name, loc, cps)
12392      char **p;
12393      char *name;
12394      char loc[];
12395      ChessProgramState *cps;
12396 {
12397   char buf[MSG_SIZ];
12398   int len = strlen(name);
12399   if (strncmp((*p), name, len) == 0
12400       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12401     (*p) += len + 2;
12402     sscanf(*p, "%[^\"]", loc);
12403     while (**p && **p != '\"') (*p)++;
12404     if (**p == '\"') (*p)++;
12405     sprintf(buf, "accepted %s\n", name);
12406     SendToProgram(buf, cps);
12407     return TRUE;
12408   }
12409   return FALSE;
12410 }
12411
12412 int 
12413 ParseOption(Option *opt, ChessProgramState *cps)
12414 // [HGM] options: process the string that defines an engine option, and determine
12415 // name, type, default value, and allowed value range
12416 {
12417         char *p, *q, buf[MSG_SIZ];
12418         int n, min = (-1)<<31, max = 1<<31, def;
12419
12420         if(p = strstr(opt->name, " -spin ")) {
12421             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12422             if(max < min) max = min; // enforce consistency
12423             if(def < min) def = min;
12424             if(def > max) def = max;
12425             opt->value = def;
12426             opt->min = min;
12427             opt->max = max;
12428             opt->type = Spin;
12429         } else if((p = strstr(opt->name, " -slider "))) {
12430             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12431             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12432             if(max < min) max = min; // enforce consistency
12433             if(def < min) def = min;
12434             if(def > max) def = max;
12435             opt->value = def;
12436             opt->min = min;
12437             opt->max = max;
12438             opt->type = Spin; // Slider;
12439         } else if((p = strstr(opt->name, " -string "))) {
12440             opt->textValue = p+9;
12441             opt->type = TextBox;
12442         } else if((p = strstr(opt->name, " -file "))) {
12443             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12444             opt->textValue = p+7;
12445             opt->type = TextBox; // FileName;
12446         } else if((p = strstr(opt->name, " -path "))) {
12447             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12448             opt->textValue = p+7;
12449             opt->type = TextBox; // PathName;
12450         } else if(p = strstr(opt->name, " -check ")) {
12451             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12452             opt->value = (def != 0);
12453             opt->type = CheckBox;
12454         } else if(p = strstr(opt->name, " -combo ")) {
12455             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12456             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12457             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12458             opt->value = n = 0;
12459             while(q = StrStr(q, " /// ")) {
12460                 n++; *q = 0;    // count choices, and null-terminate each of them
12461                 q += 5;
12462                 if(*q == '*') { // remember default, which is marked with * prefix
12463                     q++;
12464                     opt->value = n;
12465                 }
12466                 cps->comboList[cps->comboCnt++] = q;
12467             }
12468             cps->comboList[cps->comboCnt++] = NULL;
12469             opt->max = n + 1;
12470             opt->type = ComboBox;
12471         } else if(p = strstr(opt->name, " -button")) {
12472             opt->type = Button;
12473         } else if(p = strstr(opt->name, " -save")) {
12474             opt->type = SaveButton;
12475         } else return FALSE;
12476         *p = 0; // terminate option name
12477         // now look if the command-line options define a setting for this engine option.
12478         if(cps->optionSettings && cps->optionSettings[0])
12479             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12480         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12481                 sprintf(buf, "option %s", p);
12482                 if(p = strstr(buf, ",")) *p = 0;
12483                 strcat(buf, "\n");
12484                 SendToProgram(buf, cps);
12485         }
12486         return TRUE;
12487 }
12488
12489 void
12490 FeatureDone(cps, val)
12491      ChessProgramState* cps;
12492      int val;
12493 {
12494   DelayedEventCallback cb = GetDelayedEvent();
12495   if ((cb == InitBackEnd3 && cps == &first) ||
12496       (cb == TwoMachinesEventIfReady && cps == &second)) {
12497     CancelDelayedEvent();
12498     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12499   }
12500   cps->initDone = val;
12501 }
12502
12503 /* Parse feature command from engine */
12504 void
12505 ParseFeatures(args, cps)
12506      char* args;
12507      ChessProgramState *cps;  
12508 {
12509   char *p = args;
12510   char *q;
12511   int val;
12512   char buf[MSG_SIZ];
12513
12514   for (;;) {
12515     while (*p == ' ') p++;
12516     if (*p == NULLCHAR) return;
12517
12518     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12519     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12520     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12521     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12522     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12523     if (BoolFeature(&p, "reuse", &val, cps)) {
12524       /* Engine can disable reuse, but can't enable it if user said no */
12525       if (!val) cps->reuse = FALSE;
12526       continue;
12527     }
12528     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12529     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12530       if (gameMode == TwoMachinesPlay) {
12531         DisplayTwoMachinesTitle();
12532       } else {
12533         DisplayTitle("");
12534       }
12535       continue;
12536     }
12537     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12538     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12539     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12540     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12541     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12542     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12543     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12544     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12545     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12546     if (IntFeature(&p, "done", &val, cps)) {
12547       FeatureDone(cps, val);
12548       continue;
12549     }
12550     /* Added by Tord: */
12551     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12552     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12553     /* End of additions by Tord */
12554
12555     /* [HGM] added features: */
12556     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12557     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12558     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12559     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12560     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12561     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12562     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12563         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12564             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12565             SendToProgram(buf, cps);
12566             continue;
12567         }
12568         if(cps->nrOptions >= MAX_OPTIONS) {
12569             cps->nrOptions--;
12570             sprintf(buf, "%s engine has too many options\n", cps->which);
12571             DisplayError(buf, 0);
12572         }
12573         continue;
12574     }
12575     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12576     /* End of additions by HGM */
12577
12578     /* unknown feature: complain and skip */
12579     q = p;
12580     while (*q && *q != '=') q++;
12581     sprintf(buf, "rejected %.*s\n", q-p, p);
12582     SendToProgram(buf, cps);
12583     p = q;
12584     if (*p == '=') {
12585       p++;
12586       if (*p == '\"') {
12587         p++;
12588         while (*p && *p != '\"') p++;
12589         if (*p == '\"') p++;
12590       } else {
12591         while (*p && *p != ' ') p++;
12592       }
12593     }
12594   }
12595
12596 }
12597
12598 void
12599 PeriodicUpdatesEvent(newState)
12600      int newState;
12601 {
12602     if (newState == appData.periodicUpdates)
12603       return;
12604
12605     appData.periodicUpdates=newState;
12606
12607     /* Display type changes, so update it now */
12608     DisplayAnalysis();
12609
12610     /* Get the ball rolling again... */
12611     if (newState) {
12612         AnalysisPeriodicEvent(1);
12613         StartAnalysisClock();
12614     }
12615 }
12616
12617 void
12618 PonderNextMoveEvent(newState)
12619      int newState;
12620 {
12621     if (newState == appData.ponderNextMove) return;
12622     if (gameMode == EditPosition) EditPositionDone();
12623     if (newState) {
12624         SendToProgram("hard\n", &first);
12625         if (gameMode == TwoMachinesPlay) {
12626             SendToProgram("hard\n", &second);
12627         }
12628     } else {
12629         SendToProgram("easy\n", &first);
12630         thinkOutput[0] = NULLCHAR;
12631         if (gameMode == TwoMachinesPlay) {
12632             SendToProgram("easy\n", &second);
12633         }
12634     }
12635     appData.ponderNextMove = newState;
12636 }
12637
12638 void
12639 NewSettingEvent(option, command, value)
12640      char *command;
12641      int option, value;
12642 {
12643     char buf[MSG_SIZ];
12644
12645     if (gameMode == EditPosition) EditPositionDone();
12646     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12647     SendToProgram(buf, &first);
12648     if (gameMode == TwoMachinesPlay) {
12649         SendToProgram(buf, &second);
12650     }
12651 }
12652
12653 void
12654 ShowThinkingEvent()
12655 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12656 {
12657     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12658     int newState = appData.showThinking
12659         // [HGM] thinking: other features now need thinking output as well
12660         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12661     
12662     if (oldState == newState) return;
12663     oldState = newState;
12664     if (gameMode == EditPosition) EditPositionDone();
12665     if (oldState) {
12666         SendToProgram("post\n", &first);
12667         if (gameMode == TwoMachinesPlay) {
12668             SendToProgram("post\n", &second);
12669         }
12670     } else {
12671         SendToProgram("nopost\n", &first);
12672         thinkOutput[0] = NULLCHAR;
12673         if (gameMode == TwoMachinesPlay) {
12674             SendToProgram("nopost\n", &second);
12675         }
12676     }
12677 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12678 }
12679
12680 void
12681 AskQuestionEvent(title, question, replyPrefix, which)
12682      char *title; char *question; char *replyPrefix; char *which;
12683 {
12684   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12685   if (pr == NoProc) return;
12686   AskQuestion(title, question, replyPrefix, pr);
12687 }
12688
12689 void
12690 DisplayMove(moveNumber)
12691      int moveNumber;
12692 {
12693     char message[MSG_SIZ];
12694     char res[MSG_SIZ];
12695     char cpThinkOutput[MSG_SIZ];
12696
12697     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12698     
12699     if (moveNumber == forwardMostMove - 1 || 
12700         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12701
12702         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12703
12704         if (strchr(cpThinkOutput, '\n')) {
12705             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12706         }
12707     } else {
12708         *cpThinkOutput = NULLCHAR;
12709     }
12710
12711     /* [AS] Hide thinking from human user */
12712     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12713         *cpThinkOutput = NULLCHAR;
12714         if( thinkOutput[0] != NULLCHAR ) {
12715             int i;
12716
12717             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12718                 cpThinkOutput[i] = '.';
12719             }
12720             cpThinkOutput[i] = NULLCHAR;
12721             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12722         }
12723     }
12724
12725     if (moveNumber == forwardMostMove - 1 &&
12726         gameInfo.resultDetails != NULL) {
12727         if (gameInfo.resultDetails[0] == NULLCHAR) {
12728             sprintf(res, " %s", PGNResult(gameInfo.result));
12729         } else {
12730             sprintf(res, " {%s} %s",
12731                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12732         }
12733     } else {
12734         res[0] = NULLCHAR;
12735     }
12736
12737     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12738         DisplayMessage(res, cpThinkOutput);
12739     } else {
12740         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12741                 WhiteOnMove(moveNumber) ? " " : ".. ",
12742                 parseList[moveNumber], res);
12743         DisplayMessage(message, cpThinkOutput);
12744     }
12745 }
12746
12747 void
12748 DisplayAnalysisText(text)
12749      char *text;
12750 {
12751     char buf[MSG_SIZ];
12752
12753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12754                || appData.icsEngineAnalyze) {
12755         sprintf(buf, "Analysis (%s)", first.tidy);
12756         AnalysisPopUp(buf, text);
12757     }
12758 }
12759
12760 static int
12761 only_one_move(str)
12762      char *str;
12763 {
12764     while (*str && isspace(*str)) ++str;
12765     while (*str && !isspace(*str)) ++str;
12766     if (!*str) return 1;
12767     while (*str && isspace(*str)) ++str;
12768     if (!*str) return 1;
12769     return 0;
12770 }
12771
12772 void
12773 DisplayAnalysis()
12774 {
12775     char buf[MSG_SIZ];
12776     char lst[MSG_SIZ / 2];
12777     double nps;
12778     static char *xtra[] = { "", " (--)", " (++)" };
12779     int h, m, s, cs;
12780   
12781     if (programStats.time == 0) {
12782         programStats.time = 1;
12783     }
12784   
12785     if (programStats.got_only_move) {
12786         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12787     } else {
12788         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12789
12790         nps = (u64ToDouble(programStats.nodes) /
12791              ((double)programStats.time /100.0));
12792
12793         cs = programStats.time % 100;
12794         s = programStats.time / 100;
12795         h = (s / (60*60));
12796         s = s - h*60*60;
12797         m = (s/60);
12798         s = s - m*60;
12799
12800         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12801           if (programStats.move_name[0] != NULLCHAR) {
12802             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12803                     programStats.depth,
12804                     programStats.nr_moves-programStats.moves_left,
12805                     programStats.nr_moves, programStats.move_name,
12806                     ((float)programStats.score)/100.0, lst,
12807                     only_one_move(lst)?
12808                     xtra[programStats.got_fail] : "",
12809                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12810           } else {
12811             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12812                     programStats.depth,
12813                     programStats.nr_moves-programStats.moves_left,
12814                     programStats.nr_moves, ((float)programStats.score)/100.0,
12815                     lst,
12816                     only_one_move(lst)?
12817                     xtra[programStats.got_fail] : "",
12818                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12819           }
12820         } else {
12821             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12822                     programStats.depth,
12823                     ((float)programStats.score)/100.0,
12824                     lst,
12825                     only_one_move(lst)?
12826                     xtra[programStats.got_fail] : "",
12827                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12828         }
12829     }
12830     DisplayAnalysisText(buf);
12831 }
12832
12833 void
12834 DisplayComment(moveNumber, text)
12835      int moveNumber;
12836      char *text;
12837 {
12838     char title[MSG_SIZ];
12839     char buf[8000]; // comment can be long!
12840     int score, depth;
12841
12842     if( appData.autoDisplayComment ) {
12843         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12844             strcpy(title, "Comment");
12845         } else {
12846             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12847                     WhiteOnMove(moveNumber) ? " " : ".. ",
12848                     parseList[moveNumber]);
12849         }
12850         // [HGM] PV info: display PV info together with (or as) comment
12851         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12852             if(text == NULL) text = "";                                           
12853             score = pvInfoList[moveNumber].score;
12854             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12855                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12856             text = buf;
12857         }
12858     } else title[0] = 0;
12859
12860     if (text != NULL)
12861         CommentPopUp(title, text);
12862 }
12863
12864 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12865  * might be busy thinking or pondering.  It can be omitted if your
12866  * gnuchess is configured to stop thinking immediately on any user
12867  * input.  However, that gnuchess feature depends on the FIONREAD
12868  * ioctl, which does not work properly on some flavors of Unix.
12869  */
12870 void
12871 Attention(cps)
12872      ChessProgramState *cps;
12873 {
12874 #if ATTENTION
12875     if (!cps->useSigint) return;
12876     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12877     switch (gameMode) {
12878       case MachinePlaysWhite:
12879       case MachinePlaysBlack:
12880       case TwoMachinesPlay:
12881       case IcsPlayingWhite:
12882       case IcsPlayingBlack:
12883       case AnalyzeMode:
12884       case AnalyzeFile:
12885         /* Skip if we know it isn't thinking */
12886         if (!cps->maybeThinking) return;
12887         if (appData.debugMode)
12888           fprintf(debugFP, "Interrupting %s\n", cps->which);
12889         InterruptChildProcess(cps->pr);
12890         cps->maybeThinking = FALSE;
12891         break;
12892       default:
12893         break;
12894     }
12895 #endif /*ATTENTION*/
12896 }
12897
12898 int
12899 CheckFlags()
12900 {
12901     if (whiteTimeRemaining <= 0) {
12902         if (!whiteFlag) {
12903             whiteFlag = TRUE;
12904             if (appData.icsActive) {
12905                 if (appData.autoCallFlag &&
12906                     gameMode == IcsPlayingBlack && !blackFlag) {
12907                   SendToICS(ics_prefix);
12908                   SendToICS("flag\n");
12909                 }
12910             } else {
12911                 if (blackFlag) {
12912                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12913                 } else {
12914                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12915                     if (appData.autoCallFlag) {
12916                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12917                         return TRUE;
12918                     }
12919                 }
12920             }
12921         }
12922     }
12923     if (blackTimeRemaining <= 0) {
12924         if (!blackFlag) {
12925             blackFlag = TRUE;
12926             if (appData.icsActive) {
12927                 if (appData.autoCallFlag &&
12928                     gameMode == IcsPlayingWhite && !whiteFlag) {
12929                   SendToICS(ics_prefix);
12930                   SendToICS("flag\n");
12931                 }
12932             } else {
12933                 if (whiteFlag) {
12934                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12935                 } else {
12936                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12937                     if (appData.autoCallFlag) {
12938                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12939                         return TRUE;
12940                     }
12941                 }
12942             }
12943         }
12944     }
12945     return FALSE;
12946 }
12947
12948 void
12949 CheckTimeControl()
12950 {
12951     if (!appData.clockMode || appData.icsActive ||
12952         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12953
12954     /*
12955      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12956      */
12957     if ( !WhiteOnMove(forwardMostMove) )
12958         /* White made time control */
12959         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12960         /* [HGM] time odds: correct new time quota for time odds! */
12961                                             / WhitePlayer()->timeOdds;
12962       else
12963         /* Black made time control */
12964         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12965                                             / WhitePlayer()->other->timeOdds;
12966 }
12967
12968 void
12969 DisplayBothClocks()
12970 {
12971     int wom = gameMode == EditPosition ?
12972       !blackPlaysFirst : WhiteOnMove(currentMove);
12973     DisplayWhiteClock(whiteTimeRemaining, wom);
12974     DisplayBlackClock(blackTimeRemaining, !wom);
12975 }
12976
12977
12978 /* Timekeeping seems to be a portability nightmare.  I think everyone
12979    has ftime(), but I'm really not sure, so I'm including some ifdefs
12980    to use other calls if you don't.  Clocks will be less accurate if
12981    you have neither ftime nor gettimeofday.
12982 */
12983
12984 /* VS 2008 requires the #include outside of the function */
12985 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12986 #include <sys/timeb.h>
12987 #endif
12988
12989 /* Get the current time as a TimeMark */
12990 void
12991 GetTimeMark(tm)
12992      TimeMark *tm;
12993 {
12994 #if HAVE_GETTIMEOFDAY
12995
12996     struct timeval timeVal;
12997     struct timezone timeZone;
12998
12999     gettimeofday(&timeVal, &timeZone);
13000     tm->sec = (long) timeVal.tv_sec; 
13001     tm->ms = (int) (timeVal.tv_usec / 1000L);
13002
13003 #else /*!HAVE_GETTIMEOFDAY*/
13004 #if HAVE_FTIME
13005
13006 // include <sys/timeb.h> / moved to just above start of function
13007     struct timeb timeB;
13008
13009     ftime(&timeB);
13010     tm->sec = (long) timeB.time;
13011     tm->ms = (int) timeB.millitm;
13012
13013 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13014     tm->sec = (long) time(NULL);
13015     tm->ms = 0;
13016 #endif
13017 #endif
13018 }
13019
13020 /* Return the difference in milliseconds between two
13021    time marks.  We assume the difference will fit in a long!
13022 */
13023 long
13024 SubtractTimeMarks(tm2, tm1)
13025      TimeMark *tm2, *tm1;
13026 {
13027     return 1000L*(tm2->sec - tm1->sec) +
13028            (long) (tm2->ms - tm1->ms);
13029 }
13030
13031
13032 /*
13033  * Code to manage the game clocks.
13034  *
13035  * In tournament play, black starts the clock and then white makes a move.
13036  * We give the human user a slight advantage if he is playing white---the
13037  * clocks don't run until he makes his first move, so it takes zero time.
13038  * Also, we don't account for network lag, so we could get out of sync
13039  * with GNU Chess's clock -- but then, referees are always right.  
13040  */
13041
13042 static TimeMark tickStartTM;
13043 static long intendedTickLength;
13044
13045 long
13046 NextTickLength(timeRemaining)
13047      long timeRemaining;
13048 {
13049     long nominalTickLength, nextTickLength;
13050
13051     if (timeRemaining > 0L && timeRemaining <= 10000L)
13052       nominalTickLength = 100L;
13053     else
13054       nominalTickLength = 1000L;
13055     nextTickLength = timeRemaining % nominalTickLength;
13056     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13057
13058     return nextTickLength;
13059 }
13060
13061 /* Adjust clock one minute up or down */
13062 void
13063 AdjustClock(Boolean which, int dir)
13064 {
13065     if(which) blackTimeRemaining += 60000*dir;
13066     else      whiteTimeRemaining += 60000*dir;
13067     DisplayBothClocks();
13068 }
13069
13070 /* Stop clocks and reset to a fresh time control */
13071 void
13072 ResetClocks() 
13073 {
13074     (void) StopClockTimer();
13075     if (appData.icsActive) {
13076         whiteTimeRemaining = blackTimeRemaining = 0;
13077     } else { /* [HGM] correct new time quote for time odds */
13078         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13079         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13080     }
13081     if (whiteFlag || blackFlag) {
13082         DisplayTitle("");
13083         whiteFlag = blackFlag = FALSE;
13084     }
13085     DisplayBothClocks();
13086 }
13087
13088 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13089
13090 /* Decrement running clock by amount of time that has passed */
13091 void
13092 DecrementClocks()
13093 {
13094     long timeRemaining;
13095     long lastTickLength, fudge;
13096     TimeMark now;
13097
13098     if (!appData.clockMode) return;
13099     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13100         
13101     GetTimeMark(&now);
13102
13103     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13104
13105     /* Fudge if we woke up a little too soon */
13106     fudge = intendedTickLength - lastTickLength;
13107     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13108
13109     if (WhiteOnMove(forwardMostMove)) {
13110         if(whiteNPS >= 0) lastTickLength = 0;
13111         timeRemaining = whiteTimeRemaining -= lastTickLength;
13112         DisplayWhiteClock(whiteTimeRemaining - fudge,
13113                           WhiteOnMove(currentMove));
13114     } else {
13115         if(blackNPS >= 0) lastTickLength = 0;
13116         timeRemaining = blackTimeRemaining -= lastTickLength;
13117         DisplayBlackClock(blackTimeRemaining - fudge,
13118                           !WhiteOnMove(currentMove));
13119     }
13120
13121     if (CheckFlags()) return;
13122         
13123     tickStartTM = now;
13124     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13125     StartClockTimer(intendedTickLength);
13126
13127     /* if the time remaining has fallen below the alarm threshold, sound the
13128      * alarm. if the alarm has sounded and (due to a takeback or time control
13129      * with increment) the time remaining has increased to a level above the
13130      * threshold, reset the alarm so it can sound again. 
13131      */
13132     
13133     if (appData.icsActive && appData.icsAlarm) {
13134
13135         /* make sure we are dealing with the user's clock */
13136         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13137                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13138            )) return;
13139
13140         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13141             alarmSounded = FALSE;
13142         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13143             PlayAlarmSound();
13144             alarmSounded = TRUE;
13145         }
13146     }
13147 }
13148
13149
13150 /* A player has just moved, so stop the previously running
13151    clock and (if in clock mode) start the other one.
13152    We redisplay both clocks in case we're in ICS mode, because
13153    ICS gives us an update to both clocks after every move.
13154    Note that this routine is called *after* forwardMostMove
13155    is updated, so the last fractional tick must be subtracted
13156    from the color that is *not* on move now.
13157 */
13158 void
13159 SwitchClocks()
13160 {
13161     long lastTickLength;
13162     TimeMark now;
13163     int flagged = FALSE;
13164
13165     GetTimeMark(&now);
13166
13167     if (StopClockTimer() && appData.clockMode) {
13168         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13169         if (WhiteOnMove(forwardMostMove)) {
13170             if(blackNPS >= 0) lastTickLength = 0;
13171             blackTimeRemaining -= lastTickLength;
13172            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13173 //         if(pvInfoList[forwardMostMove-1].time == -1)
13174                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13175                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13176         } else {
13177            if(whiteNPS >= 0) lastTickLength = 0;
13178            whiteTimeRemaining -= lastTickLength;
13179            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13180 //         if(pvInfoList[forwardMostMove-1].time == -1)
13181                  pvInfoList[forwardMostMove-1].time = 
13182                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13183         }
13184         flagged = CheckFlags();
13185     }
13186     CheckTimeControl();
13187
13188     if (flagged || !appData.clockMode) return;
13189
13190     switch (gameMode) {
13191       case MachinePlaysBlack:
13192       case MachinePlaysWhite:
13193       case BeginningOfGame:
13194         if (pausing) return;
13195         break;
13196
13197       case EditGame:
13198       case PlayFromGameFile:
13199       case IcsExamining:
13200         return;
13201
13202       default:
13203         break;
13204     }
13205
13206     tickStartTM = now;
13207     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13208       whiteTimeRemaining : blackTimeRemaining);
13209     StartClockTimer(intendedTickLength);
13210 }
13211         
13212
13213 /* Stop both clocks */
13214 void
13215 StopClocks()
13216 {       
13217     long lastTickLength;
13218     TimeMark now;
13219
13220     if (!StopClockTimer()) return;
13221     if (!appData.clockMode) return;
13222
13223     GetTimeMark(&now);
13224
13225     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13226     if (WhiteOnMove(forwardMostMove)) {
13227         if(whiteNPS >= 0) lastTickLength = 0;
13228         whiteTimeRemaining -= lastTickLength;
13229         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13230     } else {
13231         if(blackNPS >= 0) lastTickLength = 0;
13232         blackTimeRemaining -= lastTickLength;
13233         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13234     }
13235     CheckFlags();
13236 }
13237         
13238 /* Start clock of player on move.  Time may have been reset, so
13239    if clock is already running, stop and restart it. */
13240 void
13241 StartClocks()
13242 {
13243     (void) StopClockTimer(); /* in case it was running already */
13244     DisplayBothClocks();
13245     if (CheckFlags()) return;
13246
13247     if (!appData.clockMode) return;
13248     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13249
13250     GetTimeMark(&tickStartTM);
13251     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13252       whiteTimeRemaining : blackTimeRemaining);
13253
13254    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13255     whiteNPS = blackNPS = -1; 
13256     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13257        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13258         whiteNPS = first.nps;
13259     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13260        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13261         blackNPS = first.nps;
13262     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13263         whiteNPS = second.nps;
13264     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13265         blackNPS = second.nps;
13266     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13267
13268     StartClockTimer(intendedTickLength);
13269 }
13270
13271 char *
13272 TimeString(ms)
13273      long ms;
13274 {
13275     long second, minute, hour, day;
13276     char *sign = "";
13277     static char buf[32];
13278     
13279     if (ms > 0 && ms <= 9900) {
13280       /* convert milliseconds to tenths, rounding up */
13281       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13282
13283       sprintf(buf, " %03.1f ", tenths/10.0);
13284       return buf;
13285     }
13286
13287     /* convert milliseconds to seconds, rounding up */
13288     /* use floating point to avoid strangeness of integer division
13289        with negative dividends on many machines */
13290     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13291
13292     if (second < 0) {
13293         sign = "-";
13294         second = -second;
13295     }
13296     
13297     day = second / (60 * 60 * 24);
13298     second = second % (60 * 60 * 24);
13299     hour = second / (60 * 60);
13300     second = second % (60 * 60);
13301     minute = second / 60;
13302     second = second % 60;
13303     
13304     if (day > 0)
13305       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13306               sign, day, hour, minute, second);
13307     else if (hour > 0)
13308       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13309     else
13310       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13311     
13312     return buf;
13313 }
13314
13315
13316 /*
13317  * This is necessary because some C libraries aren't ANSI C compliant yet.
13318  */
13319 char *
13320 StrStr(string, match)
13321      char *string, *match;
13322 {
13323     int i, length;
13324     
13325     length = strlen(match);
13326     
13327     for (i = strlen(string) - length; i >= 0; i--, string++)
13328       if (!strncmp(match, string, length))
13329         return string;
13330     
13331     return NULL;
13332 }
13333
13334 char *
13335 StrCaseStr(string, match)
13336      char *string, *match;
13337 {
13338     int i, j, length;
13339     
13340     length = strlen(match);
13341     
13342     for (i = strlen(string) - length; i >= 0; i--, string++) {
13343         for (j = 0; j < length; j++) {
13344             if (ToLower(match[j]) != ToLower(string[j]))
13345               break;
13346         }
13347         if (j == length) return string;
13348     }
13349
13350     return NULL;
13351 }
13352
13353 #ifndef _amigados
13354 int
13355 StrCaseCmp(s1, s2)
13356      char *s1, *s2;
13357 {
13358     char c1, c2;
13359     
13360     for (;;) {
13361         c1 = ToLower(*s1++);
13362         c2 = ToLower(*s2++);
13363         if (c1 > c2) return 1;
13364         if (c1 < c2) return -1;
13365         if (c1 == NULLCHAR) return 0;
13366     }
13367 }
13368
13369
13370 int
13371 ToLower(c)
13372      int c;
13373 {
13374     return isupper(c) ? tolower(c) : c;
13375 }
13376
13377
13378 int
13379 ToUpper(c)
13380      int c;
13381 {
13382     return islower(c) ? toupper(c) : c;
13383 }
13384 #endif /* !_amigados    */
13385
13386 char *
13387 StrSave(s)
13388      char *s;
13389 {
13390     char *ret;
13391
13392     if ((ret = (char *) malloc(strlen(s) + 1))) {
13393         strcpy(ret, s);
13394     }
13395     return ret;
13396 }
13397
13398 char *
13399 StrSavePtr(s, savePtr)
13400      char *s, **savePtr;
13401 {
13402     if (*savePtr) {
13403         free(*savePtr);
13404     }
13405     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13406         strcpy(*savePtr, s);
13407     }
13408     return(*savePtr);
13409 }
13410
13411 char *
13412 PGNDate()
13413 {
13414     time_t clock;
13415     struct tm *tm;
13416     char buf[MSG_SIZ];
13417
13418     clock = time((time_t *)NULL);
13419     tm = localtime(&clock);
13420     sprintf(buf, "%04d.%02d.%02d",
13421             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13422     return StrSave(buf);
13423 }
13424
13425
13426 char *
13427 PositionToFEN(move, overrideCastling)
13428      int move;
13429      char *overrideCastling;
13430 {
13431     int i, j, fromX, fromY, toX, toY;
13432     int whiteToPlay;
13433     char buf[128];
13434     char *p, *q;
13435     int emptycount;
13436     ChessSquare piece;
13437
13438     whiteToPlay = (gameMode == EditPosition) ?
13439       !blackPlaysFirst : (move % 2 == 0);
13440     p = buf;
13441
13442     /* Piece placement data */
13443     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13444         emptycount = 0;
13445         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13446             if (boards[move][i][j] == EmptySquare) {
13447                 emptycount++;
13448             } else { ChessSquare piece = boards[move][i][j];
13449                 if (emptycount > 0) {
13450                     if(emptycount<10) /* [HGM] can be >= 10 */
13451                         *p++ = '0' + emptycount;
13452                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13453                     emptycount = 0;
13454                 }
13455                 if(PieceToChar(piece) == '+') {
13456                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13457                     *p++ = '+';
13458                     piece = (ChessSquare)(DEMOTED piece);
13459                 } 
13460                 *p++ = PieceToChar(piece);
13461                 if(p[-1] == '~') {
13462                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13463                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13464                     *p++ = '~';
13465                 }
13466             }
13467         }
13468         if (emptycount > 0) {
13469             if(emptycount<10) /* [HGM] can be >= 10 */
13470                 *p++ = '0' + emptycount;
13471             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13472             emptycount = 0;
13473         }
13474         *p++ = '/';
13475     }
13476     *(p - 1) = ' ';
13477
13478     /* [HGM] print Crazyhouse or Shogi holdings */
13479     if( gameInfo.holdingsWidth ) {
13480         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13481         q = p;
13482         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13483             piece = boards[move][i][BOARD_WIDTH-1];
13484             if( piece != EmptySquare )
13485               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13486                   *p++ = PieceToChar(piece);
13487         }
13488         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13489             piece = boards[move][BOARD_HEIGHT-i-1][0];
13490             if( piece != EmptySquare )
13491               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13492                   *p++ = PieceToChar(piece);
13493         }
13494
13495         if( q == p ) *p++ = '-';
13496         *p++ = ']';
13497         *p++ = ' ';
13498     }
13499
13500     /* Active color */
13501     *p++ = whiteToPlay ? 'w' : 'b';
13502     *p++ = ' ';
13503
13504   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13505     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13506   } else {
13507   if(nrCastlingRights) {
13508      q = p;
13509      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13510        /* [HGM] write directly from rights */
13511            if(castlingRights[move][2] >= 0 &&
13512               castlingRights[move][0] >= 0   )
13513                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13514            if(castlingRights[move][2] >= 0 &&
13515               castlingRights[move][1] >= 0   )
13516                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13517            if(castlingRights[move][5] >= 0 &&
13518               castlingRights[move][3] >= 0   )
13519                 *p++ = castlingRights[move][3] + AAA;
13520            if(castlingRights[move][5] >= 0 &&
13521               castlingRights[move][4] >= 0   )
13522                 *p++ = castlingRights[move][4] + AAA;
13523      } else {
13524
13525         /* [HGM] write true castling rights */
13526         if( nrCastlingRights == 6 ) {
13527             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13528                castlingRights[move][2] >= 0  ) *p++ = 'K';
13529             if(castlingRights[move][1] == BOARD_LEFT &&
13530                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13531             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13532                castlingRights[move][5] >= 0  ) *p++ = 'k';
13533             if(castlingRights[move][4] == BOARD_LEFT &&
13534                castlingRights[move][5] >= 0  ) *p++ = 'q';
13535         }
13536      }
13537      if (q == p) *p++ = '-'; /* No castling rights */
13538      *p++ = ' ';
13539   }
13540
13541   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13542      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13543     /* En passant target square */
13544     if (move > backwardMostMove) {
13545         fromX = moveList[move - 1][0] - AAA;
13546         fromY = moveList[move - 1][1] - ONE;
13547         toX = moveList[move - 1][2] - AAA;
13548         toY = moveList[move - 1][3] - ONE;
13549         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13550             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13551             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13552             fromX == toX) {
13553             /* 2-square pawn move just happened */
13554             *p++ = toX + AAA;
13555             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13556         } else {
13557             *p++ = '-';
13558         }
13559     } else {
13560         *p++ = '-';
13561     }
13562     *p++ = ' ';
13563   }
13564   }
13565
13566     /* [HGM] find reversible plies */
13567     {   int i = 0, j=move;
13568
13569         if (appData.debugMode) { int k;
13570             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13571             for(k=backwardMostMove; k<=forwardMostMove; k++)
13572                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13573
13574         }
13575
13576         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13577         if( j == backwardMostMove ) i += initialRulePlies;
13578         sprintf(p, "%d ", i);
13579         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13580     }
13581     /* Fullmove number */
13582     sprintf(p, "%d", (move / 2) + 1);
13583     
13584     return StrSave(buf);
13585 }
13586
13587 Boolean
13588 ParseFEN(board, blackPlaysFirst, fen)
13589     Board board;
13590      int *blackPlaysFirst;
13591      char *fen;
13592 {
13593     int i, j;
13594     char *p;
13595     int emptycount;
13596     ChessSquare piece;
13597
13598     p = fen;
13599
13600     /* [HGM] by default clear Crazyhouse holdings, if present */
13601     if(gameInfo.holdingsWidth) {
13602        for(i=0; i<BOARD_HEIGHT; i++) {
13603            board[i][0]             = EmptySquare; /* black holdings */
13604            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13605            board[i][1]             = (ChessSquare) 0; /* black counts */
13606            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13607        }
13608     }
13609
13610     /* Piece placement data */
13611     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13612         j = 0;
13613         for (;;) {
13614             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13615                 if (*p == '/') p++;
13616                 emptycount = gameInfo.boardWidth - j;
13617                 while (emptycount--)
13618                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13619                 break;
13620 #if(BOARD_SIZE >= 10)
13621             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13622                 p++; emptycount=10;
13623                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13624                 while (emptycount--)
13625                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13626 #endif
13627             } else if (isdigit(*p)) {
13628                 emptycount = *p++ - '0';
13629                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13630                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13631                 while (emptycount--)
13632                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13633             } else if (*p == '+' || isalpha(*p)) {
13634                 if (j >= gameInfo.boardWidth) return FALSE;
13635                 if(*p=='+') {
13636                     piece = CharToPiece(*++p);
13637                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13638                     piece = (ChessSquare) (PROMOTED piece ); p++;
13639                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13640                 } else piece = CharToPiece(*p++);
13641
13642                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13643                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13644                     piece = (ChessSquare) (PROMOTED piece);
13645                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13646                     p++;
13647                 }
13648                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13649             } else {
13650                 return FALSE;
13651             }
13652         }
13653     }
13654     while (*p == '/' || *p == ' ') p++;
13655
13656     /* [HGM] look for Crazyhouse holdings here */
13657     while(*p==' ') p++;
13658     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13659         if(*p == '[') p++;
13660         if(*p == '-' ) *p++; /* empty holdings */ else {
13661             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13662             /* if we would allow FEN reading to set board size, we would   */
13663             /* have to add holdings and shift the board read so far here   */
13664             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13665                 *p++;
13666                 if((int) piece >= (int) BlackPawn ) {
13667                     i = (int)piece - (int)BlackPawn;
13668                     i = PieceToNumber((ChessSquare)i);
13669                     if( i >= gameInfo.holdingsSize ) return FALSE;
13670                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13671                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13672                 } else {
13673                     i = (int)piece - (int)WhitePawn;
13674                     i = PieceToNumber((ChessSquare)i);
13675                     if( i >= gameInfo.holdingsSize ) return FALSE;
13676                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13677                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13678                 }
13679             }
13680         }
13681         if(*p == ']') *p++;
13682     }
13683
13684     while(*p == ' ') p++;
13685
13686     /* Active color */
13687     switch (*p++) {
13688       case 'w':
13689         *blackPlaysFirst = FALSE;
13690         break;
13691       case 'b': 
13692         *blackPlaysFirst = TRUE;
13693         break;
13694       default:
13695         return FALSE;
13696     }
13697
13698     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13699     /* return the extra info in global variiables             */
13700
13701     /* set defaults in case FEN is incomplete */
13702     FENepStatus = EP_UNKNOWN;
13703     for(i=0; i<nrCastlingRights; i++ ) {
13704         FENcastlingRights[i] =
13705             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13706     }   /* assume possible unless obviously impossible */
13707     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13708     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13709     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13710     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13711     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13712     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13713     FENrulePlies = 0;
13714
13715     while(*p==' ') p++;
13716     if(nrCastlingRights) {
13717       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13718           /* castling indicator present, so default becomes no castlings */
13719           for(i=0; i<nrCastlingRights; i++ ) {
13720                  FENcastlingRights[i] = -1;
13721           }
13722       }
13723       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13724              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13725              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13726              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13727         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13728
13729         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13730             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13731             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13732         }
13733         switch(c) {
13734           case'K':
13735               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13736               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13737               FENcastlingRights[2] = whiteKingFile;
13738               break;
13739           case'Q':
13740               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13741               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13742               FENcastlingRights[2] = whiteKingFile;
13743               break;
13744           case'k':
13745               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13746               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13747               FENcastlingRights[5] = blackKingFile;
13748               break;
13749           case'q':
13750               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13751               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13752               FENcastlingRights[5] = blackKingFile;
13753           case '-':
13754               break;
13755           default: /* FRC castlings */
13756               if(c >= 'a') { /* black rights */
13757                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13758                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13759                   if(i == BOARD_RGHT) break;
13760                   FENcastlingRights[5] = i;
13761                   c -= AAA;
13762                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13763                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13764                   if(c > i)
13765                       FENcastlingRights[3] = c;
13766                   else
13767                       FENcastlingRights[4] = c;
13768               } else { /* white rights */
13769                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13770                     if(board[0][i] == WhiteKing) break;
13771                   if(i == BOARD_RGHT) break;
13772                   FENcastlingRights[2] = i;
13773                   c -= AAA - 'a' + 'A';
13774                   if(board[0][c] >= WhiteKing) break;
13775                   if(c > i)
13776                       FENcastlingRights[0] = c;
13777                   else
13778                       FENcastlingRights[1] = c;
13779               }
13780         }
13781       }
13782     if (appData.debugMode) {
13783         fprintf(debugFP, "FEN castling rights:");
13784         for(i=0; i<nrCastlingRights; i++)
13785         fprintf(debugFP, " %d", FENcastlingRights[i]);
13786         fprintf(debugFP, "\n");
13787     }
13788
13789       while(*p==' ') p++;
13790     }
13791
13792     /* read e.p. field in games that know e.p. capture */
13793     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13794        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13795       if(*p=='-') {
13796         p++; FENepStatus = EP_NONE;
13797       } else {
13798          char c = *p++ - AAA;
13799
13800          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13801          if(*p >= '0' && *p <='9') *p++;
13802          FENepStatus = c;
13803       }
13804     }
13805
13806
13807     if(sscanf(p, "%d", &i) == 1) {
13808         FENrulePlies = i; /* 50-move ply counter */
13809         /* (The move number is still ignored)    */
13810     }
13811
13812     return TRUE;
13813 }
13814       
13815 void
13816 EditPositionPasteFEN(char *fen)
13817 {
13818   if (fen != NULL) {
13819     Board initial_position;
13820
13821     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13822       DisplayError(_("Bad FEN position in clipboard"), 0);
13823       return ;
13824     } else {
13825       int savedBlackPlaysFirst = blackPlaysFirst;
13826       EditPositionEvent();
13827       blackPlaysFirst = savedBlackPlaysFirst;
13828       CopyBoard(boards[0], initial_position);
13829           /* [HGM] copy FEN attributes as well */
13830           {   int i;
13831               initialRulePlies = FENrulePlies;
13832               epStatus[0] = FENepStatus;
13833               for( i=0; i<nrCastlingRights; i++ )
13834                   castlingRights[0][i] = FENcastlingRights[i];
13835           }
13836       EditPositionDone();
13837       DisplayBothClocks();
13838       DrawPosition(FALSE, boards[currentMove]);
13839     }
13840   }
13841 }