dos 2 unix conversion of backend.c
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
4  *
5  * Copyright 1991 by Digital Equipment Corporation, Maynard,
6  * Massachusetts.  Enhancements Copyright
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
8  * Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 /* [AS] Also useful here for debugging */
53 #ifdef WIN32
54 #include <windows.h>
55
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
57
58 #else
59
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
61
62 #endif
63
64 #include "config.h"
65
66 #include <assert.h>
67 #include <stdio.h>
68 #include <ctype.h>
69 #include <errno.h>
70 #include <sys/types.h>
71 #include <sys/stat.h>
72 #include <math.h>
73 #include <ctype.h>
74
75 #if STDC_HEADERS
76 # include <stdlib.h>
77 # include <string.h>
78 #else /* not STDC_HEADERS */
79 # if HAVE_STRING_H
80 #  include <string.h>
81 # else /* not HAVE_STRING_H */
82 #  include <strings.h>
83 # endif /* not HAVE_STRING_H */
84 #endif /* not STDC_HEADERS */
85
86 #if HAVE_SYS_FCNTL_H
87 # include <sys/fcntl.h>
88 #else /* not HAVE_SYS_FCNTL_H */
89 # if HAVE_FCNTL_H
90 #  include <fcntl.h>
91 # endif /* HAVE_FCNTL_H */
92 #endif /* not HAVE_SYS_FCNTL_H */
93
94 #if TIME_WITH_SYS_TIME
95 # include <sys/time.h>
96 # include <time.h>
97 #else
98 # if HAVE_SYS_TIME_H
99 #  include <sys/time.h>
100 # else
101 #  include <time.h>
102 # endif
103 #endif
104
105 #if defined(_amigados) && !defined(__GNUC__)
106 struct timezone {
107     int tz_minuteswest;
108     int tz_dsttime;
109 };
110 extern int gettimeofday(struct timeval *, struct timezone *);
111 #endif
112
113 #if HAVE_UNISTD_H
114 # include <unistd.h>
115 #endif
116
117 #include "common.h"
118 #include "frontend.h"
119 #include "backend.h"
120 #include "parser.h"
121 #include "moves.h"
122 #if ZIPPY
123 # include "zippy.h"
124 #endif
125 #include "backendz.h"
126 #include "gettext.h" 
127  
128 #ifdef ENABLE_NLS 
129 # define _(s) gettext (s) 
130 # define N_(s) gettext_noop (s) 
131 #else 
132 # define _(s) (s) 
133 # define N_(s) s 
134 #endif 
135
136
137 /* A point in time */
138 typedef struct {
139     long sec;  /* Assuming this is >= 32 bits */
140     int ms;    /* Assuming this is >= 16 bits */
141 } TimeMark;
142
143 int establish P((void));
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
145                          char *buf, int count, int error));
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
147                       char *buf, int count, int error));
148 void SendToICS P((char *s));
149 void SendToICSDelayed P((char *s, long msdelay));
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
151                       int toX, int toY));
152 void InitPosition P((int redraw));
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
154 int AutoPlayOneMove P((void));
155 int LoadGameOneMove P((ChessMove readAhead));
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
157 int LoadPositionFromFile P((char *filename, int n, char *title));
158 int SavePositionToFile P((char *filename));
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
160                   Board board, char *castle, char *ep));
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
164                    /*char*/int promoChar));
165 void BackwardInner P((int target));
166 void ForwardInner P((int target));
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
168 void EditPositionDone P((void));
169 void PrintOpponents P((FILE *fp));
170 void PrintPosition P((FILE *fp, int move));
171 void StartChessProgram P((ChessProgramState *cps));
172 void SendToProgram P((char *message, ChessProgramState *cps));
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
175                            char *buf, int count, int error));
176 void SendTimeControl P((ChessProgramState *cps,
177                         int mps, long tc, int inc, int sd, int st));
178 char *TimeControlTagValue P((void));
179 void Attention P((ChessProgramState *cps));
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
181 void ResurrectChessProgram P((void));
182 void DisplayComment P((int moveNumber, char *text));
183 void DisplayMove P((int moveNumber));
184 void DisplayAnalysis P((void));
185
186 void ParseGameHistory P((char *game));
187 void ParseBoard12 P((char *string));
188 void StartClocks P((void));
189 void SwitchClocks P((void));
190 void StopClocks P((void));
191 void ResetClocks P((void));
192 char *PGNDate P((void));
193 void SetGameInfo P((void));
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
195 int RegisterMove P((void));
196 void MakeRegisteredMove P((void));
197 void TruncateGame P((void));
198 int looking_at P((char *, int *, char *));
199 void CopyPlayerNameIntoFileName P((char **, char *));
200 char *SavePart P((char *));
201 int SaveGameOldStyle P((FILE *));
202 int SaveGamePGN P((FILE *));
203 void GetTimeMark P((TimeMark *));
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
205 int CheckFlags P((void));
206 long NextTickLength P((long));
207 void CheckTimeControl P((void));
208 void show_bytes P((FILE *, char *, int));
209 int string_to_rating P((char *str));
210 void ParseFeatures P((char* args, ChessProgramState *cps));
211 void InitBackEnd3 P((void));
212 void FeatureDone P((ChessProgramState* cps, int val));
213 void InitChessProgram P((ChessProgramState *cps, int setup));
214 void OutputKibitz(int window, char *text);
215 int PerpetualChase(int first, int last);
216 int EngineOutputIsUp();
217 void InitDrawingSizes(int x, int y);
218
219 #ifdef WIN32
220        extern void ConsoleCreate();
221 #endif
222
223 ChessProgramState *WhitePlayer();
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
225 int VerifyDisplayMode P(());
226
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
231 extern char installDir[MSG_SIZ];
232
233 extern int tinyLayout, smallLayout;
234 ChessProgramStats programStats;
235 static int exiting = 0; /* [HGM] moved to top */
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
242 int opponentKibitzes;
243
244 /* States for ics_getting_history */
245 #define H_FALSE 0
246 #define H_REQUESTED 1
247 #define H_GOT_REQ_HEADER 2
248 #define H_GOT_UNREQ_HEADER 3
249 #define H_GETTING_MOVES 4
250 #define H_GOT_UNWANTED_HEADER 5
251
252 /* whosays values for GameEnds */
253 #define GE_ICS 0
254 #define GE_ENGINE 1
255 #define GE_PLAYER 2
256 #define GE_FILE 3
257 #define GE_XBOARD 4
258 #define GE_ENGINE1 5
259 #define GE_ENGINE2 6
260
261 /* Maximum number of games in a cmail message */
262 #define CMAIL_MAX_GAMES 20
263
264 /* Different types of move when calling RegisterMove */
265 #define CMAIL_MOVE   0
266 #define CMAIL_RESIGN 1
267 #define CMAIL_DRAW   2
268 #define CMAIL_ACCEPT 3
269
270 /* Different types of result to remember for each game */
271 #define CMAIL_NOT_RESULT 0
272 #define CMAIL_OLD_RESULT 1
273 #define CMAIL_NEW_RESULT 2
274
275 /* Telnet protocol constants */
276 #define TN_WILL 0373
277 #define TN_WONT 0374
278 #define TN_DO   0375
279 #define TN_DONT 0376
280 #define TN_IAC  0377
281 #define TN_ECHO 0001
282 #define TN_SGA  0003
283 #define TN_PORT 23
284
285 /* [AS] */
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
287 {
288     assert( dst != NULL );
289     assert( src != NULL );
290     assert( count > 0 );
291
292     strncpy( dst, src, count );
293     dst[ count-1 ] = '\0';
294     return dst;
295 }
296
297 #if 0
298 //[HGM] for future use? Conditioned out for now to suppress warning.
299 static char * safeStrCat( char * dst, const char * src, size_t count )
300 {
301     size_t  dst_len;
302
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     dst_len = strlen(dst);
308
309     assert( count > dst_len ); /* Buffer size must be greater than current length */
310
311     safeStrCpy( dst + dst_len, src, count - dst_len );
312
313     return dst;
314 }
315 #endif
316
317 /* Some compiler can't cast u64 to double
318  * This function do the job for us:
319
320  * We use the highest bit for cast, this only
321  * works if the highest bit is not
322  * in use (This should not happen)
323  *
324  * We used this for all compiler
325  */
326 double
327 u64ToDouble(u64 value)
328 {
329   double r;
330   u64 tmp = value & u64Const(0x7fffffffffffffff);
331   r = (double)(s64)tmp;
332   if (value & u64Const(0x8000000000000000))
333        r +=  9.2233720368547758080e18; /* 2^63 */
334  return r;
335 }
336
337 /* Fake up flags for now, as we aren't keeping track of castling
338    availability yet. [HGM] Change of logic: the flag now only
339    indicates the type of castlings allowed by the rule of the game.
340    The actual rights themselves are maintained in the array
341    castlingRights, as part of the game history, and are not probed
342    by this function.
343  */
344 int
345 PosFlags(index)
346 {
347   int flags = F_ALL_CASTLE_OK;
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
349   switch (gameInfo.variant) {
350   case VariantSuicide:
351     flags &= ~F_ALL_CASTLE_OK;
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
353     flags |= F_IGNORE_CHECK;
354   case VariantLosers:
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
356     break;
357   case VariantAtomic:
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359     break;
360   case VariantKriegspiel:
361     flags |= F_KRIEGSPIEL_CAPTURE;
362     break;
363   case VariantCapaRandom: 
364   case VariantFischeRandom:
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
366   case VariantNoCastle:
367   case VariantShatranj:
368   case VariantCourier:
369     flags &= ~F_ALL_CASTLE_OK;
370     break;
371   default:
372     break;
373   }
374   return flags;
375 }
376
377 FILE *gameFileFP, *debugFP;
378
379 /* 
380     [AS] Note: sometimes, the sscanf() function is used to parse the input
381     into a fixed-size buffer. Because of this, we must be prepared to
382     receive strings as long as the size of the input buffer, which is currently
383     set to 4K for Windows and 8K for the rest.
384     So, we must either allocate sufficiently large buffers here, or
385     reduce the size of the input buffer in the input reading part.
386 */
387
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
390 char thinkOutput1[MSG_SIZ*10];
391
392 ChessProgramState first, second;
393
394 /* premove variables */
395 int premoveToX = 0;
396 int premoveToY = 0;
397 int premoveFromX = 0;
398 int premoveFromY = 0;
399 int premovePromoChar = 0;
400 int gotPremove = 0;
401 Boolean alarmSounded;
402 /* end premove variables */
403
404 char *ics_prefix = "$";
405 int ics_type = ICS_GENERIC;
406
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
408 int pauseExamForwardMostMove = 0;
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
415 int whiteFlag = FALSE, blackFlag = FALSE;
416 int userOfferedDraw = FALSE;
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
419 int cmailMoveType[CMAIL_MAX_GAMES];
420 long ics_clock_paused = 0;
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
423 GameMode gameMode = BeginningOfGame;
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
427 int hiddenThinkOutputState = 0; /* [AS] */
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
429 int adjudicateLossPlies = 6;
430 char white_holding[64], black_holding[64];
431 TimeMark lastNodeCountTime;
432 long lastNodeCount=0;
433 int have_sent_ICS_logon = 0;
434 int movesPerSession;
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
436 long timeControl_2; /* [AS] Allow separate time controls */
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
438 long timeRemaining[2][MAX_MOVES];
439 int matchGame = 0;
440 TimeMark programStartTime;
441 char ics_handle[MSG_SIZ];
442 int have_set_title = 0;
443
444 /* animateTraining preserves the state of appData.animate
445  * when Training mode is activated. This allows the
446  * response to be animated when appData.animate == TRUE and
447  * appData.animateDragging == TRUE.
448  */
449 Boolean animateTraining;
450
451 GameInfo gameInfo;
452
453 AppData appData;
454
455 Board boards[MAX_MOVES];
456 /* [HGM] Following 7 needed for accurate legality tests: */
457 char  epStatus[MAX_MOVES];
458 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
459 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
460 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
462 int   initialRulePlies, FENrulePlies;
463 char  FENepStatus;
464 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
465 int loadFlag = 0; 
466 int shuffleOpenings;
467
468 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
469     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
470         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
471     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
472         BlackKing, BlackBishop, BlackKnight, BlackRook }
473 };
474
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
477         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
479         BlackKing, BlackKing, BlackKnight, BlackRook }
480 };
481
482 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
483     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
484         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
485     { BlackRook, BlackMan, BlackBishop, BlackQueen,
486         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
487 };
488
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
490     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
492     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
493         BlackKing, BlackBishop, BlackKnight, BlackRook }
494 };
495
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
497     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
498         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
499     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
500         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
501 };
502
503
504 #if (BOARD_SIZE>=10)
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
506     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
507         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
508     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
509         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
510 };
511
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
513     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
514         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
516         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
517 };
518
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
520     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
521         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
522     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
523         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
524 };
525
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
527     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
528         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
529     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
530         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
531 };
532
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
534     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
535         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
536     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
537         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
538 };
539
540 #ifdef GOTHIC
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
543         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
545         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
546 };
547 #else // !GOTHIC
548 #define GothicArray CapablancaArray
549 #endif // !GOTHIC
550
551 #ifdef FALCON
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
553     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
554         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
556         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
557 };
558 #else // !FALCON
559 #define FalconArray CapablancaArray
560 #endif // !FALCON
561
562 #else // !(BOARD_SIZE>=10)
563 #define XiangqiPosition FIDEArray
564 #define CapablancaArray FIDEArray
565 #define GothicArray FIDEArray
566 #define GreatArray FIDEArray
567 #endif // !(BOARD_SIZE>=10)
568
569 #if (BOARD_SIZE>=12)
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
571     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
572         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
574         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 };
576 #else // !(BOARD_SIZE>=12)
577 #define CourierArray CapablancaArray
578 #endif // !(BOARD_SIZE>=12)
579
580
581 Board initialPosition;
582
583
584 /* Convert str to a rating. Checks for special cases of "----",
585
586    "++++", etc. Also strips ()'s */
587 int
588 string_to_rating(str)
589   char *str;
590 {
591   while(*str && !isdigit(*str)) ++str;
592   if (!*str)
593     return 0;   /* One of the special "no rating" cases */
594   else
595     return atoi(str);
596 }
597
598 void
599 ClearProgramStats()
600 {
601     /* Init programStats */
602     programStats.movelist[0] = 0;
603     programStats.depth = 0;
604     programStats.nr_moves = 0;
605     programStats.moves_left = 0;
606     programStats.nodes = 0;
607     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
608     programStats.score = 0;
609     programStats.got_only_move = 0;
610     programStats.got_fail = 0;
611     programStats.line_is_book = 0;
612 }
613
614 void
615 InitBackEnd1()
616 {
617     int matched, min, sec;
618
619     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620
621     GetTimeMark(&programStartTime);
622     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
623
624     ClearProgramStats();
625     programStats.ok_to_send = 1;
626     programStats.seen_stat = 0;
627
628     /*
629      * Initialize game list
630      */
631     ListNew(&gameList);
632
633
634     /*
635      * Internet chess server status
636      */
637     if (appData.icsActive) {
638         appData.matchMode = FALSE;
639         appData.matchGames = 0;
640 #if ZIPPY       
641         appData.noChessProgram = !appData.zippyPlay;
642 #else
643         appData.zippyPlay = FALSE;
644         appData.zippyTalk = FALSE;
645         appData.noChessProgram = TRUE;
646 #endif
647         if (*appData.icsHelper != NULLCHAR) {
648             appData.useTelnet = TRUE;
649             appData.telnetProgram = appData.icsHelper;
650         }
651     } else {
652         appData.zippyTalk = appData.zippyPlay = FALSE;
653     }
654
655     /* [AS] Initialize pv info list [HGM] and game state */
656     {
657         int i, j;
658
659         for( i=0; i<MAX_MOVES; i++ ) {
660             pvInfoList[i].depth = -1;
661             epStatus[i]=EP_NONE;
662             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
663         }
664     }
665
666     /*
667      * Parse timeControl resource
668      */
669     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
670                           appData.movesPerSession)) {
671         char buf[MSG_SIZ];
672         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
673         DisplayFatalError(buf, 0, 2);
674     }
675
676     /*
677      * Parse searchTime resource
678      */
679     if (*appData.searchTime != NULLCHAR) {
680         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681         if (matched == 1) {
682             searchTime = min * 60;
683         } else if (matched == 2) {
684             searchTime = min * 60 + sec;
685         } else {
686             char buf[MSG_SIZ];
687             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
688             DisplayFatalError(buf, 0, 2);
689         }
690     }
691
692     /* [AS] Adjudication threshold */
693     adjudicateLossThreshold = appData.adjudicateLossThreshold;
694     
695     first.which = "first";
696     second.which = "second";
697     first.maybeThinking = second.maybeThinking = FALSE;
698     first.pr = second.pr = NoProc;
699     first.isr = second.isr = NULL;
700     first.sendTime = second.sendTime = 2;
701     first.sendDrawOffers = 1;
702     if (appData.firstPlaysBlack) {
703         first.twoMachinesColor = "black\n";
704         second.twoMachinesColor = "white\n";
705     } else {
706         first.twoMachinesColor = "white\n";
707         second.twoMachinesColor = "black\n";
708     }
709     first.program = appData.firstChessProgram;
710     second.program = appData.secondChessProgram;
711     first.host = appData.firstHost;
712     second.host = appData.secondHost;
713     first.dir = appData.firstDirectory;
714     second.dir = appData.secondDirectory;
715     first.other = &second;
716     second.other = &first;
717     first.initString = appData.initString;
718     second.initString = appData.secondInitString;
719     first.computerString = appData.firstComputerString;
720     second.computerString = appData.secondComputerString;
721     first.useSigint = second.useSigint = TRUE;
722     first.useSigterm = second.useSigterm = TRUE;
723     first.reuse = appData.reuseFirst;
724     second.reuse = appData.reuseSecond;
725     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
726     second.nps = appData.secondNPS;
727     first.useSetboard = second.useSetboard = FALSE;
728     first.useSAN = second.useSAN = FALSE;
729     first.usePing = second.usePing = FALSE;
730     first.lastPing = second.lastPing = 0;
731     first.lastPong = second.lastPong = 0;
732     first.usePlayother = second.usePlayother = FALSE;
733     first.useColors = second.useColors = TRUE;
734     first.useUsermove = second.useUsermove = FALSE;
735     first.sendICS = second.sendICS = FALSE;
736     first.sendName = second.sendName = appData.icsActive;
737     first.sdKludge = second.sdKludge = FALSE;
738     first.stKludge = second.stKludge = FALSE;
739     TidyProgramName(first.program, first.host, first.tidy);
740     TidyProgramName(second.program, second.host, second.tidy);
741     first.matchWins = second.matchWins = 0;
742     strcpy(first.variants, appData.variant);
743     strcpy(second.variants, appData.variant);
744     first.analysisSupport = second.analysisSupport = 2; /* detect */
745     first.analyzing = second.analyzing = FALSE;
746     first.initDone = second.initDone = FALSE;
747
748     /* New features added by Tord: */
749     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
750     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
751     /* End of new features added by Tord. */
752     first.fenOverride  = appData.fenOverride1;
753     second.fenOverride = appData.fenOverride2;
754
755     /* [HGM] time odds: set factor for each machine */
756     first.timeOdds  = appData.firstTimeOdds;
757     second.timeOdds = appData.secondTimeOdds;
758     { int norm = 1;
759         if(appData.timeOddsMode) {
760             norm = first.timeOdds;
761             if(norm > second.timeOdds) norm = second.timeOdds;
762         }
763         first.timeOdds /= norm;
764         second.timeOdds /= norm;
765     }
766
767     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
768     first.accumulateTC = appData.firstAccumulateTC;
769     second.accumulateTC = appData.secondAccumulateTC;
770     first.maxNrOfSessions = second.maxNrOfSessions = 1;
771
772     /* [HGM] debug */
773     first.debug = second.debug = FALSE;
774     first.supportsNPS = second.supportsNPS = UNKNOWN;
775
776     /* [HGM] options */
777     first.optionSettings  = appData.firstOptions;
778     second.optionSettings = appData.secondOptions;
779
780     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
781     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
782     first.isUCI = appData.firstIsUCI; /* [AS] */
783     second.isUCI = appData.secondIsUCI; /* [AS] */
784     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
785     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786
787     if (appData.firstProtocolVersion > PROTOVER ||
788         appData.firstProtocolVersion < 1) {
789       char buf[MSG_SIZ];
790       sprintf(buf, _("protocol version %d not supported"),
791               appData.firstProtocolVersion);
792       DisplayFatalError(buf, 0, 2);
793     } else {
794       first.protocolVersion = appData.firstProtocolVersion;
795     }
796
797     if (appData.secondProtocolVersion > PROTOVER ||
798         appData.secondProtocolVersion < 1) {
799       char buf[MSG_SIZ];
800       sprintf(buf, _("protocol version %d not supported"),
801               appData.secondProtocolVersion);
802       DisplayFatalError(buf, 0, 2);
803     } else {
804       second.protocolVersion = appData.secondProtocolVersion;
805     }
806
807     if (appData.icsActive) {
808         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
809     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
810         appData.clockMode = FALSE;
811         first.sendTime = second.sendTime = 0;
812     }
813     
814 #if ZIPPY
815     /* Override some settings from environment variables, for backward
816        compatibility.  Unfortunately it's not feasible to have the env
817        vars just set defaults, at least in xboard.  Ugh.
818     */
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
820       ZippyInit();
821     }
822 #endif
823     
824     if (appData.noChessProgram) {
825         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
826                                         + strlen(PATCHLEVEL));
827         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
828     } else {
829 #if 0
830         char *p, *q;
831         q = first.program;
832         while (*q != ' ' && *q != NULLCHAR) q++;
833         p = q;
834         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
835         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
836                                         + strlen(PATCHLEVEL) + (q - p));
837         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
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(PRODUCT) + strlen(VERSION)
842                                         + strlen(PATCHLEVEL) + strlen(first.tidy));
843         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
844 #endif
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantBerolina:   /* might work if TestLegality is off */
903       case VariantCapaRandom: /* should work */
904       case VariantJanus:      /* should work */
905       case VariantSuper:      /* experimental */
906       case VariantGreat:      /* experimental, requires legality testing to be off */
907         break;
908       }
909     }
910
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
912     InitEngineUCI( installDir, &second );
913 }
914
915 int NextIntegerFromString( char ** str, long * value )
916 {
917     int result = -1;
918     char * s = *str;
919
920     while( *s == ' ' || *s == '\t' ) {
921         s++;
922     }
923
924     *value = 0;
925
926     if( *s >= '0' && *s <= '9' ) {
927         while( *s >= '0' && *s <= '9' ) {
928             *value = *value * 10 + (*s - '0');
929             s++;
930         }
931
932         result = 0;
933     }
934
935     *str = s;
936
937     return result;
938 }
939
940 int NextTimeControlFromString( char ** str, long * value )
941 {
942     long temp;
943     int result = NextIntegerFromString( str, &temp );
944
945     if( result == 0 ) {
946         *value = temp * 60; /* Minutes */
947         if( **str == ':' ) {
948             (*str)++;
949             result = NextIntegerFromString( str, &temp );
950             *value += temp; /* Seconds */
951         }
952     }
953
954     return result;
955 }
956
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
959     int result = -1; long temp, temp2;
960
961     if(**str != '+') return -1; // old params remain in force!
962     (*str)++;
963     if( NextTimeControlFromString( str, &temp ) ) return -1;
964
965     if(**str != '/') {
966         /* time only: incremental or sudden-death time control */
967         if(**str == '+') { /* increment follows; read it */
968             (*str)++;
969             if(result = NextIntegerFromString( str, &temp2)) return -1;
970             *inc = temp2 * 1000;
971         } else *inc = 0;
972         *moves = 0; *tc = temp * 1000; 
973         return 0;
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
975
976     (*str)++; /* classical time control */
977     result = NextTimeControlFromString( str, &temp2);
978     if(result == 0) {
979         *moves = temp/60;
980         *tc    = temp2 * 1000;
981         *inc   = 0;
982     }
983     return result;
984 }
985
986 int GetTimeQuota(int movenr)
987 {   /* [HGM] get time to add from the multi-session time-control string */
988     int moves=1; /* kludge to force reading of first session */
989     long time, increment;
990     char *s = fullTimeControlString;
991
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993     do {
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996         if(movenr == -1) return time;    /* last move before new session     */
997         if(!moves) return increment;     /* current session is incremental   */
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */
999     } while(movenr >= -1);               /* try again for next session       */
1000
1001     return 0; // no new time quota on this move
1002 }
1003
1004 int
1005 ParseTimeControl(tc, ti, mps)
1006      char *tc;
1007      int ti;
1008      int mps;
1009 {
1010 #if 0
1011     int matched, min, sec;
1012
1013     matched = sscanf(tc, "%d:%d", &min, &sec);
1014     if (matched == 1) {
1015         timeControl = min * 60 * 1000;
1016     } else if (matched == 2) {
1017         timeControl = (min * 60 + sec) * 1000;
1018     } else {
1019         return FALSE;
1020     }
1021 #else
1022     long tc1;
1023     long tc2;
1024     char buf[MSG_SIZ];
1025
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1027     if(ti > 0) {
1028         if(mps)
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030         else sprintf(buf, "+%s+%d", tc, ti);
1031     } else {
1032         if(mps)
1033              sprintf(buf, "+%d/%s", mps, tc);
1034         else sprintf(buf, "+%s", tc);
1035     }
1036     fullTimeControlString = StrSave(buf);
1037
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1039         return FALSE;
1040     }
1041
1042     if( *tc == '/' ) {
1043         /* Parse second time control */
1044         tc++;
1045
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1047             return FALSE;
1048         }
1049
1050         if( tc2 == 0 ) {
1051             return FALSE;
1052         }
1053
1054         timeControl_2 = tc2 * 1000;
1055     }
1056     else {
1057         timeControl_2 = 0;
1058     }
1059
1060     if( tc1 == 0 ) {
1061         return FALSE;
1062     }
1063
1064     timeControl = tc1 * 1000;
1065 #endif
1066
1067     if (ti >= 0) {
1068         timeIncrement = ti * 1000;  /* convert to ms */
1069         movesPerSession = 0;
1070     } else {
1071         timeIncrement = 0;
1072         movesPerSession = mps;
1073     }
1074     return TRUE;
1075 }
1076
1077 void
1078 InitBackEnd2()
1079 {
1080     if (appData.debugMode) {
1081         fprintf(debugFP, "%s\n", programVersion);
1082     }
1083
1084     if (appData.matchGames > 0) {
1085         appData.matchMode = TRUE;
1086     } else if (appData.matchMode) {
1087         appData.matchGames = 1;
1088     }
1089     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090         appData.matchGames = appData.sameColorGames;
1091     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1094     }
1095     Reset(TRUE, FALSE);
1096     if (appData.noChessProgram || first.protocolVersion == 1) {
1097       InitBackEnd3();
1098     } else {
1099       /* kludge: allow timeout for initial "feature" commands */
1100       FreezeUI();
1101       DisplayMessage("", _("Starting chess program"));
1102       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1103     }
1104 }
1105
1106 void
1107 InitBackEnd3 P((void))
1108 {
1109     GameMode initialMode;
1110     char buf[MSG_SIZ];
1111     int err;
1112
1113     InitChessProgram(&first, startedFromSetupPosition);
1114
1115
1116     if (appData.icsActive) {
1117 #ifdef WIN32
1118         /* [DM] Make a console window if needed [HGM] merged ifs */
1119         ConsoleCreate(); 
1120 #endif
1121         err = establish();
1122         if (err != 0) {
1123             if (*appData.icsCommPort != NULLCHAR) {
1124                 sprintf(buf, _("Could not open comm port %s"),  
1125                         appData.icsCommPort);
1126             } else {
1127                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1128                         appData.icsHost, appData.icsPort);
1129             }
1130             DisplayFatalError(buf, err, 1);
1131             return;
1132         }
1133         SetICSMode();
1134         telnetISR =
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136         fromUserISR =
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138     } else if (appData.noChessProgram) {
1139         SetNCPMode();
1140     } else {
1141         SetGNUMode();
1142     }
1143
1144     if (*appData.cmailGameName != NULLCHAR) {
1145         SetCmailMode();
1146         OpenLoopback(&cmailPR);
1147         cmailISR =
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1149     }
1150     
1151     ThawUI();
1152     DisplayMessage("", "");
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {
1154       initialMode = BeginningOfGame;
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156       initialMode = TwoMachinesPlay;
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158       initialMode = AnalyzeFile; 
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160       initialMode = AnalyzeMode;
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162       initialMode = MachinePlaysWhite;
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164       initialMode = MachinePlaysBlack;
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166       initialMode = EditGame;
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168       initialMode = EditPosition;
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170       initialMode = Training;
1171     } else {
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173       DisplayFatalError(buf, 0, 2);
1174       return;
1175     }
1176
1177     if (appData.matchMode) {
1178         /* Set up machine vs. machine match */
1179         if (appData.noChessProgram) {
1180             DisplayFatalError(_("Can't have a match with no chess programs"),
1181                               0, 2);
1182             return;
1183         }
1184         matchMode = TRUE;
1185         matchGame = 1;
1186         if (*appData.loadGameFile != NULLCHAR) {
1187             int index = appData.loadGameIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadGameFromFile(appData.loadGameFile,
1190                                   index,
1191                                   appData.loadGameFile, FALSE)) {
1192                 DisplayFatalError(_("Bad game file"), 0, 1);
1193                 return;
1194             }
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             int index = appData.loadPositionIndex; // [HGM] autoinc
1197             if(index<0) lastIndex = index = 1;
1198             if (!LoadPositionFromFile(appData.loadPositionFile,
1199                                       index,
1200                                       appData.loadPositionFile)) {
1201                 DisplayFatalError(_("Bad position file"), 0, 1);
1202                 return;
1203             }
1204         }
1205         TwoMachinesEvent();
1206     } else if (*appData.cmailGameName != NULLCHAR) {
1207         /* Set up cmail mode */
1208         ReloadCmailMsgEvent(TRUE);
1209     } else {
1210         /* Set up other modes */
1211         if (initialMode == AnalyzeFile) {
1212           if (*appData.loadGameFile == NULLCHAR) {
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1214             return;
1215           }
1216         }
1217         if (*appData.loadGameFile != NULLCHAR) {
1218             (void) LoadGameFromFile(appData.loadGameFile,
1219                                     appData.loadGameIndex,
1220                                     appData.loadGameFile, TRUE);
1221         } else if (*appData.loadPositionFile != NULLCHAR) {
1222             (void) LoadPositionFromFile(appData.loadPositionFile,
1223                                         appData.loadPositionIndex,
1224                                         appData.loadPositionFile);
1225             /* [HGM] try to make self-starting even after FEN load */
1226             /* to allow automatic setup of fairy variants with wtm */
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228                 gameMode = BeginningOfGame;
1229                 setboardSpoiledMachineBlack = 1;
1230             }
1231             /* [HGM] loadPos: make that every new game uses the setup */
1232             /* from file as long as we do not switch variant          */
1233             if(!blackPlaysFirst) { int i;
1234                 startedFromPositionFile = TRUE;
1235                 CopyBoard(filePosition, boards[0]);
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1237             }
1238         }
1239         if (initialMode == AnalyzeMode) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1242             return;
1243           }
1244           if (appData.icsActive) {
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1246             return;
1247           }
1248           AnalyzeModeEvent();
1249         } else if (initialMode == AnalyzeFile) {
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251           ShowThinkingEvent();
1252           AnalyzeFileEvent();
1253           AnalysisPeriodicEvent(1);
1254         } else if (initialMode == MachinePlaysWhite) {
1255           if (appData.noChessProgram) {
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1257                               0, 2);
1258             return;
1259           }
1260           if (appData.icsActive) {
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1262                               0, 2);
1263             return;
1264           }
1265           MachineWhiteEvent();
1266         } else if (initialMode == MachinePlaysBlack) {
1267           if (appData.noChessProgram) {
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1269                               0, 2);
1270             return;
1271           }
1272           if (appData.icsActive) {
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1274                               0, 2);
1275             return;
1276           }
1277           MachineBlackEvent();
1278         } else if (initialMode == TwoMachinesPlay) {
1279           if (appData.noChessProgram) {
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1281                               0, 2);
1282             return;
1283           }
1284           if (appData.icsActive) {
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1286                               0, 2);
1287             return;
1288           }
1289           TwoMachinesEvent();
1290         } else if (initialMode == EditGame) {
1291           EditGameEvent();
1292         } else if (initialMode == EditPosition) {
1293           EditPositionEvent();
1294         } else if (initialMode == Training) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1297             return;
1298           }
1299           TrainingEvent();
1300         }
1301     }
1302 }
1303
1304 /*
1305  * Establish will establish a contact to a remote host.port.
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307  *  used to talk to the host.
1308  * Returns 0 if okay, error code if not.
1309  */
1310 int
1311 establish()
1312 {
1313     char buf[MSG_SIZ];
1314
1315     if (*appData.icsCommPort != NULLCHAR) {
1316         /* Talk to the host through a serial comm port */
1317         return OpenCommPort(appData.icsCommPort, &icsPR);
1318
1319     } else if (*appData.gateway != NULLCHAR) {
1320         if (*appData.remoteShell == NULLCHAR) {
1321             /* Use the rcmd protocol to run telnet program on a gateway host */
1322             snprintf(buf, sizeof(buf), "%s %s %s",
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1325
1326         } else {
1327             /* Use the rsh program to run telnet program on a gateway host */
1328             if (*appData.remoteUser == NULLCHAR) {
1329                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330                         appData.gateway, appData.telnetProgram,
1331                         appData.icsHost, appData.icsPort);
1332             } else {
1333                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334                         appData.remoteShell, appData.gateway, 
1335                         appData.remoteUser, appData.telnetProgram,
1336                         appData.icsHost, appData.icsPort);
1337             }
1338             return StartChildProcess(buf, "", &icsPR);
1339
1340         }
1341     } else if (appData.useTelnet) {
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1343
1344     } else {
1345         /* TCP socket interface differs somewhat between
1346            Unix and NT; handle details in the front end.
1347            */
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1349     }
1350 }
1351
1352 void
1353 show_bytes(fp, buf, count)
1354      FILE *fp;
1355      char *buf;
1356      int count;
1357 {
1358     while (count--) {
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360             fprintf(fp, "\\%03o", *buf & 0xff);
1361         } else {
1362             putc(*buf, fp);
1363         }
1364         buf++;
1365     }
1366     fflush(fp);
1367 }
1368
1369 /* Returns an errno value */
1370 int
1371 OutputMaybeTelnet(pr, message, count, outError)
1372      ProcRef pr;
1373      char *message;
1374      int count;
1375      int *outError;
1376 {
1377     char buf[8192], *p, *q, *buflim;
1378     int left, newcount, outcount;
1379
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381         *appData.gateway != NULLCHAR) {
1382         if (appData.debugMode) {
1383             fprintf(debugFP, ">ICS: ");
1384             show_bytes(debugFP, message, count);
1385             fprintf(debugFP, "\n");
1386         }
1387         return OutputToProcess(pr, message, count, outError);
1388     }
1389
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1391     p = message;
1392     q = buf;
1393     left = count;
1394     newcount = 0;
1395     while (left) {
1396         if (q >= buflim) {
1397             if (appData.debugMode) {
1398                 fprintf(debugFP, ">ICS: ");
1399                 show_bytes(debugFP, buf, newcount);
1400                 fprintf(debugFP, "\n");
1401             }
1402             outcount = OutputToProcess(pr, buf, newcount, outError);
1403             if (outcount < newcount) return -1; /* to be sure */
1404             q = buf;
1405             newcount = 0;
1406         }
1407         if (*p == '\n') {
1408             *q++ = '\r';
1409             newcount++;
1410         } else if (((unsigned char) *p) == TN_IAC) {
1411             *q++ = (char) TN_IAC;
1412             newcount ++;
1413         }
1414         *q++ = *p++;
1415         newcount++;
1416         left--;
1417     }
1418     if (appData.debugMode) {
1419         fprintf(debugFP, ">ICS: ");
1420         show_bytes(debugFP, buf, newcount);
1421         fprintf(debugFP, "\n");
1422     }
1423     outcount = OutputToProcess(pr, buf, newcount, outError);
1424     if (outcount < newcount) return -1; /* to be sure */
1425     return count;
1426 }
1427
1428 void
1429 read_from_player(isr, closure, message, count, error)
1430      InputSourceRef isr;
1431      VOIDSTAR closure;
1432      char *message;
1433      int count;
1434      int error;
1435 {
1436     int outError, outCount;
1437     static int gotEof = 0;
1438
1439     /* Pass data read from player on to ICS */
1440     if (count > 0) {
1441         gotEof = 0;
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443         if (outCount < count) {
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445         }
1446     } else if (count < 0) {
1447         RemoveInputSource(isr);
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449     } else if (gotEof++ > 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1452     }
1453 }
1454
1455 void
1456 SendToICS(s)
1457      char *s;
1458 {
1459     int count, outCount, outError;
1460
1461     if (icsPR == NULL) return;
1462
1463     count = strlen(s);
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465     if (outCount < count) {
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1467     }
1468 }
1469
1470 /* This is used for sending logon scripts to the ICS. Sending
1471    without a delay causes problems when using timestamp on ICC
1472    (at least on my machine). */
1473 void
1474 SendToICSDelayed(s,msdelay)
1475      char *s;
1476      long msdelay;
1477 {
1478     int count, outCount, outError;
1479
1480     if (icsPR == NULL) return;
1481
1482     count = strlen(s);
1483     if (appData.debugMode) {
1484         fprintf(debugFP, ">ICS: ");
1485         show_bytes(debugFP, s, count);
1486         fprintf(debugFP, "\n");
1487     }
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489                                       msdelay);
1490     if (outCount < count) {
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492     }
1493 }
1494
1495
1496 /* Remove all highlighting escape sequences in s
1497    Also deletes any suffix starting with '(' 
1498    */
1499 char *
1500 StripHighlightAndTitle(s)
1501      char *s;
1502 {
1503     static char retbuf[MSG_SIZ];
1504     char *p = retbuf;
1505
1506     while (*s != NULLCHAR) {
1507         while (*s == '\033') {
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;
1509             if (*s != NULLCHAR) s++;
1510         }
1511         while (*s != NULLCHAR && *s != '\033') {
1512             if (*s == '(' || *s == '[') {
1513                 *p = NULLCHAR;
1514                 return retbuf;
1515             }
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 /* Remove all highlighting escape sequences in s */
1524 char *
1525 StripHighlight(s)
1526      char *s;
1527 {
1528     static char retbuf[MSG_SIZ];
1529     char *p = retbuf;
1530
1531     while (*s != NULLCHAR) {
1532         while (*s == '\033') {
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;
1534             if (*s != NULLCHAR) s++;
1535         }
1536         while (*s != NULLCHAR && *s != '\033') {
1537             *p++ = *s++;
1538         }
1539     }
1540     *p = NULLCHAR;
1541     return retbuf;
1542 }
1543
1544 char *variantNames[] = VARIANT_NAMES;
1545 char *
1546 VariantName(v)
1547      VariantClass v;
1548 {
1549     return variantNames[v];
1550 }
1551
1552
1553 /* Identify a variant from the strings the chess servers use or the
1554    PGN Variant tag names we use. */
1555 VariantClass
1556 StringToVariant(e)
1557      char *e;
1558 {
1559     char *p;
1560     int wnum = -1;
1561     VariantClass v = VariantNormal;
1562     int i, found = FALSE;
1563     char buf[MSG_SIZ];
1564
1565     if (!e) return v;
1566
1567     /* [HGM] skip over optional board-size prefixes */
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570         while( *e++ != '_');
1571     }
1572
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574       if (StrCaseStr(e, variantNames[i])) {
1575         v = (VariantClass) i;
1576         found = TRUE;
1577         break;
1578       }
1579     }
1580
1581     if (!found) {
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583           || StrCaseStr(e, "wild/fr") 
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585         v = VariantFischeRandom;
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587                  (i = 1, p = StrCaseStr(e, "w"))) {
1588         p += i;
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1590         if (isdigit(*p)) {
1591           wnum = atoi(p);
1592         } else {
1593           wnum = -1;
1594         }
1595         switch (wnum) {
1596         case 0: /* FICS only, actually */
1597         case 1:
1598           /* Castling legal even if K starts on d-file */
1599           v = VariantWildCastle;
1600           break;
1601         case 2:
1602         case 3:
1603         case 4:
1604           /* Castling illegal even if K & R happen to start in
1605              normal positions. */
1606           v = VariantNoCastle;
1607           break;
1608         case 5:
1609         case 7:
1610         case 8:
1611         case 10:
1612         case 11:
1613         case 12:
1614         case 13:
1615         case 14:
1616         case 15:
1617         case 18:
1618         case 19:
1619           /* Castling legal iff K & R start in normal positions */
1620           v = VariantNormal;
1621           break;
1622         case 6:
1623         case 20:
1624         case 21:
1625           /* Special wilds for position setup; unclear what to do here */
1626           v = VariantLoadable;
1627           break;
1628         case 9:
1629           /* Bizarre ICC game */
1630           v = VariantTwoKings;
1631           break;
1632         case 16:
1633           v = VariantKriegspiel;
1634           break;
1635         case 17:
1636           v = VariantLosers;
1637           break;
1638         case 22:
1639           v = VariantFischeRandom;
1640           break;
1641         case 23:
1642           v = VariantCrazyhouse;
1643           break;
1644         case 24:
1645           v = VariantBughouse;
1646           break;
1647         case 25:
1648           v = Variant3Check;
1649           break;
1650         case 26:
1651           /* Not quite the same as FICS suicide! */
1652           v = VariantGiveaway;
1653           break;
1654         case 27:
1655           v = VariantAtomic;
1656           break;
1657         case 28:
1658           v = VariantShatranj;
1659           break;
1660
1661         /* Temporary names for future ICC types.  The name *will* change in 
1662            the next xboard/WinBoard release after ICC defines it. */
1663         case 29:
1664           v = Variant29;
1665           break;
1666         case 30:
1667           v = Variant30;
1668           break;
1669         case 31:
1670           v = Variant31;
1671           break;
1672         case 32:
1673           v = Variant32;
1674           break;
1675         case 33:
1676           v = Variant33;
1677           break;
1678         case 34:
1679           v = Variant34;
1680           break;
1681         case 35:
1682           v = Variant35;
1683           break;
1684         case 36:
1685           v = Variant36;
1686           break;
1687         case 37:
1688           v = VariantShogi;
1689           break;
1690         case 38:
1691           v = VariantXiangqi;
1692           break;
1693         case 39:
1694           v = VariantCourier;
1695           break;
1696         case 40:
1697           v = VariantGothic;
1698           break;
1699         case 41:
1700           v = VariantCapablanca;
1701           break;
1702         case 42:
1703           v = VariantKnightmate;
1704           break;
1705         case 43:
1706           v = VariantFairy;
1707           break;
1708         case 44:
1709           v = VariantCylinder;
1710           break;
1711         case 45:
1712           v = VariantFalcon;
1713           break;
1714         case 46:
1715           v = VariantCapaRandom;
1716           break;
1717         case 47:
1718           v = VariantBerolina;
1719           break;
1720         case 48:
1721           v = VariantJanus;
1722           break;
1723         case 49:
1724           v = VariantSuper;
1725           break;
1726         case 50:
1727           v = VariantGreat;
1728           break;
1729         case -1:
1730           /* Found "wild" or "w" in the string but no number;
1731              must assume it's normal chess. */
1732           v = VariantNormal;
1733           break;
1734         default:
1735           sprintf(buf, _("Unknown wild type %d"), wnum);
1736           DisplayError(buf, 0);
1737           v = VariantUnknown;
1738           break;
1739         }
1740       }
1741     }
1742     if (appData.debugMode) {
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744               e, wnum, VariantName(v));
1745     }
1746     return v;
1747 }
1748
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1751
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753    advance *index beyond it, and set leftover_start to the new value of
1754    *index; else return FALSE.  If pattern contains the character '*', it
1755    matches any sequence of characters not containing '\r', '\n', or the
1756    character following the '*' (if any), and the matched sequence(s) are
1757    copied into star_match.
1758    */
1759 int
1760 looking_at(buf, index, pattern)
1761      char *buf;
1762      int *index;
1763      char *pattern;
1764 {
1765     char *bufp = &buf[*index], *patternp = pattern;
1766     int star_count = 0;
1767     char *matchp = star_match[0];
1768     
1769     for (;;) {
1770         if (*patternp == NULLCHAR) {
1771             *index = leftover_start = bufp - buf;
1772             *matchp = NULLCHAR;
1773             return TRUE;
1774         }
1775         if (*bufp == NULLCHAR) return FALSE;
1776         if (*patternp == '*') {
1777             if (*bufp == *(patternp + 1)) {
1778                 *matchp = NULLCHAR;
1779                 matchp = star_match[++star_count];
1780                 patternp += 2;
1781                 bufp++;
1782                 continue;
1783             } else if (*bufp == '\n' || *bufp == '\r') {
1784                 patternp++;
1785                 if (*patternp == NULLCHAR)
1786                   continue;
1787                 else
1788                   return FALSE;
1789             } else {
1790                 *matchp++ = *bufp++;
1791                 continue;
1792             }
1793         }
1794         if (*patternp != *bufp) return FALSE;
1795         patternp++;
1796         bufp++;
1797     }
1798 }
1799
1800 void
1801 SendToPlayer(data, length)
1802      char *data;
1803      int length;
1804 {
1805     int error, outCount;
1806     outCount = OutputToProcess(NoProc, data, length, &error);
1807     if (outCount < length) {
1808         DisplayFatalError(_("Error writing to display"), error, 1);
1809     }
1810 }
1811
1812 void
1813 PackHolding(packed, holding)
1814      char packed[];
1815      char *holding;
1816 {
1817     char *p = holding;
1818     char *q = packed;
1819     int runlength = 0;
1820     int curr = 9999;
1821     do {
1822         if (*p == curr) {
1823             runlength++;
1824         } else {
1825             switch (runlength) {
1826               case 0:
1827                 break;
1828               case 1:
1829                 *q++ = curr;
1830                 break;
1831               case 2:
1832                 *q++ = curr;
1833                 *q++ = curr;
1834                 break;
1835               default:
1836                 sprintf(q, "%d", runlength);
1837                 while (*q) q++;
1838                 *q++ = curr;
1839                 break;
1840             }
1841             runlength = 1;
1842             curr = *p;
1843         }
1844     } while (*p++);
1845     *q = NULLCHAR;
1846 }
1847
1848 /* Telnet protocol requests from the front end */
1849 void
1850 TelnetRequest(ddww, option)
1851      unsigned char ddww, option;
1852 {
1853     unsigned char msg[3];
1854     int outCount, outError;
1855
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857
1858     if (appData.debugMode) {
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860         switch (ddww) {
1861           case TN_DO:
1862             ddwwStr = "DO";
1863             break;
1864           case TN_DONT:
1865             ddwwStr = "DONT";
1866             break;
1867           case TN_WILL:
1868             ddwwStr = "WILL";
1869             break;
1870           case TN_WONT:
1871             ddwwStr = "WONT";
1872             break;
1873           default:
1874             ddwwStr = buf1;
1875             sprintf(buf1, "%d", ddww);
1876             break;
1877         }
1878         switch (option) {
1879           case TN_ECHO:
1880             optionStr = "ECHO";
1881             break;
1882           default:
1883             optionStr = buf2;
1884             sprintf(buf2, "%d", option);
1885             break;
1886         }
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888     }
1889     msg[0] = TN_IAC;
1890     msg[1] = ddww;
1891     msg[2] = option;
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893     if (outCount < 3) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 void
1899 DoEcho()
1900 {
1901     if (!appData.icsActive) return;
1902     TelnetRequest(TN_DO, TN_ECHO);
1903 }
1904
1905 void
1906 DontEcho()
1907 {
1908     if (!appData.icsActive) return;
1909     TelnetRequest(TN_DONT, TN_ECHO);
1910 }
1911
1912 void
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 {
1915     /* put the holdings sent to us by the server on the board holdings area */
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917     char p;
1918     ChessSquare piece;
1919
1920     if(gameInfo.holdingsWidth < 2)  return;
1921
1922     if( (int)lowestPiece >= BlackPawn ) {
1923         holdingsColumn = 0;
1924         countsColumn = 1;
1925         holdingsStartRow = BOARD_HEIGHT-1;
1926         direction = -1;
1927     } else {
1928         holdingsColumn = BOARD_WIDTH-1;
1929         countsColumn = BOARD_WIDTH-2;
1930         holdingsStartRow = 0;
1931         direction = 1;
1932     }
1933
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935         board[i][holdingsColumn] = EmptySquare;
1936         board[i][countsColumn]   = (ChessSquare) 0;
1937     }
1938     while( (p=*holdings++) != NULLCHAR ) {
1939         piece = CharToPiece( ToUpper(p) );
1940         if(piece == EmptySquare) continue;
1941         /*j = (int) piece - (int) WhitePawn;*/
1942         j = PieceToNumber(piece);
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944         if(j < 0) continue;               /* should not happen */
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947         board[holdingsStartRow+j*direction][countsColumn]++;
1948     }
1949
1950 }
1951
1952
1953 void
1954 VariantSwitch(Board board, VariantClass newVariant)
1955 {
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959
1960    startedFromPositionFile = FALSE;
1961    if(gameInfo.variant == newVariant) return;
1962
1963    /* [HGM] This routine is called each time an assignment is made to
1964     * gameInfo.variant during a game, to make sure the board sizes
1965     * are set to match the new variant. If that means adding or deleting
1966     * holdings, we shift the playing board accordingly
1967     * This kludge is needed because in ICS observe mode, we get boards
1968     * of an ongoing game without knowing the variant, and learn about the
1969     * latter only later. This can be because of the move list we requested,
1970     * in which case the game history is refilled from the beginning anyway,
1971     * but also when receiving holdings of a crazyhouse game. In the latter
1972     * case we want to add those holdings to the already received position.
1973     */
1974
1975
1976   if (appData.debugMode) {
1977     fprintf(debugFP, "Switch board from %s to %s\n",
1978                VariantName(gameInfo.variant), VariantName(newVariant));
1979     setbuf(debugFP, NULL);
1980   }
1981     shuffleOpenings = 0;       /* [HGM] shuffle */
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983     switch(newVariant) {
1984             case VariantShogi:
1985               newWidth = 9;  newHeight = 9;
1986               gameInfo.holdingsSize = 7;
1987             case VariantBughouse:
1988             case VariantCrazyhouse:
1989               newHoldingsWidth = 2; break;
1990             default:
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;
1992     }
1993
1994     if(newWidth  != gameInfo.boardWidth  ||
1995        newHeight != gameInfo.boardHeight ||
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {
1997
1998         /* shift position to new playing area, if needed */
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000            for(i=0; i<BOARD_HEIGHT; i++) 
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003                                                      board[i][j];
2004            for(i=0; i<newHeight; i++) {
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007            }
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009            for(i=0; i<BOARD_HEIGHT; i++)
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012                                                  board[i][j];
2013         }
2014
2015         gameInfo.boardWidth  = newWidth;
2016         gameInfo.boardHeight = newHeight;
2017         gameInfo.holdingsWidth = newHoldingsWidth;
2018         gameInfo.variant = newVariant;
2019         InitDrawingSizes(-2, 0);
2020
2021         /* [HGM] The following should definitely be solved in a better way */
2022 #if 0
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025         saveEP = epStatus[0];
2026 #endif
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2028 #if 0
2029         epStatus[0] = saveEP;
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2032 #endif
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034
2035     forwardMostMove = oldForwardMostMove;
2036     backwardMostMove = oldBackwardMostMove;
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2038 }
2039
2040 static int loggedOn = FALSE;
2041
2042 /*-- Game start info cache: --*/
2043 int gs_gamenum;
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2050
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2053
2054 void
2055 read_from_ics(isr, closure, data, count, error)
2056      InputSourceRef isr;
2057      VOIDSTAR closure;
2058      char *data;
2059      int count;
2060      int error;
2061 {
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2071     
2072     static int started = STARTED_NONE;
2073     static char parse[20000];
2074     static int parse_pos = 0;
2075     static char buf[BUF_SIZE + 1];
2076     static int firstTime = TRUE, intfSet = FALSE;
2077     static ColorClass prevColor = ColorNormal;
2078     static int savingComment = FALSE;
2079     char str[500];
2080     int i, oldi;
2081     int buf_len;
2082     int next_out;
2083     int tkind;
2084     int backup;    /* [DM] For zippy color lines */
2085     char *p;
2086
2087     if (appData.debugMode) {
2088       if (!error) {
2089         fprintf(debugFP, "<ICS: ");
2090         show_bytes(debugFP, data, count);
2091         fprintf(debugFP, "\n");
2092       }
2093     }
2094
2095     if (appData.debugMode) { int f = forwardMostMove;
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2098     }
2099     if (count > 0) {
2100         /* If last read ended with a partial line that we couldn't parse,
2101            prepend it to the new read and try again. */
2102         if (leftover_len > 0) {
2103             for (i=0; i<leftover_len; i++)
2104               buf[i] = buf[leftover_start + i];
2105         }
2106
2107         /* Copy in new characters, removing nulls and \r's */
2108         buf_len = leftover_len;
2109         for (i = 0; i < count; i++) {
2110             if (data[i] != NULLCHAR && data[i] != '\r')
2111               buf[buf_len++] = data[i];
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') 
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2115         }
2116
2117         buf[buf_len] = NULLCHAR;
2118         next_out = leftover_len;
2119         leftover_start = 0;
2120         
2121         i = 0;
2122         while (i < buf_len) {
2123             /* Deal with part of the TELNET option negotiation
2124                protocol.  We refuse to do anything beyond the
2125                defaults, except that we allow the WILL ECHO option,
2126                which ICS uses to turn off password echoing when we are
2127                directly connected to it.  We reject this option
2128                if localLineEditing mode is on (always on in xboard)
2129                and we are talking to port 23, which might be a real
2130                telnet server that will try to keep WILL ECHO on permanently.
2131              */
2132             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2133                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2134                 unsigned char option;
2135                 oldi = i;
2136                 switch ((unsigned char) buf[++i]) {
2137                   case TN_WILL:
2138                     if (appData.debugMode)
2139                       fprintf(debugFP, "\n<WILL ");
2140                     switch (option = (unsigned char) buf[++i]) {
2141                       case TN_ECHO:
2142                         if (appData.debugMode)
2143                           fprintf(debugFP, "ECHO ");
2144                         /* Reply only if this is a change, according
2145                            to the protocol rules. */
2146                         if (remoteEchoOption) break;
2147                         if (appData.localLineEditing &&
2148                             atoi(appData.icsPort) == TN_PORT) {
2149                             TelnetRequest(TN_DONT, TN_ECHO);
2150                         } else {
2151                             EchoOff();
2152                             TelnetRequest(TN_DO, TN_ECHO);
2153                             remoteEchoOption = TRUE;
2154                         }
2155                         break;
2156                       default:
2157                         if (appData.debugMode)
2158                           fprintf(debugFP, "%d ", option);
2159                         /* Whatever this is, we don't want it. */
2160                         TelnetRequest(TN_DONT, option);
2161                         break;
2162                     }
2163                     break;
2164                   case TN_WONT:
2165                     if (appData.debugMode)
2166                       fprintf(debugFP, "\n<WONT ");
2167                     switch (option = (unsigned char) buf[++i]) {
2168                       case TN_ECHO:
2169                         if (appData.debugMode)
2170                           fprintf(debugFP, "ECHO ");
2171                         /* Reply only if this is a change, according
2172                            to the protocol rules. */
2173                         if (!remoteEchoOption) break;
2174                         EchoOn();
2175                         TelnetRequest(TN_DONT, TN_ECHO);
2176                         remoteEchoOption = FALSE;
2177                         break;
2178                       default:
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", (unsigned char) option);
2181                         /* Whatever this is, it must already be turned
2182                            off, because we never agree to turn on
2183                            anything non-default, so according to the
2184                            protocol rules, we don't reply. */
2185                         break;
2186                     }
2187                     break;
2188                   case TN_DO:
2189                     if (appData.debugMode)
2190                       fprintf(debugFP, "\n<DO ");
2191                     switch (option = (unsigned char) buf[++i]) {
2192                       default:
2193                         /* Whatever this is, we refuse to do it. */
2194                         if (appData.debugMode)
2195                           fprintf(debugFP, "%d ", option);
2196                         TelnetRequest(TN_WONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_DONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<DONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       default:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "%d ", option);
2207                         /* Whatever this is, we are already not doing
2208                            it, because we never agree to do anything
2209                            non-default, so according to the protocol
2210                            rules, we don't reply. */
2211                         break;
2212                     }
2213                     break;
2214                   case TN_IAC:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<IAC ");
2217                     /* Doubled IAC; pass it through */
2218                     i--;
2219                     break;
2220                   default:
2221                     if (appData.debugMode)
2222                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2223                     /* Drop all other telnet commands on the floor */
2224                     break;
2225                 }
2226                 if (oldi > next_out)
2227                   SendToPlayer(&buf[next_out], oldi - next_out);
2228                 if (++i > next_out)
2229                   next_out = i;
2230                 continue;
2231             }
2232                 
2233             /* OK, this at least will *usually* work */
2234             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2235                 loggedOn = TRUE;
2236             }
2237             
2238             if (loggedOn && !intfSet) {
2239                 if (ics_type == ICS_ICC) {
2240                   sprintf(str,
2241                           "/set-quietly interface %s\n/set-quietly style 12\n",
2242                           programVersion);
2243
2244                 } else if (ics_type == ICS_CHESSNET) {
2245                   sprintf(str, "/style 12\n");
2246                 } else {
2247                   strcpy(str, "alias $ @\n$set interface ");
2248                   strcat(str, programVersion);
2249                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2250 #ifdef WIN32
2251                   strcat(str, "$iset nohighlight 1\n");
2252 #endif
2253                   strcat(str, "$iset lock 1\n$style 12\n");
2254                 }
2255                 SendToICS(str);
2256                 intfSet = TRUE;
2257             }
2258
2259             if (started == STARTED_COMMENT) {
2260                 /* Accumulate characters in comment */
2261                 parse[parse_pos++] = buf[i];
2262                 if (buf[i] == '\n') {
2263                     parse[parse_pos] = NULLCHAR;
2264                     if(!suppressKibitz) // [HGM] kibitz
2265                         AppendComment(forwardMostMove, StripHighlight(parse));
2266                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2267                         int nrDigit = 0, nrAlph = 0, i;
2268                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2269                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2270                         parse[parse_pos] = NULLCHAR;
2271                         // try to be smart: if it does not look like search info, it should go to
2272                         // ICS interaction window after all, not to engine-output window.
2273                         for(i=0; i<parse_pos; i++) { // count letters and digits
2274                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2275                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2276                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2277                         }
2278                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2279                             int depth=0; float score;
2280                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2281                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2282                                 pvInfoList[forwardMostMove-1].depth = depth;
2283                                 pvInfoList[forwardMostMove-1].score = 100*score;
2284                             }
2285                             OutputKibitz(suppressKibitz, parse);
2286                         } else {
2287                             char tmp[MSG_SIZ];
2288                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2289                             SendToPlayer(tmp, strlen(tmp));
2290                         }
2291                     }
2292                     started = STARTED_NONE;
2293                 } else {
2294                     /* Don't match patterns against characters in chatter */
2295                     i++;
2296                     continue;
2297                 }
2298             }
2299             if (started == STARTED_CHATTER) {
2300                 if (buf[i] != '\n') {
2301                     /* Don't match patterns against characters in chatter */
2302                     i++;
2303                     continue;
2304                 }
2305                 started = STARTED_NONE;
2306             }
2307
2308             /* Kludge to deal with rcmd protocol */
2309             if (firstTime && looking_at(buf, &i, "\001*")) {
2310                 DisplayFatalError(&buf[1], 0, 1);
2311                 continue;
2312             } else {
2313                 firstTime = FALSE;
2314             }
2315
2316             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2317                 ics_type = ICS_ICC;
2318                 ics_prefix = "/";
2319                 if (appData.debugMode)
2320                   fprintf(debugFP, "ics_type %d\n", ics_type);
2321                 continue;
2322             }
2323             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2324                 ics_type = ICS_FICS;
2325                 ics_prefix = "$";
2326                 if (appData.debugMode)
2327                   fprintf(debugFP, "ics_type %d\n", ics_type);
2328                 continue;
2329             }
2330             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2331                 ics_type = ICS_CHESSNET;
2332                 ics_prefix = "/";
2333                 if (appData.debugMode)
2334                   fprintf(debugFP, "ics_type %d\n", ics_type);
2335                 continue;
2336             }
2337
2338             if (!loggedOn &&
2339                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2340                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2341                  looking_at(buf, &i, "will be \"*\""))) {
2342               strcpy(ics_handle, star_match[0]);
2343               continue;
2344             }
2345
2346             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2347               char buf[MSG_SIZ];
2348               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2349               DisplayIcsInteractionTitle(buf);
2350               have_set_title = TRUE;
2351             }
2352
2353             /* skip finger notes */
2354             if (started == STARTED_NONE &&
2355                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2356                  (buf[i] == '1' && buf[i+1] == '0')) &&
2357                 buf[i+2] == ':' && buf[i+3] == ' ') {
2358               started = STARTED_CHATTER;
2359               i += 3;
2360               continue;
2361             }
2362
2363             /* skip formula vars */
2364             if (started == STARTED_NONE &&
2365                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2366               started = STARTED_CHATTER;
2367               i += 3;
2368               continue;
2369             }
2370
2371             oldi = i;
2372             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2373             if (appData.autoKibitz && started == STARTED_NONE && 
2374                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2375                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2376                 if(looking_at(buf, &i, "* kibitzes: ") &&
2377                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2378                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2379                         suppressKibitz = TRUE;
2380                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2381                                 && (gameMode == IcsPlayingWhite)) ||
2382                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2383                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2384                             started = STARTED_CHATTER; // own kibitz we simply discard
2385                         else {
2386                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2387                             parse_pos = 0; parse[0] = NULLCHAR;
2388                             savingComment = TRUE;
2389                             suppressKibitz = gameMode != IcsObserving ? 2 :
2390                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2391                         } 
2392                         continue;
2393                 } else
2394                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2395                     started = STARTED_CHATTER;
2396                     suppressKibitz = TRUE;
2397                 }
2398             } // [HGM] kibitz: end of patch
2399
2400             if (appData.zippyTalk || appData.zippyPlay) {
2401                 /* [DM] Backup address for color zippy lines */
2402                 backup = i;
2403 #if ZIPPY
2404        #ifdef WIN32
2405                if (loggedOn == TRUE)
2406                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2407                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2408        #else
2409                 if (ZippyControl(buf, &i) ||
2410                     ZippyConverse(buf, &i) ||
2411                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2412                       loggedOn = TRUE;
2413                       if (!appData.colorize) continue;
2414                 }
2415        #endif
2416 #endif
2417             } // [DM] 'else { ' deleted
2418                 if (/* Don't color "message" or "messages" output */
2419                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2420                     looking_at(buf, &i, "*. * at *:*: ") ||
2421                     looking_at(buf, &i, "--* (*:*): ") ||
2422                     /* Regular tells and says */
2423                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2424                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2425                     looking_at(buf, &i, "* says: ") ||
2426                     /* Message notifications (same color as tells) */
2427                     looking_at(buf, &i, "* has left a message ") ||
2428                     looking_at(buf, &i, "* just sent you a message:\n") ||
2429                     /* Whispers and kibitzes */
2430                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2431                     looking_at(buf, &i, "* kibitzes: ") ||
2432                     /* Channel tells */
2433                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2434
2435                   if (tkind == 1 && strchr(star_match[0], ':')) {
2436                       /* Avoid "tells you:" spoofs in channels */
2437                      tkind = 3;
2438                   }
2439                   if (star_match[0][0] == NULLCHAR ||
2440                       strchr(star_match[0], ' ') ||
2441                       (tkind == 3 && strchr(star_match[1], ' '))) {
2442                     /* Reject bogus matches */
2443                     i = oldi;
2444                   } else {
2445                     if (appData.colorize) {
2446                       if (oldi > next_out) {
2447                         SendToPlayer(&buf[next_out], oldi - next_out);
2448                         next_out = oldi;
2449                       }
2450                       switch (tkind) {
2451                       case 1:
2452                         Colorize(ColorTell, FALSE);
2453                         curColor = ColorTell;
2454                         break;
2455                       case 2:
2456                         Colorize(ColorKibitz, FALSE);
2457                         curColor = ColorKibitz;
2458                         break;
2459                       case 3:
2460                         p = strrchr(star_match[1], '(');
2461                         if (p == NULL) {
2462                           p = star_match[1];
2463                         } else {
2464                           p++;
2465                         }
2466                         if (atoi(p) == 1) {
2467                           Colorize(ColorChannel1, FALSE);
2468                           curColor = ColorChannel1;
2469                         } else {
2470                           Colorize(ColorChannel, FALSE);
2471                           curColor = ColorChannel;
2472                         }
2473                         break;
2474                       case 5:
2475                         curColor = ColorNormal;
2476                         break;
2477                       }
2478                     }
2479                     if (started == STARTED_NONE && appData.autoComment &&
2480                         (gameMode == IcsObserving ||
2481                          gameMode == IcsPlayingWhite ||
2482                          gameMode == IcsPlayingBlack)) {
2483                       parse_pos = i - oldi;
2484                       memcpy(parse, &buf[oldi], parse_pos);
2485                       parse[parse_pos] = NULLCHAR;
2486                       started = STARTED_COMMENT;
2487                       savingComment = TRUE;
2488                     } else {
2489                       started = STARTED_CHATTER;
2490                       savingComment = FALSE;
2491                     }
2492                     loggedOn = TRUE;
2493                     continue;
2494                   }
2495                 }
2496
2497                 if (looking_at(buf, &i, "* s-shouts: ") ||
2498                     looking_at(buf, &i, "* c-shouts: ")) {
2499                     if (appData.colorize) {
2500                         if (oldi > next_out) {
2501                             SendToPlayer(&buf[next_out], oldi - next_out);
2502                             next_out = oldi;
2503                         }
2504                         Colorize(ColorSShout, FALSE);
2505                         curColor = ColorSShout;
2506                     }
2507                     loggedOn = TRUE;
2508                     started = STARTED_CHATTER;
2509                     continue;
2510                 }
2511
2512                 if (looking_at(buf, &i, "--->")) {
2513                     loggedOn = TRUE;
2514                     continue;
2515                 }
2516
2517                 if (looking_at(buf, &i, "* shouts: ") ||
2518                     looking_at(buf, &i, "--> ")) {
2519                     if (appData.colorize) {
2520                         if (oldi > next_out) {
2521                             SendToPlayer(&buf[next_out], oldi - next_out);
2522                             next_out = oldi;
2523                         }
2524                         Colorize(ColorShout, FALSE);
2525                         curColor = ColorShout;
2526                     }
2527                     loggedOn = TRUE;
2528                     started = STARTED_CHATTER;
2529                     continue;
2530                 }
2531
2532                 if (looking_at( buf, &i, "Challenge:")) {
2533                     if (appData.colorize) {
2534                         if (oldi > next_out) {
2535                             SendToPlayer(&buf[next_out], oldi - next_out);
2536                             next_out = oldi;
2537                         }
2538                         Colorize(ColorChallenge, FALSE);
2539                         curColor = ColorChallenge;
2540                     }
2541                     loggedOn = TRUE;
2542                     continue;
2543                 }
2544
2545                 if (looking_at(buf, &i, "* offers you") ||
2546                     looking_at(buf, &i, "* offers to be") ||
2547                     looking_at(buf, &i, "* would like to") ||
2548                     looking_at(buf, &i, "* requests to") ||
2549                     looking_at(buf, &i, "Your opponent offers") ||
2550                     looking_at(buf, &i, "Your opponent requests")) {
2551
2552                     if (appData.colorize) {
2553                         if (oldi > next_out) {
2554                             SendToPlayer(&buf[next_out], oldi - next_out);
2555                             next_out = oldi;
2556                         }
2557                         Colorize(ColorRequest, FALSE);
2558                         curColor = ColorRequest;
2559                     }
2560                     continue;
2561                 }
2562
2563                 if (looking_at(buf, &i, "* (*) seeking")) {
2564                     if (appData.colorize) {
2565                         if (oldi > next_out) {
2566                             SendToPlayer(&buf[next_out], oldi - next_out);
2567                             next_out = oldi;
2568                         }
2569                         Colorize(ColorSeek, FALSE);
2570                         curColor = ColorSeek;
2571                     }
2572                     continue;
2573             }
2574
2575             if (looking_at(buf, &i, "\\   ")) {
2576                 if (prevColor != ColorNormal) {
2577                     if (oldi > next_out) {
2578                         SendToPlayer(&buf[next_out], oldi - next_out);
2579                         next_out = oldi;
2580                     }
2581                     Colorize(prevColor, TRUE);
2582                     curColor = prevColor;
2583                 }
2584                 if (savingComment) {
2585                     parse_pos = i - oldi;
2586                     memcpy(parse, &buf[oldi], parse_pos);
2587                     parse[parse_pos] = NULLCHAR;
2588                     started = STARTED_COMMENT;
2589                 } else {
2590                     started = STARTED_CHATTER;
2591                 }
2592                 continue;
2593             }
2594
2595             if (looking_at(buf, &i, "Black Strength :") ||
2596                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2597                 looking_at(buf, &i, "<10>") ||
2598                 looking_at(buf, &i, "#@#")) {
2599                 /* Wrong board style */
2600                 loggedOn = TRUE;
2601                 SendToICS(ics_prefix);
2602                 SendToICS("set style 12\n");
2603                 SendToICS(ics_prefix);
2604                 SendToICS("refresh\n");
2605                 continue;
2606             }
2607             
2608             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2609                 ICSInitScript();
2610                 have_sent_ICS_logon = 1;
2611                 continue;
2612             }
2613               
2614             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2615                 (looking_at(buf, &i, "\n<12> ") ||
2616                  looking_at(buf, &i, "<12> "))) {
2617                 loggedOn = TRUE;
2618                 if (oldi > next_out) {
2619                     SendToPlayer(&buf[next_out], oldi - next_out);
2620                 }
2621                 next_out = i;
2622                 started = STARTED_BOARD;
2623                 parse_pos = 0;
2624                 continue;
2625             }
2626
2627             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2628                 looking_at(buf, &i, "<b1> ")) {
2629                 if (oldi > next_out) {
2630                     SendToPlayer(&buf[next_out], oldi - next_out);
2631                 }
2632                 next_out = i;
2633                 started = STARTED_HOLDINGS;
2634                 parse_pos = 0;
2635                 continue;
2636             }
2637
2638             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2639                 loggedOn = TRUE;
2640                 /* Header for a move list -- first line */
2641
2642                 switch (ics_getting_history) {
2643                   case H_FALSE:
2644                     switch (gameMode) {
2645                       case IcsIdle:
2646                       case BeginningOfGame:
2647                         /* User typed "moves" or "oldmoves" while we
2648                            were idle.  Pretend we asked for these
2649                            moves and soak them up so user can step
2650                            through them and/or save them.
2651                            */
2652                         Reset(FALSE, TRUE);
2653                         gameMode = IcsObserving;
2654                         ModeHighlight();
2655                         ics_gamenum = -1;
2656                         ics_getting_history = H_GOT_UNREQ_HEADER;
2657                         break;
2658                       case EditGame: /*?*/
2659                       case EditPosition: /*?*/
2660                         /* Should above feature work in these modes too? */
2661                         /* For now it doesn't */
2662                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2663                         break;
2664                       default:
2665                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2666                         break;
2667                     }
2668                     break;
2669                   case H_REQUESTED:
2670                     /* Is this the right one? */
2671                     if (gameInfo.white && gameInfo.black &&
2672                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2673                         strcmp(gameInfo.black, star_match[2]) == 0) {
2674                         /* All is well */
2675                         ics_getting_history = H_GOT_REQ_HEADER;
2676                     }
2677                     break;
2678                   case H_GOT_REQ_HEADER:
2679                   case H_GOT_UNREQ_HEADER:
2680                   case H_GOT_UNWANTED_HEADER:
2681                   case H_GETTING_MOVES:
2682                     /* Should not happen */
2683                     DisplayError(_("Error gathering move list: two headers"), 0);
2684                     ics_getting_history = H_FALSE;
2685                     break;
2686                 }
2687
2688                 /* Save player ratings into gameInfo if needed */
2689                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2690                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2691                     (gameInfo.whiteRating == -1 ||
2692                      gameInfo.blackRating == -1)) {
2693
2694                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2695                     gameInfo.blackRating = string_to_rating(star_match[3]);
2696                     if (appData.debugMode)
2697                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2698                               gameInfo.whiteRating, gameInfo.blackRating);
2699                 }
2700                 continue;
2701             }
2702
2703             if (looking_at(buf, &i,
2704               "* * match, initial time: * minute*, increment: * second")) {
2705                 /* Header for a move list -- second line */
2706                 /* Initial board will follow if this is a wild game */
2707                 if (gameInfo.event != NULL) free(gameInfo.event);
2708                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2709                 gameInfo.event = StrSave(str);
2710                 /* [HGM] we switched variant. Translate boards if needed. */
2711                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2712                 continue;
2713             }
2714
2715             if (looking_at(buf, &i, "Move  ")) {
2716                 /* Beginning of a move list */
2717                 switch (ics_getting_history) {
2718                   case H_FALSE:
2719                     /* Normally should not happen */
2720                     /* Maybe user hit reset while we were parsing */
2721                     break;
2722                   case H_REQUESTED:
2723                     /* Happens if we are ignoring a move list that is not
2724                      * the one we just requested.  Common if the user
2725                      * tries to observe two games without turning off
2726                      * getMoveList */
2727                     break;
2728                   case H_GETTING_MOVES:
2729                     /* Should not happen */
2730                     DisplayError(_("Error gathering move list: nested"), 0);
2731                     ics_getting_history = H_FALSE;
2732                     break;
2733                   case H_GOT_REQ_HEADER:
2734                     ics_getting_history = H_GETTING_MOVES;
2735                     started = STARTED_MOVES;
2736                     parse_pos = 0;
2737                     if (oldi > next_out) {
2738                         SendToPlayer(&buf[next_out], oldi - next_out);
2739                     }
2740                     break;
2741                   case H_GOT_UNREQ_HEADER:
2742                     ics_getting_history = H_GETTING_MOVES;
2743                     started = STARTED_MOVES_NOHIDE;
2744                     parse_pos = 0;
2745                     break;
2746                   case H_GOT_UNWANTED_HEADER:
2747                     ics_getting_history = H_FALSE;
2748                     break;
2749                 }
2750                 continue;
2751             }                           
2752             
2753             if (looking_at(buf, &i, "% ") ||
2754                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2755                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2756                 savingComment = FALSE;
2757                 switch (started) {
2758                   case STARTED_MOVES:
2759                   case STARTED_MOVES_NOHIDE:
2760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2761                     parse[parse_pos + i - oldi] = NULLCHAR;
2762                     ParseGameHistory(parse);
2763 #if ZIPPY
2764                     if (appData.zippyPlay && first.initDone) {
2765                         FeedMovesToProgram(&first, forwardMostMove);
2766                         if (gameMode == IcsPlayingWhite) {
2767                             if (WhiteOnMove(forwardMostMove)) {
2768                                 if (first.sendTime) {
2769                                   if (first.useColors) {
2770                                     SendToProgram("black\n", &first); 
2771                                   }
2772                                   SendTimeRemaining(&first, TRUE);
2773                                 }
2774 #if 0
2775                                 if (first.useColors) {
2776                                   SendToProgram("white\ngo\n", &first);
2777                                 } else {
2778                                   SendToProgram("go\n", &first);
2779                                 }
2780 #else
2781                                 if (first.useColors) {
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783                                 }
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785 #endif
2786                                 first.maybeThinking = TRUE;
2787                             } else {
2788                                 if (first.usePlayother) {
2789                                   if (first.sendTime) {
2790                                     SendTimeRemaining(&first, TRUE);
2791                                   }
2792                                   SendToProgram("playother\n", &first);
2793                                   firstMove = FALSE;
2794                                 } else {
2795                                   firstMove = TRUE;
2796                                 }
2797                             }
2798                         } else if (gameMode == IcsPlayingBlack) {
2799                             if (!WhiteOnMove(forwardMostMove)) {
2800                                 if (first.sendTime) {
2801                                   if (first.useColors) {
2802                                     SendToProgram("white\n", &first);
2803                                   }
2804                                   SendTimeRemaining(&first, FALSE);
2805                                 }
2806 #if 0
2807                                 if (first.useColors) {
2808                                   SendToProgram("black\ngo\n", &first);
2809                                 } else {
2810                                   SendToProgram("go\n", &first);
2811                                 }
2812 #else
2813                                 if (first.useColors) {
2814                                   SendToProgram("black\n", &first);
2815                                 }
2816                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2817 #endif
2818                                 first.maybeThinking = TRUE;
2819                             } else {
2820                                 if (first.usePlayother) {
2821                                   if (first.sendTime) {
2822                                     SendTimeRemaining(&first, FALSE);
2823                                   }
2824                                   SendToProgram("playother\n", &first);
2825                                   firstMove = FALSE;
2826                                 } else {
2827                                   firstMove = TRUE;
2828                                 }
2829                             }
2830                         }                       
2831                     }
2832 #endif
2833                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2834                         /* Moves came from oldmoves or moves command
2835                            while we weren't doing anything else.
2836                            */
2837                         currentMove = forwardMostMove;
2838                         ClearHighlights();/*!!could figure this out*/
2839                         flipView = appData.flipView;
2840                         DrawPosition(FALSE, boards[currentMove]);
2841                         DisplayBothClocks();
2842                         sprintf(str, "%s vs. %s",
2843                                 gameInfo.white, gameInfo.black);
2844                         DisplayTitle(str);
2845                         gameMode = IcsIdle;
2846                     } else {
2847                         /* Moves were history of an active game */
2848                         if (gameInfo.resultDetails != NULL) {
2849                             free(gameInfo.resultDetails);
2850                             gameInfo.resultDetails = NULL;
2851                         }
2852                     }
2853                     HistorySet(parseList, backwardMostMove,
2854                                forwardMostMove, currentMove-1);
2855                     DisplayMove(currentMove - 1);
2856                     if (started == STARTED_MOVES) next_out = i;
2857                     started = STARTED_NONE;
2858                     ics_getting_history = H_FALSE;
2859                     break;
2860
2861                   case STARTED_OBSERVE:
2862                     started = STARTED_NONE;
2863                     SendToICS(ics_prefix);
2864                     SendToICS("refresh\n");
2865                     break;
2866
2867                   default:
2868                     break;
2869                 }
2870                 if(bookHit) { // [HGM] book: simulate book reply
2871                     static char bookMove[MSG_SIZ]; // a bit generous?
2872
2873                     programStats.nodes = programStats.depth = programStats.time = 
2874                     programStats.score = programStats.got_only_move = 0;
2875                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2876
2877                     strcpy(bookMove, "move ");
2878                     strcat(bookMove, bookHit);
2879                     HandleMachineMove(bookMove, &first);
2880                 }
2881                 continue;
2882             }
2883             
2884             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2885                  started == STARTED_HOLDINGS ||
2886                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2887                 /* Accumulate characters in move list or board */
2888                 parse[parse_pos++] = buf[i];
2889             }
2890             
2891             /* Start of game messages.  Mostly we detect start of game
2892                when the first board image arrives.  On some versions
2893                of the ICS, though, we need to do a "refresh" after starting
2894                to observe in order to get the current board right away. */
2895             if (looking_at(buf, &i, "Adding game * to observation list")) {
2896                 started = STARTED_OBSERVE;
2897                 continue;
2898             }
2899
2900             /* Handle auto-observe */
2901             if (appData.autoObserve &&
2902                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2903                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2904                 char *player;
2905                 /* Choose the player that was highlighted, if any. */
2906                 if (star_match[0][0] == '\033' ||
2907                     star_match[1][0] != '\033') {
2908                     player = star_match[0];
2909                 } else {
2910                     player = star_match[2];
2911                 }
2912                 sprintf(str, "%sobserve %s\n",
2913                         ics_prefix, StripHighlightAndTitle(player));
2914                 SendToICS(str);
2915
2916                 /* Save ratings from notify string */
2917                 strcpy(player1Name, star_match[0]);
2918                 player1Rating = string_to_rating(star_match[1]);
2919                 strcpy(player2Name, star_match[2]);
2920                 player2Rating = string_to_rating(star_match[3]);
2921
2922                 if (appData.debugMode)
2923                   fprintf(debugFP, 
2924                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2925                           player1Name, player1Rating,
2926                           player2Name, player2Rating);
2927
2928                 continue;
2929             }
2930
2931             /* Deal with automatic examine mode after a game,
2932                and with IcsObserving -> IcsExamining transition */
2933             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2934                 looking_at(buf, &i, "has made you an examiner of game *")) {
2935
2936                 int gamenum = atoi(star_match[0]);
2937                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2938                     gamenum == ics_gamenum) {
2939                     /* We were already playing or observing this game;
2940                        no need to refetch history */
2941                     gameMode = IcsExamining;
2942                     if (pausing) {
2943                         pauseExamForwardMostMove = forwardMostMove;
2944                     } else if (currentMove < forwardMostMove) {
2945                         ForwardInner(forwardMostMove);
2946                     }
2947                 } else {
2948                     /* I don't think this case really can happen */
2949                     SendToICS(ics_prefix);
2950                     SendToICS("refresh\n");
2951                 }
2952                 continue;
2953             }    
2954             
2955             /* Error messages */
2956             if (ics_user_moved) {
2957                 if (looking_at(buf, &i, "Illegal move") ||
2958                     looking_at(buf, &i, "Not a legal move") ||
2959                     looking_at(buf, &i, "Your king is in check") ||
2960                     looking_at(buf, &i, "It isn't your turn") ||
2961                     looking_at(buf, &i, "It is not your move")) {
2962                     /* Illegal move */
2963                     ics_user_moved = 0;
2964                     if (forwardMostMove > backwardMostMove) {
2965                         currentMove = --forwardMostMove;
2966                         DisplayMove(currentMove - 1); /* before DMError */
2967                         DisplayMoveError(_("Illegal move (rejected by ICS)"));
2968                         DrawPosition(FALSE, boards[currentMove]);
2969                         SwitchClocks();
2970                         DisplayBothClocks();
2971                     }
2972                     continue;
2973                 }
2974             }
2975
2976             if (looking_at(buf, &i, "still have time") ||
2977                 looking_at(buf, &i, "not out of time") ||
2978                 looking_at(buf, &i, "either player is out of time") ||
2979                 looking_at(buf, &i, "has timeseal; checking")) {
2980                 /* We must have called his flag a little too soon */
2981                 whiteFlag = blackFlag = FALSE;
2982                 continue;
2983             }
2984
2985             if (looking_at(buf, &i, "added * seconds to") ||
2986                 looking_at(buf, &i, "seconds were added to")) {
2987                 /* Update the clocks */
2988                 SendToICS(ics_prefix);
2989                 SendToICS("refresh\n");
2990                 continue;
2991             }
2992
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994                 ics_clock_paused = TRUE;
2995                 StopClocks();
2996                 continue;
2997             }
2998
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000                 ics_clock_paused = FALSE;
3001                 StartClocks();
3002                 continue;
3003             }
3004
3005             /* Grab player ratings from the Creating: message.
3006                Note we have to check for the special case when
3007                the ICS inserts things like [white] or [black]. */
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3010                 /* star_matches:
3011                    0    player 1 name (not necessarily white)
3012                    1    player 1 rating
3013                    2    empty, white, or black (IGNORED)
3014                    3    player 2 name (not necessarily black)
3015                    4    player 2 rating
3016                    
3017                    The names/ratings are sorted out when the game
3018                    actually starts (below).
3019                 */
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021                 player1Rating = string_to_rating(star_match[1]);
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023                 player2Rating = string_to_rating(star_match[4]);
3024
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, 
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",
3028                           player1Name, player1Rating,
3029                           player2Name, player2Rating);
3030
3031                 continue;
3032             }
3033             
3034             /* Improved generic start/end-of-game messages */
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037                 /* If tkind == 0: */
3038                 /* star_match[0] is the game number */
3039                 /*           [1] is the white player's name */
3040                 /*           [2] is the black player's name */
3041                 /* For end-of-game: */
3042                 /*           [3] is the reason for the game end */
3043                 /*           [4] is a PGN end game-token, preceded by " " */
3044                 /* For start-of-game: */
3045                 /*           [3] begins with "Creating" or "Continuing" */
3046                 /*           [4] is " *" or empty (don't care). */
3047                 int gamenum = atoi(star_match[0]);
3048                 char *whitename, *blackname, *why, *endtoken;
3049                 ChessMove endtype = (ChessMove) 0;
3050
3051                 if (tkind == 0) {
3052                   whitename = star_match[1];
3053                   blackname = star_match[2];
3054                   why = star_match[3];
3055                   endtoken = star_match[4];
3056                 } else {
3057                   whitename = star_match[1];
3058                   blackname = star_match[3];
3059                   why = star_match[5];
3060                   endtoken = star_match[6];
3061                 }
3062
3063                 /* Game start messages */
3064                 if (strncmp(why, "Creating ", 9) == 0 ||
3065                     strncmp(why, "Continuing ", 11) == 0) {
3066                     gs_gamenum = gamenum;
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);
3068 #if ZIPPY
3069                     if (appData.zippyPlay) {
3070                         ZippyGameStart(whitename, blackname);
3071                     }
3072 #endif /*ZIPPY*/
3073                     continue;
3074                 }
3075
3076                 /* Game end messages */
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078                     ics_gamenum != gamenum) {
3079                     continue;
3080                 }
3081                 while (endtoken[0] == ' ') endtoken++;
3082                 switch (endtoken[0]) {
3083                   case '*':
3084                   default:
3085                     endtype = GameUnfinished;
3086                     break;
3087                   case '0':
3088                     endtype = BlackWins;
3089                     break;
3090                   case '1':
3091                     if (endtoken[1] == '/')
3092                       endtype = GameIsDrawn;
3093                     else
3094                       endtype = WhiteWins;
3095                     break;
3096                 }
3097                 GameEnds(endtype, why, GE_ICS);
3098 #if ZIPPY
3099                 if (appData.zippyPlay && first.initDone) {
3100                     ZippyGameEnd(endtype, why);
3101                     if (first.pr == NULL) {
3102                       /* Start the next process early so that we'll
3103                          be ready for the next challenge */
3104                       StartChessProgram(&first);
3105                     }
3106                     /* Send "new" early, in case this command takes
3107                        a long time to finish, so that we'll be ready
3108                        for the next challenge. */
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3110                     Reset(TRUE, TRUE);
3111                 }
3112 #endif /*ZIPPY*/
3113                 continue;
3114             }
3115
3116             if (looking_at(buf, &i, "Removing game * from observation") ||
3117                 looking_at(buf, &i, "no longer observing game *") ||
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119                 if (gameMode == IcsObserving &&
3120                     atoi(star_match[0]) == ics_gamenum)
3121                   {
3122                       /* icsEngineAnalyze */
3123                       if (appData.icsEngineAnalyze) {
3124                             ExitAnalyzeMode();
3125                             ModeHighlight();
3126                       }
3127                       StopClocks();
3128                       gameMode = IcsIdle;
3129                       ics_gamenum = -1;
3130                       ics_user_moved = FALSE;
3131                   }
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "no longer examining game *")) {
3136                 if (gameMode == IcsExamining &&
3137                     atoi(star_match[0]) == ics_gamenum)
3138                   {
3139                       gameMode = IcsIdle;
3140                       ics_gamenum = -1;
3141                       ics_user_moved = FALSE;
3142                   }
3143                 continue;
3144             }
3145
3146             /* Advance leftover_start past any newlines we find,
3147                so only partial lines can get reparsed */
3148             if (looking_at(buf, &i, "\n")) {
3149                 prevColor = curColor;
3150                 if (curColor != ColorNormal) {
3151                     if (oldi > next_out) {
3152                         SendToPlayer(&buf[next_out], oldi - next_out);
3153                         next_out = oldi;
3154                     }
3155                     Colorize(ColorNormal, FALSE);
3156                     curColor = ColorNormal;
3157                 }
3158                 if (started == STARTED_BOARD) {
3159                     started = STARTED_NONE;
3160                     parse[parse_pos] = NULLCHAR;
3161                     ParseBoard12(parse);
3162                     ics_user_moved = 0;
3163
3164                     /* Send premove here */
3165                     if (appData.premove) {
3166                       char str[MSG_SIZ];
3167                       if (currentMove == 0 &&
3168                           gameMode == IcsPlayingWhite &&
3169                           appData.premoveWhite) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveWhiteText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (currentMove == 1 &&
3176                                  gameMode == IcsPlayingBlack &&
3177                                  appData.premoveBlack) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveBlackText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (gotPremove) {
3184                         gotPremove = 0;
3185                         ClearPremoveHighlights();
3186                         if (appData.debugMode)
3187                           fprintf(debugFP, "Sending premove:\n");
3188                           UserMoveEvent(premoveFromX, premoveFromY, 
3189                                         premoveToX, premoveToY, 
3190                                         premovePromoChar);
3191                       }
3192                     }
3193
3194                     /* Usually suppress following prompt */
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196                         if (looking_at(buf, &i, "*% ")) {
3197                             savingComment = FALSE;
3198                         }
3199                     }
3200                     next_out = i;
3201                 } else if (started == STARTED_HOLDINGS) {
3202                     int gamenum;
3203                     char new_piece[MSG_SIZ];
3204                     started = STARTED_NONE;
3205                     parse[parse_pos] = NULLCHAR;
3206                     if (appData.debugMode)
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208                                                         parse, currentMove);
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210                         gamenum == ics_gamenum) {
3211                         if (gameInfo.variant == VariantNormal) {
3212                           /* [HGM] We seem to switch variant during a game!
3213                            * Presumably no holdings were displayed, so we have
3214                            * to move the position two files to the right to
3215                            * create room for them!
3216                            */
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218                           /* Get a move list just to see the header, which
3219                              will tell us whether this is really bug or zh */
3220                           if (ics_getting_history == H_FALSE) {
3221                             ics_getting_history = H_REQUESTED;
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3223                             SendToICS(str);
3224                           }
3225                         }
3226                         new_piece[0] = NULLCHAR;
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",
3228                                &gamenum, white_holding, black_holding,
3229                                new_piece);
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3232                         /* [HGM] copy holdings to board holdings area */
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3235 #if ZIPPY
3236                         if (appData.zippyPlay && first.initDone) {
3237                             ZippyHoldings(white_holding, black_holding,
3238                                           new_piece);
3239                         }
3240 #endif /*ZIPPY*/
3241                         if (tinyLayout || smallLayout) {
3242                             char wh[16], bh[16];
3243                             PackHolding(wh, white_holding);
3244                             PackHolding(bh, black_holding);
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246                                     gameInfo.white, gameInfo.black);
3247                         } else {
3248                             sprintf(str, "%s [%s] vs. %s [%s]",
3249                                     gameInfo.white, white_holding,
3250                                     gameInfo.black, black_holding);
3251                         }
3252
3253                         DrawPosition(FALSE, boards[currentMove]);
3254                         DisplayTitle(str);
3255                     }
3256                     /* Suppress following prompt */
3257                     if (looking_at(buf, &i, "*% ")) {
3258                         savingComment = FALSE;
3259                     }
3260                     next_out = i;
3261                 }
3262                 continue;
3263             }
3264
3265             i++;                /* skip unparsed character and loop back */
3266         }
3267         
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269             started != STARTED_HOLDINGS && i > next_out) {
3270             SendToPlayer(&buf[next_out], i - next_out);
3271             next_out = i;
3272         }
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3274         
3275         leftover_len = buf_len - leftover_start;
3276         /* if buffer ends with something we couldn't parse,
3277            reparse it after appending the next read */
3278         
3279     } else if (count == 0) {
3280         RemoveInputSource(isr);
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3282     } else {
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);
3284     }
3285 }
3286
3287
3288 /* Board style 12 looks like this:
3289    
3290    <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
3291    
3292  * The "<12> " is stripped before it gets to this routine.  The two
3293  * trailing 0's (flip state and clock ticking) are later addition, and
3294  * some chess servers may not have them, or may have only the first.
3295  * Additional trailing fields may be added in the future.  
3296  */
3297
3298 #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"
3299
3300 #define RELATION_OBSERVING_PLAYED    0
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE      1
3303 #define RELATION_PLAYING_NOTMYMOVE  -1
3304 #define RELATION_EXAMINING           2
3305 #define RELATION_ISOLATED_BOARD     -3
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3307
3308 void
3309 ParseBoard12(string)
3310      char *string;
3311
3312     GameMode newGameMode;
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316     char to_play, board_chars[200];
3317     char move_str[500], str[500], elapsed_time[500];
3318     char black[32], white[32];
3319     Board board;
3320     int prevMove = currentMove;
3321     int ticking = 2;
3322     ChessMove moveType;
3323     int fromX, fromY, toX, toY;
3324     char promoChar;
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326     char *bookHit = NULL; // [HGM] book
3327
3328     fromX = fromY = toX = toY = -1;
3329     
3330     newGame = FALSE;
3331
3332     if (appData.debugMode)
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);
3334
3335     move_str[0] = NULLCHAR;
3336     elapsed_time[0] = NULLCHAR;
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3338         int  i = 0, j;
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340             if(string[i] == ' ') { ranks++; files = 0; }
3341             else files++;
3342             i++;
3343         }
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];
3345         board_chars[i] = '\0';
3346         string += i + 1;
3347     }
3348     n = sscanf(string, PATTERN, &to_play, &double_push,
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350                &gamenum, white, black, &relation, &basetime, &increment,
3351                &white_stren, &black_stren, &white_time, &black_time,
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,
3353                &ticking);
3354
3355     if (n < 21) {
3356         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357         DisplayError(str, 0);
3358         return;
3359     }
3360
3361     /* Convert the move number to internal form */
3362     moveNum = (moveNum - 1) * 2;
3363     if (to_play == 'B') moveNum++;
3364     if (moveNum >= MAX_MOVES) {
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3366                         0, 1);
3367       return;
3368     }
3369     
3370     switch (relation) {
3371       case RELATION_OBSERVING_PLAYED:
3372       case RELATION_OBSERVING_STATIC:
3373         if (gamenum == -1) {
3374             /* Old ICC buglet */
3375             relation = RELATION_OBSERVING_STATIC;
3376         }
3377         newGameMode = IcsObserving;
3378         break;
3379       case RELATION_PLAYING_MYMOVE:
3380       case RELATION_PLAYING_NOTMYMOVE:
3381         newGameMode =
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383             IcsPlayingWhite : IcsPlayingBlack;
3384         break;
3385       case RELATION_EXAMINING:
3386         newGameMode = IcsExamining;
3387         break;
3388       case RELATION_ISOLATED_BOARD:
3389       default:
3390         /* Just display this board.  If user was doing something else,
3391            we will forget about it until the next board comes. */ 
3392         newGameMode = IcsIdle;
3393         break;
3394       case RELATION_STARTING_POSITION:
3395         newGameMode = gameMode;
3396         break;
3397     }
3398     
3399     /* Modify behavior for initial board display on move listing
3400        of wild games.
3401        */
3402     switch (ics_getting_history) {
3403       case H_FALSE:
3404       case H_REQUESTED:
3405         break;
3406       case H_GOT_REQ_HEADER:
3407       case H_GOT_UNREQ_HEADER:
3408         /* This is the initial position of the current game */
3409         gamenum = ics_gamenum;
3410         moveNum = 0;            /* old ICS bug workaround */
3411         if (to_play == 'B') {
3412           startedFromSetupPosition = TRUE;
3413           blackPlaysFirst = TRUE;
3414           moveNum = 1;
3415           if (forwardMostMove == 0) forwardMostMove = 1;
3416           if (backwardMostMove == 0) backwardMostMove = 1;
3417           if (currentMove == 0) currentMove = 1;
3418         }
3419         newGameMode = gameMode;
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3421         break;
3422       case H_GOT_UNWANTED_HEADER:
3423         /* This is an initial board that we don't want */
3424         return;
3425       case H_GETTING_MOVES:
3426         /* Should not happen */
3427         DisplayError(_("Error gathering move list: extra board"), 0);
3428         ics_getting_history = H_FALSE;
3429         return;
3430     }
3431     
3432     /* Take action if this is the first board of a new game, or of a
3433        different game than is currently being displayed.  */
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435         relation == RELATION_ISOLATED_BOARD) {
3436         
3437         /* Forget the old game and get the history (if any) of the new one */
3438         if (gameMode != BeginningOfGame) {
3439           Reset(FALSE, TRUE);
3440         }
3441         newGame = TRUE;
3442         if (appData.autoRaiseBoard) BoardToTop();
3443         prevMove = -3;
3444         if (gamenum == -1) {
3445             newGameMode = IcsIdle;
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447                    appData.getMoveList) {
3448             /* Need to get game history */
3449             ics_getting_history = H_REQUESTED;
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451             SendToICS(str);
3452         }
3453         
3454         /* Initially flip the board to have black on the bottom if playing
3455            black or if the ICS flip flag is set, but let the user change
3456            it with the Flip View button. */
3457         flipView = appData.autoFlipView ? 
3458           (newGameMode == IcsPlayingBlack) || ics_flip :
3459           appData.flipView;
3460         
3461         /* Done with values from previous mode; copy in new ones */
3462         gameMode = newGameMode;
3463         ModeHighlight();
3464         ics_gamenum = gamenum;
3465         if (gamenum == gs_gamenum) {
3466             int klen = strlen(gs_kind);
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468             sprintf(str, "ICS %s", gs_kind);
3469             gameInfo.event = StrSave(str);
3470         } else {
3471             gameInfo.event = StrSave("ICS game");
3472         }
3473         gameInfo.site = StrSave(appData.icsHost);
3474         gameInfo.date = PGNDate();
3475         gameInfo.round = StrSave("-");
3476         gameInfo.white = StrSave(white);
3477         gameInfo.black = StrSave(black);
3478         timeControl = basetime * 60 * 1000;
3479         timeControl_2 = 0;
3480         timeIncrement = increment * 1000;
3481         movesPerSession = 0;
3482         gameInfo.timeControl = TimeControlTagValue();
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );
3484   if (appData.debugMode) {
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487     setbuf(debugFP, NULL);
3488   }
3489
3490         gameInfo.outOfBook = NULL;
3491         
3492         /* Do we have the ratings? */
3493         if (strcmp(player1Name, white) == 0 &&
3494             strcmp(player2Name, black) == 0) {
3495             if (appData.debugMode)
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497                       player1Rating, player2Rating);
3498             gameInfo.whiteRating = player1Rating;
3499             gameInfo.blackRating = player2Rating;
3500         } else if (strcmp(player2Name, white) == 0 &&
3501                    strcmp(player1Name, black) == 0) {
3502             if (appData.debugMode)
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504                       player2Rating, player1Rating);
3505             gameInfo.whiteRating = player2Rating;
3506             gameInfo.blackRating = player1Rating;
3507         }
3508         player1Name[0] = player2Name[0] = NULLCHAR;
3509
3510         /* Silence shouts if requested */
3511         if (appData.quietPlay &&
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513             SendToICS(ics_prefix);
3514             SendToICS("set shout 0\n");
3515         }
3516     }
3517     
3518     /* Deal with midgame name changes */
3519     if (!newGame) {
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521             if (gameInfo.white) free(gameInfo.white);
3522             gameInfo.white = StrSave(white);
3523         }
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525             if (gameInfo.black) free(gameInfo.black);
3526             gameInfo.black = StrSave(black);
3527         }
3528     }
3529     
3530     /* Throw away game result if anything actually changes in examine mode */
3531     if (gameMode == IcsExamining && !newGame) {
3532         gameInfo.result = GameUnfinished;
3533         if (gameInfo.resultDetails != NULL) {
3534             free(gameInfo.resultDetails);
3535             gameInfo.resultDetails = NULL;
3536         }
3537     }
3538     
3539     /* In pausing && IcsExamining mode, we ignore boards coming
3540        in if they are in a different variation than we are. */
3541     if (pauseExamInvalid) return;
3542     if (pausing && gameMode == IcsExamining) {
3543         if (moveNum <= pauseExamForwardMostMove) {
3544             pauseExamInvalid = TRUE;
3545             forwardMostMove = pauseExamForwardMostMove;
3546             return;
3547         }
3548     }
3549     
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3552   }
3553     /* Parse the board */
3554     for (k = 0; k < ranks; k++) {
3555       for (j = 0; j < files; j++)
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557       if(gameInfo.holdingsWidth > 1) {
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3560       }
3561     }
3562     CopyBoard(boards[moveNum], board);
3563     if (moveNum == 0) {
3564         startedFromSetupPosition =
3565           !CompareBoards(board, initialPosition);
3566         if(startedFromSetupPosition)
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3568     }
3569
3570     /* [HGM] Set castling rights. Take the outermost Rooks,
3571        to make it also work for FRC opening positions. Note that board12
3572        is really defective for later FRC positions, as it has no way to
3573        indicate which Rook can castle if they are on the same side of King.
3574        For the initial position we grant rights to the outermost Rooks,
3575        and remember thos rights, and we then copy them on positions
3576        later in an FRC game. This means WB might not recognize castlings with
3577        Rooks that have moved back to their original position as illegal,
3578        but in ICS mode that is not its job anyway.
3579     */
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3582
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584             if(board[0][i] == WhiteRook) j = i;
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[BOARD_HEIGHT-1][k] == bKing)
3601                 initialRights[5] = castlingRights[moveNum][5] = k;
3602     } else { int r;
3603         r = castlingRights[moveNum][0] = initialRights[0];
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605         r = castlingRights[moveNum][1] = initialRights[1];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607         r = castlingRights[moveNum][3] = initialRights[3];
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609         r = castlingRights[moveNum][4] = initialRights[4];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611         /* wildcastle kludge: always assume King has rights */
3612         r = castlingRights[moveNum][2] = initialRights[2];
3613         r = castlingRights[moveNum][5] = initialRights[5];
3614     }
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3617
3618     
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {
3621         /* This was an initial position from a move list, not
3622            the current position */
3623         return;
3624     }
3625     
3626     /* Update currentMove and known move number limits */
3627     newMove = newGame || moveNum > forwardMostMove;
3628
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631         takeback = forwardMostMove - moveNum;
3632         for (i = 0; i < takeback; i++) {
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634              SendToProgram("undo\n", &first);
3635         }
3636     }
3637
3638     if (newGame) {
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;
3640         if (gameMode == IcsExamining && moveNum == 0) {
3641           /* Workaround for ICS limitation: we are not told the wild
3642              type when starting to examine a game.  But if we ask for
3643              the move list, the move list header will tell us */
3644             ics_getting_history = H_REQUESTED;
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3646             SendToICS(str);
3647         }
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650         forwardMostMove = moveNum;
3651         if (!pausing || currentMove > forwardMostMove)
3652           currentMove = forwardMostMove;
3653     } else {
3654         /* New part of history that is not contiguous with old part */ 
3655         if (pausing && gameMode == IcsExamining) {
3656             pauseExamInvalid = TRUE;
3657             forwardMostMove = pauseExamForwardMostMove;
3658             return;
3659         }
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662             ics_getting_history = H_REQUESTED;
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664             SendToICS(str);
3665         }
3666     }
3667     
3668     /* Update the clocks */
3669     if (strchr(elapsed_time, '.')) {
3670       /* Time is in ms */
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3673     } else {
3674       /* Time is in seconds */
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3677     }
3678       
3679
3680 #if ZIPPY
3681     if (appData.zippyPlay && newGame &&
3682         gameMode != IcsObserving && gameMode != IcsIdle &&
3683         gameMode != IcsExamining)
3684       ZippyFirstBoard(moveNum, basetime, increment);
3685 #endif
3686     
3687     /* Put the move on the move list, first converting
3688        to canonical algebraic form. */
3689     if (moveNum > 0) {
3690   if (appData.debugMode) {
3691     if (appData.debugMode) { int f = forwardMostMove;
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3694     }
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698     setbuf(debugFP, NULL);
3699   }
3700         if (moveNum <= backwardMostMove) {
3701             /* We don't know what the board looked like before
3702                this move.  Punt. */
3703             strcpy(parseList[moveNum - 1], move_str);
3704             strcat(parseList[moveNum - 1], " ");
3705             strcat(parseList[moveNum - 1], elapsed_time);
3706             moveList[moveNum - 1][0] = NULLCHAR;
3707         } else if (strcmp(move_str, "none") == 0) {
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709             /* Again, we don't know what the board looked like;
3710                this is really the start of the game. */
3711             parseList[moveNum - 1][0] = NULLCHAR;
3712             moveList[moveNum - 1][0] = NULLCHAR;
3713             backwardMostMove = moveNum;
3714             startedFromSetupPosition = TRUE;
3715             fromX = fromY = toX = toY = -1;
3716         } else {
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3718           //                 So we parse the long-algebraic move string in stead of the SAN move
3719           int valid; char buf[MSG_SIZ], *prom;
3720
3721           // str looks something like "Q/a1-a2"; kill the slash
3722           if(str[1] == '/') 
3723                 sprintf(buf, "%c%s", str[0], str+2);
3724           else  strcpy(buf, str); // might be castling
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3726                 strcat(buf, prom); // long move lacks promo specification!
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728                 if(appData.debugMode) 
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730                 strcpy(move_str, buf);
3731           }
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)
3734                || ParseOneMove(buf, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);
3736           // end of long SAN patch
3737           if (valid) {
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3740                                      fromY, fromX, toY, toX, promoChar,
3741                                      parseList[moveNum-1]);
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743                              castlingRights[moveNum]) ) {
3744               case MT_NONE:
3745               case MT_STALEMATE:
3746               default:
3747                 break;
3748               case MT_CHECK:
3749                 if(gameInfo.variant != VariantShogi)
3750                     strcat(parseList[moveNum - 1], "+");
3751                 break;
3752               case MT_CHECKMATE:
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754                 strcat(parseList[moveNum - 1], "#");
3755                 break;
3756             }
3757             strcat(parseList[moveNum - 1], " ");
3758             strcat(parseList[moveNum - 1], elapsed_time);
3759             /* currentMoveString is set as a side-effect of ParseOneMove */
3760             strcpy(moveList[moveNum - 1], currentMoveString);
3761             strcat(moveList[moveNum - 1], "\n");
3762           } else {
3763             /* Move from ICS was illegal!?  Punt. */
3764   if (appData.debugMode) {
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3767   }
3768 #if 0
3769             if (appData.testLegality && appData.debugMode) {
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771                 DisplayError(str, 0);
3772             }
3773 #endif
3774             strcpy(parseList[moveNum - 1], move_str);
3775             strcat(parseList[moveNum - 1], " ");
3776             strcat(parseList[moveNum - 1], elapsed_time);
3777             moveList[moveNum - 1][0] = NULLCHAR;
3778             fromX = fromY = toX = toY = -1;
3779           }
3780         }
3781   if (appData.debugMode) {
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783     setbuf(debugFP, NULL);
3784   }
3785
3786 #if ZIPPY
3787         /* Send move to chess program (BEFORE animating it). */
3788         if (appData.zippyPlay && !newGame && newMove && 
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795                             move_str);
3796                     DisplayError(str, 0);
3797                 } else {
3798                     if (first.sendTime) {
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800                     }
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802                     if (firstMove && !bookHit) {
3803                         firstMove = FALSE;
3804                         if (first.useColors) {
3805                           SendToProgram(gameMode == IcsPlayingWhite ?
3806                                         "white\ngo\n" :
3807                                         "black\ngo\n", &first);
3808                         } else {
3809                           SendToProgram("go\n", &first);
3810                         }
3811                         first.maybeThinking = TRUE;
3812                     }
3813                 }
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817                 DisplayError(str, 0);
3818               } else {
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820                 SendMoveToProgram(moveNum - 1, &first);
3821               }
3822             }
3823         }
3824 #endif
3825     }
3826
3827     if (moveNum > 0 && !gotPremove) {
3828         /* If move comes from a remote source, animate it.  If it
3829            isn't remote, it will have already been animated. */
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832         }
3833         if (!pausing && appData.highlightLastMove) {
3834             SetHighlights(fromX, fromY, toX, toY);
3835         }
3836     }
3837     
3838     /* Start the clocks */
3839     whiteFlag = blackFlag = FALSE;
3840     appData.clockMode = !(basetime == 0 && increment == 0);
3841     if (ticking == 0) {
3842       ics_clock_paused = TRUE;
3843       StopClocks();
3844     } else if (ticking == 1) {
3845       ics_clock_paused = FALSE;
3846     }
3847     if (gameMode == IcsIdle ||
3848         relation == RELATION_OBSERVING_STATIC ||
3849         relation == RELATION_EXAMINING ||
3850         ics_clock_paused)
3851       DisplayBothClocks();
3852     else
3853       StartClocks();
3854     
3855     /* Display opponents and material strengths */
3856     if (gameInfo.variant != VariantBughouse &&
3857         gameInfo.variant != VariantCrazyhouse) {
3858         if (tinyLayout || smallLayout) {
3859             if(gameInfo.variant == VariantNormal)
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3862                     basetime, increment);
3863             else
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3866                     basetime, increment, (int) gameInfo.variant);
3867         } else {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, VariantName(gameInfo.variant));
3876         }
3877         DisplayTitle(str);
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3880   }
3881     }
3882
3883    
3884     /* Display the board */
3885     if (!pausing) {
3886       
3887       if (appData.premove)
3888           if (!gotPremove || 
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891               ClearPremoveHighlights();
3892
3893       DrawPosition(FALSE, boards[currentMove]);
3894       DisplayMove(moveNum - 1);
3895       if (appData.ringBellAfterMoves && !ics_user_moved)
3896         RingBell();
3897     }
3898
3899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3900 #if ZIPPY
3901     if(bookHit) { // [HGM] book: simulate book reply
3902         static char bookMove[MSG_SIZ]; // a bit generous?
3903
3904         programStats.nodes = programStats.depth = programStats.time = 
3905         programStats.score = programStats.got_only_move = 0;
3906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3907
3908         strcpy(bookMove, "move ");
3909         strcat(bookMove, bookHit);
3910         HandleMachineMove(bookMove, &first);
3911     }
3912 #endif
3913 }
3914
3915 void
3916 GetMoveListEvent()
3917 {
3918     char buf[MSG_SIZ];
3919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3920         ics_getting_history = H_REQUESTED;
3921         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3922         SendToICS(buf);
3923     }
3924 }
3925
3926 void
3927 AnalysisPeriodicEvent(force)
3928      int force;
3929 {
3930     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3931          && !force) || !appData.periodicUpdates)
3932       return;
3933
3934     /* Send . command to Crafty to collect stats */
3935     SendToProgram(".\n", &first);
3936
3937     /* Don't send another until we get a response (this makes
3938        us stop sending to old Crafty's which don't understand
3939        the "." command (sending illegal cmds resets node count & time,
3940        which looks bad)) */
3941     programStats.ok_to_send = 0;
3942 }
3943
3944 void
3945 SendMoveToProgram(moveNum, cps)
3946      int moveNum;
3947      ChessProgramState *cps;
3948 {
3949     char buf[MSG_SIZ];
3950
3951     if (cps->useUsermove) {
3952       SendToProgram("usermove ", cps);
3953     }
3954     if (cps->useSAN) {
3955       char *space;
3956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3957         int len = space - parseList[moveNum];
3958         memcpy(buf, parseList[moveNum], len);
3959         buf[len++] = '\n';
3960         buf[len] = NULLCHAR;
3961       } else {
3962         sprintf(buf, "%s\n", parseList[moveNum]);
3963       }
3964       SendToProgram(buf, cps);
3965     } else {
3966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3967         AlphaRank(moveList[moveNum], 4);
3968         SendToProgram(moveList[moveNum], cps);
3969         AlphaRank(moveList[moveNum], 4); // and back
3970       } else
3971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3972        * the engine. It would be nice to have a better way to identify castle 
3973        * moves here. */
3974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3975                                                                          && cps->useOOCastle) {
3976         int fromX = moveList[moveNum][0] - AAA; 
3977         int fromY = moveList[moveNum][1] - ONE;
3978         int toX = moveList[moveNum][2] - AAA; 
3979         int toY = moveList[moveNum][3] - ONE;
3980         if((boards[moveNum][fromY][fromX] == WhiteKing 
3981             && boards[moveNum][toY][toX] == WhiteRook)
3982            || (boards[moveNum][fromY][fromX] == BlackKing 
3983                && boards[moveNum][toY][toX] == BlackRook)) {
3984           if(toX > fromX) SendToProgram("O-O\n", cps);
3985           else SendToProgram("O-O-O\n", cps);
3986         }
3987         else SendToProgram(moveList[moveNum], cps);
3988       }
3989       else SendToProgram(moveList[moveNum], cps);
3990       /* End of additions by Tord */
3991     }
3992
3993     /* [HGM] setting up the opening has brought engine in force mode! */
3994     /*       Send 'go' if we are in a mode where machine should play. */
3995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3996         (gameMode == TwoMachinesPlay   ||
3997 #ifdef ZIPPY
3998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
3999 #endif
4000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4001         SendToProgram("go\n", cps);
4002   if (appData.debugMode) {
4003     fprintf(debugFP, "(extra)\n");
4004   }
4005     }
4006     setboardSpoiledMachineBlack = 0;
4007 }
4008
4009 void
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4011      ChessMove moveType;
4012      int fromX, fromY, toX, toY;
4013 {
4014     char user_move[MSG_SIZ];
4015
4016     switch (moveType) {
4017       default:
4018         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4019                 (int)moveType, fromX, fromY, toX, toY);
4020         DisplayError(user_move + strlen("say "), 0);
4021         break;
4022       case WhiteKingSideCastle:
4023       case BlackKingSideCastle:
4024       case WhiteQueenSideCastleWild:
4025       case BlackQueenSideCastleWild:
4026       /* PUSH Fabien */
4027       case WhiteHSideCastleFR:
4028       case BlackHSideCastleFR:
4029       /* POP Fabien */
4030         sprintf(user_move, "o-o\n");
4031         break;
4032       case WhiteQueenSideCastle:
4033       case BlackQueenSideCastle:
4034       case WhiteKingSideCastleWild:
4035       case BlackKingSideCastleWild:
4036       /* PUSH Fabien */
4037       case WhiteASideCastleFR:
4038       case BlackASideCastleFR:
4039       /* POP Fabien */
4040         sprintf(user_move, "o-o-o\n");
4041         break;
4042       case WhitePromotionQueen:
4043       case BlackPromotionQueen:
4044       case WhitePromotionRook:
4045       case BlackPromotionRook:
4046       case WhitePromotionBishop:
4047       case BlackPromotionBishop:
4048       case WhitePromotionKnight:
4049       case BlackPromotionKnight:
4050       case WhitePromotionKing:
4051       case BlackPromotionKing:
4052       case WhitePromotionChancellor:
4053       case BlackPromotionChancellor:
4054       case WhitePromotionArchbishop:
4055       case BlackPromotionArchbishop:
4056         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4057             sprintf(user_move, "%c%c%c%c=%c\n",
4058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4059                 PieceToChar(WhiteFerz));
4060         else if(gameInfo.variant == VariantGreat)
4061             sprintf(user_move, "%c%c%c%c=%c\n",
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063                 PieceToChar(WhiteMan));
4064         else
4065             sprintf(user_move, "%c%c%c%c=%c\n",
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067                 PieceToChar(PromoPiece(moveType)));
4068         break;
4069       case WhiteDrop:
4070       case BlackDrop:
4071         sprintf(user_move, "%c@%c%c\n",
4072                 ToUpper(PieceToChar((ChessSquare) fromX)),
4073                 AAA + toX, ONE + toY);
4074         break;
4075       case NormalMove:
4076       case WhiteCapturesEnPassant:
4077       case BlackCapturesEnPassant:
4078       case IllegalMove:  /* could be a variant we don't quite understand */
4079         sprintf(user_move, "%c%c%c%c\n",
4080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4081         break;
4082     }
4083     SendToICS(user_move);
4084 }
4085
4086 void
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4088      int rf, ff, rt, ft;
4089      char promoChar;
4090      char move[7];
4091 {
4092     if (rf == DROP_RANK) {
4093         sprintf(move, "%c@%c%c\n",
4094                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4095     } else {
4096         if (promoChar == 'x' || promoChar == NULLCHAR) {
4097             sprintf(move, "%c%c%c%c\n",
4098                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4099         } else {
4100             sprintf(move, "%c%c%c%c%c\n",
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4102         }
4103     }
4104 }
4105
4106 void
4107 ProcessICSInitScript(f)
4108      FILE *f;
4109 {
4110     char buf[MSG_SIZ];
4111
4112     while (fgets(buf, MSG_SIZ, f)) {
4113         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4114     }
4115
4116     fclose(f);
4117 }
4118
4119
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4121 void
4122 AlphaRank(char *move, int n)
4123 {
4124 //    char *p = move, c; int x, y;
4125
4126     if (appData.debugMode) {
4127         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4128     }
4129
4130     if(move[1]=='*' && 
4131        move[2]>='0' && move[2]<='9' &&
4132        move[3]>='a' && move[3]<='x'    ) {
4133         move[1] = '@';
4134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4136     } else
4137     if(move[0]>='0' && move[0]<='9' &&
4138        move[1]>='a' && move[1]<='x' &&
4139        move[2]>='0' && move[2]<='9' &&
4140        move[3]>='a' && move[3]<='x'    ) {
4141         /* input move, Shogi -> normal */
4142         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4143         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4144         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4146     } else
4147     if(move[1]=='@' &&
4148        move[3]>='0' && move[3]<='9' &&
4149        move[2]>='a' && move[2]<='x'    ) {
4150         move[1] = '*';
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4153     } else
4154     if(
4155        move[0]>='a' && move[0]<='x' &&
4156        move[3]>='0' && move[3]<='9' &&
4157        move[2]>='a' && move[2]<='x'    ) {
4158          /* output move, normal -> Shogi */
4159         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4160         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4161         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4163         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4164     }
4165     if (appData.debugMode) {
4166         fprintf(debugFP, "   out = '%s'\n", move);
4167     }
4168 }
4169
4170 /* Parser for moves from gnuchess, ICS, or user typein box */
4171 Boolean
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4173      char *move;
4174      int moveNum;
4175      ChessMove *moveType;
4176      int *fromX, *fromY, *toX, *toY;
4177      char *promoChar;
4178 {       
4179     if (appData.debugMode) {
4180         fprintf(debugFP, "move to parse: %s\n", move);
4181     }
4182     *moveType = yylexstr(moveNum, move);
4183
4184     switch (*moveType) {
4185       case WhitePromotionChancellor:
4186       case BlackPromotionChancellor:
4187       case WhitePromotionArchbishop:
4188       case BlackPromotionArchbishop:
4189       case WhitePromotionQueen:
4190       case BlackPromotionQueen:
4191       case WhitePromotionRook:
4192       case BlackPromotionRook:
4193       case WhitePromotionBishop:
4194       case BlackPromotionBishop:
4195       case WhitePromotionKnight:
4196       case BlackPromotionKnight:
4197       case WhitePromotionKing:
4198       case BlackPromotionKing:
4199       case NormalMove:
4200       case WhiteCapturesEnPassant:
4201       case BlackCapturesEnPassant:
4202       case WhiteKingSideCastle:
4203       case WhiteQueenSideCastle:
4204       case BlackKingSideCastle:
4205       case BlackQueenSideCastle:
4206       case WhiteKingSideCastleWild:
4207       case WhiteQueenSideCastleWild:
4208       case BlackKingSideCastleWild:
4209       case BlackQueenSideCastleWild:
4210       /* Code added by Tord: */
4211       case WhiteHSideCastleFR:
4212       case WhiteASideCastleFR:
4213       case BlackHSideCastleFR:
4214       case BlackASideCastleFR:
4215       /* End of code added by Tord */
4216       case IllegalMove:         /* bug or odd chess variant */
4217         *fromX = currentMoveString[0] - AAA;
4218         *fromY = currentMoveString[1] - ONE;
4219         *toX = currentMoveString[2] - AAA;
4220         *toY = currentMoveString[3] - ONE;
4221         *promoChar = currentMoveString[4];
4222         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4223             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4224     if (appData.debugMode) {
4225         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4226     }
4227             *fromX = *fromY = *toX = *toY = 0;
4228             return FALSE;
4229         }
4230         if (appData.testLegality) {
4231           return (*moveType != IllegalMove);
4232         } else {
4233           return !(fromX == fromY && toX == toY);
4234         }
4235
4236       case WhiteDrop:
4237       case BlackDrop:
4238         *fromX = *moveType == WhiteDrop ?
4239           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4240           (int) CharToPiece(ToLower(currentMoveString[0]));
4241         *fromY = DROP_RANK;
4242         *toX = currentMoveString[2] - AAA;
4243         *toY = currentMoveString[3] - ONE;
4244         *promoChar = NULLCHAR;
4245         return TRUE;
4246
4247       case AmbiguousMove:
4248       case ImpossibleMove:
4249       case (ChessMove) 0:       /* end of file */
4250       case ElapsedTime:
4251       case Comment:
4252       case PGNTag:
4253       case NAG:
4254       case WhiteWins:
4255       case BlackWins:
4256       case GameIsDrawn:
4257       default:
4258     if (appData.debugMode) {
4259         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4260     }
4261         /* bug? */
4262         *fromX = *fromY = *toX = *toY = 0;
4263         *promoChar = NULLCHAR;
4264         return FALSE;
4265     }
4266 }
4267
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4269 // All positions will have equal probability, but the current method will not provide a unique
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4271 #define DARK 1
4272 #define LITE 2
4273 #define ANY 3
4274
4275 int squaresLeft[4];
4276 int piecesLeft[(int)BlackPawn];
4277 int seed, nrOfShuffles;
4278
4279 void GetPositionNumber()
4280 {       // sets global variable seed
4281         int i;
4282
4283         seed = appData.defaultFrcPosition;
4284         if(seed < 0) { // randomize based on time for negative FRC position numbers
4285                 for(i=0; i<50; i++) seed += random();
4286                 seed = random() ^ random() >> 8 ^ random() << 8;
4287                 if(seed<0) seed = -seed;
4288         }
4289 }
4290
4291 int put(Board board, int pieceType, int rank, int n, int shade)
4292 // put the piece on the (n-1)-th empty squares of the given shade
4293 {
4294         int i;
4295
4296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4297                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4298                         board[rank][i] = (ChessSquare) pieceType;
4299                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4300                         squaresLeft[ANY]--;
4301                         piecesLeft[pieceType]--; 
4302                         return i;
4303                 }
4304         }
4305         return -1;
4306 }
4307
4308
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4310 // calculate where the next piece goes, (any empty square), and put it there
4311 {
4312         int i;
4313
4314         i = seed % squaresLeft[shade];
4315         nrOfShuffles *= squaresLeft[shade];
4316         seed /= squaresLeft[shade];
4317         put(board, pieceType, rank, i, shade);
4318 }
4319
4320 void AddTwoPieces(Board board, int pieceType, int rank)
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4322 {
4323         int i, n=squaresLeft[ANY], j=n-1, k;
4324
4325         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4326         i = seed % k;  // pick one
4327         nrOfShuffles *= k;
4328         seed /= k;
4329         while(i >= j) i -= j--;
4330         j = n - 1 - j; i += j;
4331         put(board, pieceType, rank, j, ANY);
4332         put(board, pieceType, rank, i, ANY);
4333 }
4334
4335 void SetUpShuffle(Board board, int number)
4336 {
4337         int i, p, first=1;
4338
4339         GetPositionNumber(); nrOfShuffles = 1;
4340
4341         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4342         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4343         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4344
4345         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4346
4347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4348             p = (int) board[0][i];
4349             if(p < (int) BlackPawn) piecesLeft[p] ++;
4350             board[0][i] = EmptySquare;
4351         }
4352
4353         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4354             // shuffles restricted to allow normal castling put KRR first
4355             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4356                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4357             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4358                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4359             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4360                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4361             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4362                 put(board, WhiteRook, 0, 0, ANY);
4363             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4364         }
4365
4366         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4367             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4368             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4369                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4370                 while(piecesLeft[p] >= 2) {
4371                     AddOnePiece(board, p, 0, LITE);
4372                     AddOnePiece(board, p, 0, DARK);
4373                 }
4374                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4375             }
4376
4377         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4378             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4379             // but we leave King and Rooks for last, to possibly obey FRC restriction
4380             if(p == (int)WhiteRook) continue;
4381             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4382             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4383         }
4384
4385         // now everything is placed, except perhaps King (Unicorn) and Rooks
4386
4387         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4388             // Last King gets castling rights
4389             while(piecesLeft[(int)WhiteUnicorn]) {
4390                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4391                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4392             }
4393
4394             while(piecesLeft[(int)WhiteKing]) {
4395                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4397             }
4398
4399
4400         } else {
4401             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4402             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4403         }
4404
4405         // Only Rooks can be left; simply place them all
4406         while(piecesLeft[(int)WhiteRook]) {
4407                 i = put(board, WhiteRook, 0, 0, ANY);
4408                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4409                         if(first) {
4410                                 first=0;
4411                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4412                         }
4413                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4414                 }
4415         }
4416         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4417             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4418         }
4419
4420         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4421 }
4422
4423 int SetCharTable( char *table, const char * map )
4424 /* [HGM] moved here from winboard.c because of its general usefulness */
4425 /*       Basically a safe strcpy that uses the last character as King */
4426 {
4427     int result = FALSE; int NrPieces;
4428
4429     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4430                     && NrPieces >= 12 && !(NrPieces&1)) {
4431         int i; /* [HGM] Accept even length from 12 to 34 */
4432
4433         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4434         for( i=0; i<NrPieces/2-1; i++ ) {
4435             table[i] = map[i];
4436             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4437         }
4438         table[(int) WhiteKing]  = map[NrPieces/2-1];
4439         table[(int) BlackKing]  = map[NrPieces-1];
4440
4441         result = TRUE;
4442     }
4443
4444     return result;
4445 }
4446
4447 void Prelude(Board board)
4448 {       // [HGM] superchess: random selection of exo-pieces
4449         int i, j, k; ChessSquare p; 
4450         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4451
4452         GetPositionNumber(); // use FRC position number
4453
4454         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4455             SetCharTable(pieceToChar, appData.pieceToCharTable);
4456             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4457                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4458         }
4459
4460         j = seed%4;                 seed /= 4; 
4461         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4462         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4463         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4464         j = seed%3 + (seed%3 >= j); seed /= 3; 
4465         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4466         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4467         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4468         j = seed%3;                 seed /= 3; 
4469         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4470         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4471         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4472         j = seed%2 + (seed%2 >= j); seed /= 2; 
4473         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4474         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4475         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4476         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4477         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4478         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4479         put(board, exoPieces[0],    0, 0, ANY);
4480         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4481 }
4482
4483 void
4484 InitPosition(redraw)
4485      int redraw;
4486 {
4487     ChessSquare (* pieces)[BOARD_SIZE];
4488     int i, j, pawnRow, overrule,
4489     oldx = gameInfo.boardWidth,
4490     oldy = gameInfo.boardHeight,
4491     oldh = gameInfo.holdingsWidth,
4492     oldv = gameInfo.variant;
4493
4494     currentMove = forwardMostMove = backwardMostMove = 0;
4495     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4496
4497     /* [AS] Initialize pv info list [HGM] and game status */
4498     {
4499         for( i=0; i<MAX_MOVES; i++ ) {
4500             pvInfoList[i].depth = 0;
4501             epStatus[i]=EP_NONE;
4502             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4503         }
4504
4505         initialRulePlies = 0; /* 50-move counter start */
4506
4507         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4508         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4509     }
4510
4511     
4512     /* [HGM] logic here is completely changed. In stead of full positions */
4513     /* the initialized data only consist of the two backranks. The switch */
4514     /* selects which one we will use, which is than copied to the Board   */
4515     /* initialPosition, which for the rest is initialized by Pawns and    */
4516     /* empty squares. This initial position is then copied to boards[0],  */
4517     /* possibly after shuffling, so that it remains available.            */
4518
4519     gameInfo.holdingsWidth = 0; /* default board sizes */
4520     gameInfo.boardWidth    = 8;
4521     gameInfo.boardHeight   = 8;
4522     gameInfo.holdingsSize  = 0;
4523     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4524     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4525     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4526
4527     switch (gameInfo.variant) {
4528     case VariantFischeRandom:
4529       shuffleOpenings = TRUE;
4530     default:
4531       pieces = FIDEArray;
4532       break;
4533     case VariantShatranj:
4534       pieces = ShatranjArray;
4535       nrCastlingRights = 0;
4536       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4537       break;
4538     case VariantTwoKings:
4539       pieces = twoKingsArray;
4540       break;
4541     case VariantCapaRandom:
4542       shuffleOpenings = TRUE;
4543     case VariantCapablanca:
4544       pieces = CapablancaArray;
4545       gameInfo.boardWidth = 10;
4546       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4547       break;
4548     case VariantGothic:
4549       pieces = GothicArray;
4550       gameInfo.boardWidth = 10;
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4552       break;
4553     case VariantJanus:
4554       pieces = JanusArray;
4555       gameInfo.boardWidth = 10;
4556       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4557       nrCastlingRights = 6;
4558         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4559         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4560         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4561         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4562         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4563         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4564       break;
4565     case VariantFalcon:
4566       pieces = FalconArray;
4567       gameInfo.boardWidth = 10;
4568       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4569       break;
4570     case VariantXiangqi:
4571       pieces = XiangqiArray;
4572       gameInfo.boardWidth  = 9;
4573       gameInfo.boardHeight = 10;
4574       nrCastlingRights = 0;
4575       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4576       break;
4577     case VariantShogi:
4578       pieces = ShogiArray;
4579       gameInfo.boardWidth  = 9;
4580       gameInfo.boardHeight = 9;
4581       gameInfo.holdingsSize = 7;
4582       nrCastlingRights = 0;
4583       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4584       break;
4585     case VariantCourier:
4586       pieces = CourierArray;
4587       gameInfo.boardWidth  = 12;
4588       nrCastlingRights = 0;
4589       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4590       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4591       break;
4592     case VariantKnightmate:
4593       pieces = KnightmateArray;
4594       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4595       break;
4596     case VariantFairy:
4597       pieces = fairyArray;
4598       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4599       break;
4600     case VariantGreat:
4601       pieces = GreatArray;
4602       gameInfo.boardWidth = 10;
4603       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4604       gameInfo.holdingsSize = 8;
4605       break;
4606     case VariantSuper:
4607       pieces = FIDEArray;
4608       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4609       gameInfo.holdingsSize = 8;
4610       startedFromSetupPosition = TRUE;
4611       break;
4612     case VariantCrazyhouse:
4613     case VariantBughouse:
4614       pieces = FIDEArray;
4615       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4616       gameInfo.holdingsSize = 5;
4617       break;
4618     case VariantWildCastle:
4619       pieces = FIDEArray;
4620       /* !!?shuffle with kings guaranteed to be on d or e file */
4621       shuffleOpenings = 1;
4622       break;
4623     case VariantNoCastle:
4624       pieces = FIDEArray;
4625       nrCastlingRights = 0;
4626       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4627       /* !!?unconstrained back-rank shuffle */
4628       shuffleOpenings = 1;
4629       break;
4630     }
4631
4632     overrule = 0;
4633     if(appData.NrFiles >= 0) {
4634         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4635         gameInfo.boardWidth = appData.NrFiles;
4636     }
4637     if(appData.NrRanks >= 0) {
4638         gameInfo.boardHeight = appData.NrRanks;
4639     }
4640     if(appData.holdingsSize >= 0) {
4641         i = appData.holdingsSize;
4642         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4643         gameInfo.holdingsSize = i;
4644     }
4645     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4646     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4647         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4648
4649     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4650     if(pawnRow < 1) pawnRow = 1;
4651
4652     /* User pieceToChar list overrules defaults */
4653     if(appData.pieceToCharTable != NULL)
4654         SetCharTable(pieceToChar, appData.pieceToCharTable);
4655
4656     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4657
4658         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4659             s = (ChessSquare) 0; /* account holding counts in guard band */
4660         for( i=0; i<BOARD_HEIGHT; i++ )
4661             initialPosition[i][j] = s;
4662
4663         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4664         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4665         initialPosition[pawnRow][j] = WhitePawn;
4666         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4667         if(gameInfo.variant == VariantXiangqi) {
4668             if(j&1) {
4669                 initialPosition[pawnRow][j] = 
4670                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4671                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4672                    initialPosition[2][j] = WhiteCannon;
4673                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4674                 }
4675             }
4676         }
4677         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4678     }
4679     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4680
4681             j=BOARD_LEFT+1;
4682             initialPosition[1][j] = WhiteBishop;
4683             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4684             j=BOARD_RGHT-2;
4685             initialPosition[1][j] = WhiteRook;
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4687     }
4688
4689     if( nrCastlingRights == -1) {
4690         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4691         /*       This sets default castling rights from none to normal corners   */
4692         /* Variants with other castling rights must set them themselves above    */
4693         nrCastlingRights = 6;
4694        
4695         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4696         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4697         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4698         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4699         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4700         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4701      }
4702
4703      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4704      if(gameInfo.variant == VariantGreat) { // promotion commoners
4705         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4706         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4707         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4708         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4709      }
4710 #if 0
4711     if(gameInfo.variant == VariantFischeRandom) {
4712       if( appData.defaultFrcPosition < 0 ) {
4713         ShuffleFRC( initialPosition );
4714       }
4715       else {
4716         SetupFRC( initialPosition, appData.defaultFrcPosition );
4717       }
4718       startedFromSetupPosition = TRUE;
4719     } else 
4720 #else
4721   if (appData.debugMode) {
4722     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4723   }
4724     if(shuffleOpenings) {
4725         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4726         startedFromSetupPosition = TRUE;
4727     }
4728 #endif
4729     if(startedFromPositionFile) {
4730       /* [HGM] loadPos: use PositionFile for every new game */
4731       CopyBoard(initialPosition, filePosition);
4732       for(i=0; i<nrCastlingRights; i++)
4733           castlingRights[0][i] = initialRights[i] = fileRights[i];
4734       startedFromSetupPosition = TRUE;
4735     }
4736
4737     CopyBoard(boards[0], initialPosition);
4738
4739     if(oldx != gameInfo.boardWidth ||
4740        oldy != gameInfo.boardHeight ||
4741        oldh != gameInfo.holdingsWidth
4742 #ifdef GOTHIC
4743        || oldv == VariantGothic ||        // For licensing popups
4744        gameInfo.variant == VariantGothic
4745 #endif
4746 #ifdef FALCON
4747        || oldv == VariantFalcon ||
4748        gameInfo.variant == VariantFalcon
4749 #endif
4750                                          )
4751             InitDrawingSizes(-2 ,0);
4752
4753     if (redraw)
4754       DrawPosition(TRUE, boards[currentMove]);
4755 }
4756
4757 void
4758 SendBoard(cps, moveNum)
4759      ChessProgramState *cps;
4760      int moveNum;
4761 {
4762     char message[MSG_SIZ];
4763     
4764     if (cps->useSetboard) {
4765       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4766       sprintf(message, "setboard %s\n", fen);
4767       SendToProgram(message, cps);
4768       free(fen);
4769
4770     } else {
4771       ChessSquare *bp;
4772       int i, j;
4773       /* Kludge to set black to move, avoiding the troublesome and now
4774        * deprecated "black" command.
4775        */
4776       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4777
4778       SendToProgram("edit\n", cps);
4779       SendToProgram("#\n", cps);
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4781         bp = &boards[moveNum][i][BOARD_LEFT];
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4783           if ((int) *bp < (int) BlackPawn) {
4784             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4785                     AAA + j, ONE + i);
4786             if(message[0] == '+' || message[0] == '~') {
4787                 sprintf(message, "%c%c%c+\n",
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4789                         AAA + j, ONE + i);
4790             }
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4794             }
4795             SendToProgram(message, cps);
4796           }
4797         }
4798       }
4799     
4800       SendToProgram("c\n", cps);
4801       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4802         bp = &boards[moveNum][i][BOARD_LEFT];
4803         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4804           if (((int) *bp != (int) EmptySquare)
4805               && ((int) *bp >= (int) BlackPawn)) {
4806             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4807                     AAA + j, ONE + i);
4808             if(message[0] == '+' || message[0] == '~') {
4809                 sprintf(message, "%c%c%c+\n",
4810                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4811                         AAA + j, ONE + i);
4812             }
4813             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4814                 message[1] = BOARD_RGHT   - 1 - j + '1';
4815                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4816             }
4817             SendToProgram(message, cps);
4818           }
4819         }
4820       }
4821     
4822       SendToProgram(".\n", cps);
4823     }
4824     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4825 }
4826
4827 int
4828 IsPromotion(fromX, fromY, toX, toY)
4829      int fromX, fromY, toX, toY;
4830 {
4831     /* [HGM] add Shogi promotions */
4832     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4833     ChessSquare piece;
4834
4835     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4836       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4837    /* [HGM] Note to self: line above also weeds out drops */
4838     piece = boards[currentMove][fromY][fromX];
4839     if(gameInfo.variant == VariantShogi) {
4840         promotionZoneSize = 3;
4841         highestPromotingPiece = (int)WhiteKing;
4842         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4843            and if in normal chess we then allow promotion to King, why not
4844            allow promotion of other piece in Shogi?                         */
4845     }
4846     if((int)piece >= BlackPawn) {
4847         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4848              return FALSE;
4849         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4850     } else {
4851         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4852            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4853     }
4854     return ( (int)piece <= highestPromotingPiece );
4855 }
4856
4857 int
4858 InPalace(row, column)
4859      int row, column;
4860 {   /* [HGM] for Xiangqi */
4861     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4862          column < (BOARD_WIDTH + 4)/2 &&
4863          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4864     return FALSE;
4865 }
4866
4867 int
4868 PieceForSquare (x, y)
4869      int x;
4870      int y;
4871 {
4872   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4873      return -1;
4874   else
4875      return boards[currentMove][y][x];
4876 }
4877
4878 int
4879 OKToStartUserMove(x, y)
4880      int x, y;
4881 {
4882     ChessSquare from_piece;
4883     int white_piece;
4884
4885     if (matchMode) return FALSE;
4886     if (gameMode == EditPosition) return TRUE;
4887
4888     if (x >= 0 && y >= 0)
4889       from_piece = boards[currentMove][y][x];
4890     else
4891       from_piece = EmptySquare;
4892
4893     if (from_piece == EmptySquare) return FALSE;
4894
4895     white_piece = (int)from_piece >= (int)WhitePawn &&
4896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4897
4898     switch (gameMode) {
4899       case PlayFromGameFile:
4900       case AnalyzeFile:
4901       case TwoMachinesPlay:
4902       case EndOfGame:
4903         return FALSE;
4904
4905       case IcsObserving:
4906       case IcsIdle:
4907         return FALSE;
4908
4909       case MachinePlaysWhite:
4910       case IcsPlayingBlack:
4911         if (appData.zippyPlay) return FALSE;
4912         if (white_piece) {
4913             DisplayMoveError(_("You are playing Black"));
4914             return FALSE;
4915         }
4916         break;
4917
4918       case MachinePlaysBlack:
4919       case IcsPlayingWhite:
4920         if (appData.zippyPlay) return FALSE;
4921         if (!white_piece) {
4922             DisplayMoveError(_("You are playing White"));
4923             return FALSE;
4924         }
4925         break;
4926
4927       case EditGame:
4928         if (!white_piece && WhiteOnMove(currentMove)) {
4929             DisplayMoveError(_("It is White's turn"));
4930             return FALSE;
4931         }           
4932         if (white_piece && !WhiteOnMove(currentMove)) {
4933             DisplayMoveError(_("It is Black's turn"));
4934             return FALSE;
4935         }           
4936         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4937             /* Editing correspondence game history */
4938             /* Could disallow this or prompt for confirmation */
4939             cmailOldMove = -1;
4940         }
4941         if (currentMove < forwardMostMove) {
4942             /* Discarding moves */
4943             /* Could prompt for confirmation here,
4944                but I don't think that's such a good idea */
4945             forwardMostMove = currentMove;
4946         }
4947         break;
4948
4949       case BeginningOfGame:
4950         if (appData.icsActive) return FALSE;
4951         if (!appData.noChessProgram) {
4952             if (!white_piece) {
4953                 DisplayMoveError(_("You are playing White"));
4954                 return FALSE;
4955             }
4956         }
4957         break;
4958         
4959       case Training:
4960         if (!white_piece && WhiteOnMove(currentMove)) {
4961             DisplayMoveError(_("It is White's turn"));
4962             return FALSE;
4963         }           
4964         if (white_piece && !WhiteOnMove(currentMove)) {
4965             DisplayMoveError(_("It is Black's turn"));
4966             return FALSE;
4967         }           
4968         break;
4969
4970       default:
4971       case IcsExamining:
4972         break;
4973     }
4974     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4975         && gameMode != AnalyzeFile && gameMode != Training) {
4976         DisplayMoveError(_("Displayed position is not current"));
4977         return FALSE;
4978     }
4979     return TRUE;
4980 }
4981
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4984 int lastLoadGameUseList = FALSE;
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4986 ChessMove lastLoadGameStart = (ChessMove) 0;
4987
4988
4989 ChessMove
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4991      int fromX, fromY, toX, toY;
4992      int promoChar;
4993 {
4994     ChessMove moveType;
4995     ChessSquare pdown, pup;
4996
4997     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4998     if ((fromX == toX) && (fromY == toY)) {
4999         return ImpossibleMove;
5000     }
5001
5002     /* [HGM] suppress all moves into holdings area and guard band */
5003     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5004             return ImpossibleMove;
5005
5006     /* [HGM] <sameColor> moved to here from winboard.c */
5007     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5008     pdown = boards[currentMove][fromY][fromX];
5009     pup = boards[currentMove][toY][toX];
5010     if (    gameMode != EditPosition &&
5011             (WhitePawn <= pdown && pdown < BlackPawn &&
5012              WhitePawn <= pup && pup < BlackPawn  ||
5013              BlackPawn <= pdown && pdown < EmptySquare &&
5014              BlackPawn <= pup && pup < EmptySquare 
5015             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5016                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5017                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5018         )           )
5019          return ImpossibleMove;
5020
5021     /* Check if the user is playing in turn.  This is complicated because we
5022        let the user "pick up" a piece before it is his turn.  So the piece he
5023        tried to pick up may have been captured by the time he puts it down!
5024        Therefore we use the color the user is supposed to be playing in this
5025        test, not the color of the piece that is currently on the starting
5026        square---except in EditGame mode, where the user is playing both
5027        sides; fortunately there the capture race can't happen.  (It can
5028        now happen in IcsExamining mode, but that's just too bad.  The user
5029        will get a somewhat confusing message in that case.)
5030        */
5031
5032     switch (gameMode) {
5033       case PlayFromGameFile:
5034       case AnalyzeFile:
5035       case TwoMachinesPlay:
5036       case EndOfGame:
5037       case IcsObserving:
5038       case IcsIdle:
5039         /* We switched into a game mode where moves are not accepted,
5040            perhaps while the mouse button was down. */
5041         return ImpossibleMove;
5042
5043       case MachinePlaysWhite:
5044         /* User is moving for Black */
5045         if (WhiteOnMove(currentMove)) {
5046             DisplayMoveError(_("It is White's turn"));
5047             return ImpossibleMove;
5048         }
5049         break;
5050
5051       case MachinePlaysBlack:
5052         /* User is moving for White */
5053         if (!WhiteOnMove(currentMove)) {
5054             DisplayMoveError(_("It is Black's turn"));
5055             return ImpossibleMove;
5056         }
5057         break;
5058
5059       case EditGame:
5060       case IcsExamining:
5061       case BeginningOfGame:
5062       case AnalyzeMode:
5063       case Training:
5064         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5065             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5066             /* User is moving for Black */
5067             if (WhiteOnMove(currentMove)) {
5068                 DisplayMoveError(_("It is White's turn"));
5069                 return ImpossibleMove;
5070             }
5071         } else {
5072             /* User is moving for White */
5073             if (!WhiteOnMove(currentMove)) {
5074                 DisplayMoveError(_("It is Black's turn"));
5075                 return ImpossibleMove;
5076             }
5077         }
5078         break;
5079
5080       case IcsPlayingBlack:
5081         /* User is moving for Black */
5082         if (WhiteOnMove(currentMove)) {
5083             if (!appData.premove) {
5084                 DisplayMoveError(_("It is White's turn"));
5085             } else if (toX >= 0 && toY >= 0) {
5086                 premoveToX = toX;
5087                 premoveToY = toY;
5088                 premoveFromX = fromX;
5089                 premoveFromY = fromY;
5090                 premovePromoChar = promoChar;
5091                 gotPremove = 1;
5092                 if (appData.debugMode) 
5093                     fprintf(debugFP, "Got premove: fromX %d,"
5094                             "fromY %d, toX %d, toY %d\n",
5095                             fromX, fromY, toX, toY);
5096             }
5097             return ImpossibleMove;
5098         }
5099         break;
5100
5101       case IcsPlayingWhite:
5102         /* User is moving for White */
5103         if (!WhiteOnMove(currentMove)) {
5104             if (!appData.premove) {
5105                 DisplayMoveError(_("It is Black's turn"));
5106             } else if (toX >= 0 && toY >= 0) {
5107                 premoveToX = toX;
5108                 premoveToY = toY;
5109                 premoveFromX = fromX;
5110                 premoveFromY = fromY;
5111                 premovePromoChar = promoChar;
5112                 gotPremove = 1;
5113                 if (appData.debugMode) 
5114                     fprintf(debugFP, "Got premove: fromX %d,"
5115                             "fromY %d, toX %d, toY %d\n",
5116                             fromX, fromY, toX, toY);
5117             }
5118             return ImpossibleMove;
5119         }
5120         break;
5121
5122       default:
5123         break;
5124
5125       case EditPosition:
5126         /* EditPosition, empty square, or different color piece;
5127            click-click move is possible */
5128         if (toX == -2 || toY == -2) {
5129             boards[0][fromY][fromX] = EmptySquare;
5130             return AmbiguousMove;
5131         } else if (toX >= 0 && toY >= 0) {
5132             boards[0][toY][toX] = boards[0][fromY][fromX];
5133             boards[0][fromY][fromX] = EmptySquare;
5134             return AmbiguousMove;
5135         }
5136         return ImpossibleMove;
5137     }
5138
5139     /* [HGM] If move started in holdings, it means a drop */
5140     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5141          if( pup != EmptySquare ) return ImpossibleMove;
5142          if(appData.testLegality) {
5143              /* it would be more logical if LegalityTest() also figured out
5144               * which drops are legal. For now we forbid pawns on back rank.
5145               * Shogi is on its own here...
5146               */
5147              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5148                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5149                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5150          }
5151          return WhiteDrop; /* Not needed to specify white or black yet */
5152     }
5153
5154     userOfferedDraw = FALSE;
5155         
5156     /* [HGM] always test for legality, to get promotion info */
5157     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5158                           epStatus[currentMove], castlingRights[currentMove],
5159                                          fromY, fromX, toY, toX, promoChar);
5160
5161     /* [HGM] but possibly ignore an IllegalMove result */
5162     if (appData.testLegality) {
5163         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5164             DisplayMoveError(_("Illegal move"));
5165             return ImpossibleMove;
5166         }
5167     }
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5169     return moveType;
5170     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5171        function is made into one that returns an OK move type if FinishMove
5172        should be called. This to give the calling driver routine the
5173        opportunity to finish the userMove input with a promotion popup,
5174        without bothering the user with this for invalid or illegal moves */
5175
5176 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5177 }
5178
5179 /* Common tail of UserMoveEvent and DropMenuEvent */
5180 int
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5182      ChessMove moveType;
5183      int fromX, fromY, toX, toY;
5184      /*char*/int promoChar;
5185 {
5186     char *bookHit = 0;
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5189         // [HGM] superchess: suppress promotions to non-available piece
5190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5191         if(WhiteOnMove(currentMove)) {
5192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5193         } else {
5194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5195         }
5196     }
5197
5198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5199        move type in caller when we know the move is a legal promotion */
5200     if(moveType == NormalMove && promoChar)
5201         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5203     /* [HGM] convert drag-and-drop piece drops to standard form */
5204     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5205          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5206          fromX = boards[currentMove][fromY][fromX];
5207          fromY = DROP_RANK;
5208     }
5209
5210     /* [HGM] <popupFix> The following if has been moved here from
5211        UserMoveEvent(). Because it seemed to belon here (why not allow
5212        piece drops in training games?), and because it can only be
5213        performed after it is known to what we promote. */
5214     if (gameMode == Training) {
5215       /* compare the move played on the board to the next move in the
5216        * game. If they match, display the move and the opponent's response. 
5217        * If they don't match, display an error message.
5218        */
5219       int saveAnimate;
5220       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5221       CopyBoard(testBoard, boards[currentMove]);
5222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5223
5224       if (CompareBoards(testBoard, boards[currentMove+1])) {
5225         ForwardInner(currentMove+1);
5226
5227         /* Autoplay the opponent's response.
5228          * if appData.animate was TRUE when Training mode was entered,
5229          * the response will be animated.
5230          */
5231         saveAnimate = appData.animate;
5232         appData.animate = animateTraining;
5233         ForwardInner(currentMove+1);
5234         appData.animate = saveAnimate;
5235
5236         /* check for the end of the game */
5237         if (currentMove >= forwardMostMove) {
5238           gameMode = PlayFromGameFile;
5239           ModeHighlight();
5240           SetTrainingModeOff();
5241           DisplayInformation(_("End of game"));
5242         }
5243       } else {
5244         DisplayError(_("Incorrect move"), 0);
5245       }
5246       return 1;
5247     }
5248
5249   /* Ok, now we know that the move is good, so we can kill
5250      the previous line in Analysis Mode */
5251   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5252     forwardMostMove = currentMove;
5253   }
5254
5255   /* If we need the chess program but it's dead, restart it */
5256   ResurrectChessProgram();
5257
5258   /* A user move restarts a paused game*/
5259   if (pausing)
5260     PauseEvent();
5261
5262   thinkOutput[0] = NULLCHAR;
5263
5264   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5265
5266   if (gameMode == BeginningOfGame) {
5267     if (appData.noChessProgram) {
5268       gameMode = EditGame;
5269       SetGameInfo();
5270     } else {
5271       char buf[MSG_SIZ];
5272       gameMode = MachinePlaysBlack;
5273       StartClocks();
5274       SetGameInfo();
5275       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5276       DisplayTitle(buf);
5277       if (first.sendName) {
5278         sprintf(buf, "name %s\n", gameInfo.white);
5279         SendToProgram(buf, &first);
5280       }
5281       StartClocks();
5282     }
5283     ModeHighlight();
5284   }
5285 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5286   /* Relay move to ICS or chess engine */
5287   if (appData.icsActive) {
5288     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5289         gameMode == IcsExamining) {
5290       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5291       ics_user_moved = 1;
5292     }
5293   } else {
5294     if (first.sendTime && (gameMode == BeginningOfGame ||
5295                            gameMode == MachinePlaysWhite ||
5296                            gameMode == MachinePlaysBlack)) {
5297       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5298     }
5299     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5300          // [HGM] book: if program might be playing, let it use book
5301         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5302         first.maybeThinking = TRUE;
5303     } else SendMoveToProgram(forwardMostMove-1, &first);
5304     if (currentMove == cmailOldMove + 1) {
5305       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5306     }
5307   }
5308
5309   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5310
5311   switch (gameMode) {
5312   case EditGame:
5313     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5314                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5315     case MT_NONE:
5316     case MT_CHECK:
5317       break;
5318     case MT_CHECKMATE:
5319     case MT_STAINMATE:
5320       if (WhiteOnMove(currentMove)) {
5321         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5322       } else {
5323         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5324       }
5325       break;
5326     case MT_STALEMATE:
5327       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5328       break;
5329     }
5330     break;
5331     
5332   case MachinePlaysBlack:
5333   case MachinePlaysWhite:
5334     /* disable certain menu options while machine is thinking */
5335     SetMachineThinkingEnables();
5336     break;
5337
5338   default:
5339     break;
5340   }
5341
5342   if(bookHit) { // [HGM] book: simulate book reply
5343         static char bookMove[MSG_SIZ]; // a bit generous?
5344
5345         programStats.nodes = programStats.depth = programStats.time = 
5346         programStats.score = programStats.got_only_move = 0;
5347         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5348
5349         strcpy(bookMove, "move ");
5350         strcat(bookMove, bookHit);
5351         HandleMachineMove(bookMove, &first);
5352   }
5353   return 1;
5354 }
5355
5356 void
5357 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5358      int fromX, fromY, toX, toY;
5359      int promoChar;
5360 {
5361     /* [HGM] This routine was added to allow calling of its two logical
5362        parts from other modules in the old way. Before, UserMoveEvent()
5363        automatically called FinishMove() if the move was OK, and returned
5364        otherwise. I separated the two, in order to make it possible to
5365        slip a promotion popup in between. But that it always needs two
5366        calls, to the first part, (now called UserMoveTest() ), and to
5367        FinishMove if the first part succeeded. Calls that do not need
5368        to do anything in between, can call this routine the old way. 
5369     */
5370     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5371 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5372     if(moveType != ImpossibleMove)
5373         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5374 }
5375
5376 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5377 {
5378 //    char * hint = lastHint;
5379     FrontEndProgramStats stats;
5380
5381     stats.which = cps == &first ? 0 : 1;
5382     stats.depth = cpstats->depth;
5383     stats.nodes = cpstats->nodes;
5384     stats.score = cpstats->score;
5385     stats.time = cpstats->time;
5386     stats.pv = cpstats->movelist;
5387     stats.hint = lastHint;
5388     stats.an_move_index = 0;
5389     stats.an_move_count = 0;
5390
5391     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5392         stats.hint = cpstats->move_name;
5393         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5394         stats.an_move_count = cpstats->nr_moves;
5395     }
5396
5397     SetProgramStats( &stats );
5398 }
5399
5400 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5401 {   // [HGM] book: this routine intercepts moves to simulate book replies
5402     char *bookHit = NULL;
5403
5404     //first determine if the incoming move brings opponent into his book
5405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5408     if(bookHit != NULL && !cps->bookSuspend) {
5409         // make sure opponent is not going to reply after receiving move to book position
5410         SendToProgram("force\n", cps);
5411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5412     }
5413     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5414     // now arrange restart after book miss
5415     if(bookHit) {
5416         // after a book hit we never send 'go', and the code after the call to this routine
5417         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5418         char buf[MSG_SIZ];
5419         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5420         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5421         SendToProgram(buf, cps);
5422         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5423     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5424         SendToProgram("go\n", cps);
5425         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5426     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5427         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5428             SendToProgram("go\n", cps); 
5429         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5430     }
5431     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5432 }
5433
5434 char *savedMessage;
5435 ChessProgramState *savedState;
5436 void DeferredBookMove(void)
5437 {
5438         if(savedState->lastPing != savedState->lastPong)
5439                     ScheduleDelayedEvent(DeferredBookMove, 10);
5440         else
5441         HandleMachineMove(savedMessage, savedState);
5442 }
5443
5444 void
5445 HandleMachineMove(message, cps)
5446      char *message;
5447      ChessProgramState *cps;
5448 {
5449     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5450     char realname[MSG_SIZ];
5451     int fromX, fromY, toX, toY;
5452     ChessMove moveType;
5453     char promoChar;
5454     char *p;
5455     int machineWhite;
5456     char *bookHit;
5457
5458 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5459     /*
5460      * Kludge to ignore BEL characters
5461      */
5462     while (*message == '\007') message++;
5463
5464     /*
5465      * [HGM] engine debug message: ignore lines starting with '#' character
5466      */
5467     if(cps->debug && *message == '#') return;
5468
5469     /*
5470      * Look for book output
5471      */
5472     if (cps == &first && bookRequested) {
5473         if (message[0] == '\t' || message[0] == ' ') {
5474             /* Part of the book output is here; append it */
5475             strcat(bookOutput, message);
5476             strcat(bookOutput, "  \n");
5477             return;
5478         } else if (bookOutput[0] != NULLCHAR) {
5479             /* All of book output has arrived; display it */
5480             char *p = bookOutput;
5481             while (*p != NULLCHAR) {
5482                 if (*p == '\t') *p = ' ';
5483                 p++;
5484             }
5485             DisplayInformation(bookOutput);
5486             bookRequested = FALSE;
5487             /* Fall through to parse the current output */
5488         }
5489     }
5490
5491     /*
5492      * Look for machine move.
5493      */
5494     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5495         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5496     {
5497         /* This method is only useful on engines that support ping */
5498         if (cps->lastPing != cps->lastPong) {
5499           if (gameMode == BeginningOfGame) {
5500             /* Extra move from before last new; ignore */
5501             if (appData.debugMode) {
5502                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5503             }
5504           } else {
5505             if (appData.debugMode) {
5506                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5507                         cps->which, gameMode);
5508             }
5509
5510             SendToProgram("undo\n", cps);
5511           }
5512           return;
5513         }
5514
5515         switch (gameMode) {
5516           case BeginningOfGame:
5517             /* Extra move from before last reset; ignore */
5518             if (appData.debugMode) {
5519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5520             }
5521             return;
5522
5523           case EndOfGame:
5524           case IcsIdle:
5525           default:
5526             /* Extra move after we tried to stop.  The mode test is
5527                not a reliable way of detecting this problem, but it's
5528                the best we can do on engines that don't support ping.
5529             */
5530             if (appData.debugMode) {
5531                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5532                         cps->which, gameMode);
5533             }
5534             SendToProgram("undo\n", cps);
5535             return;
5536
5537           case MachinePlaysWhite:
5538           case IcsPlayingWhite:
5539             machineWhite = TRUE;
5540             break;
5541
5542           case MachinePlaysBlack:
5543           case IcsPlayingBlack:
5544             machineWhite = FALSE;
5545             break;
5546
5547           case TwoMachinesPlay:
5548             machineWhite = (cps->twoMachinesColor[0] == 'w');
5549             break;
5550         }
5551         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5552             if (appData.debugMode) {
5553                 fprintf(debugFP,
5554                         "Ignoring move out of turn by %s, gameMode %d"
5555                         ", forwardMost %d\n",
5556                         cps->which, gameMode, forwardMostMove);
5557             }
5558             return;
5559         }
5560
5561     if (appData.debugMode) { int f = forwardMostMove;
5562         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5563                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5564     }
5565         if(cps->alphaRank) AlphaRank(machineMove, 4);
5566         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5567                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5568             /* Machine move could not be parsed; ignore it. */
5569             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5570                     machineMove, cps->which);
5571             DisplayError(buf1, 0);
5572             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5573                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5574             if (gameMode == TwoMachinesPlay) {
5575               GameEnds(machineWhite ? BlackWins : WhiteWins,
5576                        buf1, GE_XBOARD);
5577             }
5578             return;
5579         }
5580
5581         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5582         /* So we have to redo legality test with true e.p. status here,  */
5583         /* to make sure an illegal e.p. capture does not slip through,   */
5584         /* to cause a forfeit on a justified illegal-move complaint      */
5585         /* of the opponent.                                              */
5586         if( gameMode==TwoMachinesPlay && appData.testLegality
5587             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5588                                                               ) {
5589            ChessMove moveType;
5590            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5591                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5592                              fromY, fromX, toY, toX, promoChar);
5593             if (appData.debugMode) {
5594                 int i;
5595                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5596                     castlingRights[forwardMostMove][i], castlingRank[i]);
5597                 fprintf(debugFP, "castling rights\n");
5598             }
5599             if(moveType == IllegalMove) {
5600                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5601                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5602                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5603                            buf1, GE_XBOARD);
5604                 return;
5605            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5606            /* [HGM] Kludge to handle engines that send FRC-style castling
5607               when they shouldn't (like TSCP-Gothic) */
5608            switch(moveType) {
5609              case WhiteASideCastleFR:
5610              case BlackASideCastleFR:
5611                toX+=2;
5612                currentMoveString[2]++;
5613                break;
5614              case WhiteHSideCastleFR:
5615              case BlackHSideCastleFR:
5616                toX--;
5617                currentMoveString[2]--;
5618                break;
5619              default: ; // nothing to do, but suppresses warning of pedantic compilers
5620            }
5621         }
5622         hintRequested = FALSE;
5623         lastHint[0] = NULLCHAR;
5624         bookRequested = FALSE;
5625         /* Program may be pondering now */
5626         cps->maybeThinking = TRUE;
5627         if (cps->sendTime == 2) cps->sendTime = 1;
5628         if (cps->offeredDraw) cps->offeredDraw--;
5629
5630 #if ZIPPY
5631         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5632             first.initDone) {
5633           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5634           ics_user_moved = 1;
5635           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5636                 char buf[3*MSG_SIZ];
5637
5638                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5639                         programStats.score / 100.,
5640                         programStats.depth,
5641                         programStats.time / 100.,
5642                         u64ToDouble(programStats.nodes),
5643                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5644                         programStats.movelist);
5645                 SendToICS(buf);
5646           }
5647         }
5648 #endif
5649         /* currentMoveString is set as a side-effect of ParseOneMove */
5650         strcpy(machineMove, currentMoveString);
5651         strcat(machineMove, "\n");
5652         strcpy(moveList[forwardMostMove], machineMove);
5653
5654         /* [AS] Save move info and clear stats for next move */
5655         pvInfoList[ forwardMostMove ].score = programStats.score;
5656         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5657         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5658         ClearProgramStats();
5659         thinkOutput[0] = NULLCHAR;
5660         hiddenThinkOutputState = 0;
5661
5662         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5663
5664         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5665         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5666             int count = 0;
5667
5668             while( count < adjudicateLossPlies ) {
5669                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5670
5671                 if( count & 1 ) {
5672                     score = -score; /* Flip score for winning side */
5673                 }
5674
5675                 if( score > adjudicateLossThreshold ) {
5676                     break;
5677                 }
5678
5679                 count++;
5680             }
5681
5682             if( count >= adjudicateLossPlies ) {
5683                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5684
5685                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5686                     "Xboard adjudication", 
5687                     GE_XBOARD );
5688
5689                 return;
5690             }
5691         }
5692
5693         if( gameMode == TwoMachinesPlay ) {
5694           // [HGM] some adjudications useful with buggy engines
5695             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5696           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5697
5698
5699             if( appData.testLegality )
5700             {   /* [HGM] Some more adjudications for obstinate engines */
5701                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5702                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5703                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5704                 static int moveCount = 6;
5705                 ChessMove result;
5706                 char *reason = NULL;
5707
5708                 /* Count what is on board. */
5709                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5710                 {   ChessSquare p = boards[forwardMostMove][i][j];
5711                     int m=i;
5712
5713                     switch((int) p)
5714                     {   /* count B,N,R and other of each side */
5715                         case WhiteKing:
5716                         case BlackKing:
5717                              NrK++; break; // [HGM] atomic: count Kings
5718                         case WhiteKnight:
5719                              NrWN++; break;
5720                         case WhiteBishop:
5721                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5722                              bishopsColor |= 1 << ((i^j)&1);
5723                              NrWB++; break;
5724                         case BlackKnight:
5725                              NrBN++; break;
5726                         case BlackBishop:
5727                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5728                              bishopsColor |= 1 << ((i^j)&1);
5729                              NrBB++; break;
5730                         case WhiteRook:
5731                              NrWR++; break;
5732                         case BlackRook:
5733                              NrBR++; break;
5734                         case WhiteQueen:
5735                              NrWQ++; break;
5736                         case BlackQueen:
5737                              NrBQ++; break;
5738                         case EmptySquare: 
5739                              break;
5740                         case BlackPawn:
5741                              m = 7-i;
5742                         case WhitePawn:
5743                              PawnAdvance += m; NrPawns++;
5744                     }
5745                     NrPieces += (p != EmptySquare);
5746                     NrW += ((int)p < (int)BlackPawn);
5747                     if(gameInfo.variant == VariantXiangqi && 
5748                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5749                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5750                         NrW -= ((int)p < (int)BlackPawn);
5751                     }
5752                 }
5753
5754                 /* Some material-based adjudications that have to be made before stalemate test */
5755                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5756                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5757                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5758                      if(appData.checkMates) {
5759                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5760                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5761                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5762                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5763                          return;
5764                      }
5765                 }
5766
5767                 /* Bare King in Shatranj (loses) or Losers (wins) */
5768                 if( NrW == 1 || NrPieces - NrW == 1) {
5769                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5770                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5771                      if(appData.checkMates) {
5772                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5773                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5774                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5775                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5776                          return;
5777                      }
5778                   } else
5779                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5780                   {    /* bare King */
5781                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5782                         if(appData.checkMates) {
5783                             /* but only adjudicate if adjudication enabled */
5784                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5785                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5787                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5788                             return;
5789                         }
5790                   }
5791                 } else bare = 1;
5792
5793
5794             // don't wait for engine to announce game end if we can judge ourselves
5795             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5796                                        castlingRights[forwardMostMove]) ) {
5797               case MT_CHECK:
5798                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5799                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5800                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5801                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5802                             checkCnt++;
5803                         if(checkCnt >= 2) {
5804                             reason = "Xboard adjudication: 3rd check";
5805                             epStatus[forwardMostMove] = EP_CHECKMATE;
5806                             break;
5807                         }
5808                     }
5809                 }
5810               case MT_NONE:
5811               default:
5812                 break;
5813               case MT_STALEMATE:
5814               case MT_STAINMATE:
5815                 reason = "Xboard adjudication: Stalemate";
5816                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5817                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5818                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5819                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5820                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5821                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5822                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5823                                                                         EP_CHECKMATE : EP_WINS);
5824                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5825                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5826                 }
5827                 break;
5828               case MT_CHECKMATE:
5829                 reason = "Xboard adjudication: Checkmate";
5830                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5831                 break;
5832             }
5833
5834                 switch(i = epStatus[forwardMostMove]) {
5835                     case EP_STALEMATE:
5836                         result = GameIsDrawn; break;
5837                     case EP_CHECKMATE:
5838                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5839                     case EP_WINS:
5840                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5841                     default:
5842                         result = (ChessMove) 0;
5843                 }
5844                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5845                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5846                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847                     GameEnds( result, reason, GE_XBOARD );
5848                     return;
5849                 }
5850
5851                 /* Next absolutely insufficient mating material. */
5852                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5853                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5854                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5855                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5856                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5857
5858                      /* always flag draws, for judging claims */
5859                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5860
5861                      if(appData.materialDraws) {
5862                          /* but only adjudicate them if adjudication enabled */
5863                          SendToProgram("force\n", cps->other); // suppress reply
5864                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5865                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5866                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5867                          return;
5868                      }
5869                 }
5870
5871                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5872                 if(NrPieces == 4 && 
5873                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5874                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5875                    || NrWN==2 || NrBN==2     /* KNNK */
5876                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5877                   ) ) {
5878                      if(--moveCount < 0 && appData.trivialDraws)
5879                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5880                           SendToProgram("force\n", cps->other); // suppress reply
5881                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5882                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5883                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5884                           return;
5885                      }
5886                 } else moveCount = 6;
5887             }
5888           }
5889 #if 1
5890     if (appData.debugMode) { int i;
5891       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5892               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5893               appData.drawRepeats);
5894       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5895            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5896
5897     }
5898 #endif
5899                 /* Check for rep-draws */
5900                 count = 0;
5901                 for(k = forwardMostMove-2;
5902                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5903                         epStatus[k] < EP_UNKNOWN &&
5904                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5905                     k-=2)
5906                 {   int rights=0;
5907 #if 0
5908     if (appData.debugMode) {
5909       fprintf(debugFP, " loop\n");
5910     }
5911 #endif
5912                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5913 #if 0
5914     if (appData.debugMode) {
5915       fprintf(debugFP, "match\n");
5916     }
5917 #endif
5918                         /* compare castling rights */
5919                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5920                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5921                                 rights++; /* King lost rights, while rook still had them */
5922                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5923                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5924                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5925                                    rights++; /* but at least one rook lost them */
5926                         }
5927                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5928                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5929                                 rights++; 
5930                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5931                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5932                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5933                                    rights++;
5934                         }
5935 #if 0
5936     if (appData.debugMode) {
5937       for(i=0; i<nrCastlingRights; i++)
5938       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5939     }
5940
5941     if (appData.debugMode) {
5942       fprintf(debugFP, " %d %d\n", rights, k);
5943     }
5944 #endif
5945                         if( rights == 0 && ++count > appData.drawRepeats-2
5946                             && appData.drawRepeats > 1) {
5947                              /* adjudicate after user-specified nr of repeats */
5948                              SendToProgram("force\n", cps->other); // suppress reply
5949                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5950                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5951                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5952                                 // [HGM] xiangqi: check for forbidden perpetuals
5953                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5954                                 for(m=forwardMostMove; m>k; m-=2) {
5955                                     if(MateTest(boards[m], PosFlags(m), 
5956                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5957                                         ourPerpetual = 0; // the current mover did not always check
5958                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5959                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5960                                         hisPerpetual = 0; // the opponent did not always check
5961                                 }
5962                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5963                                                                         ourPerpetual, hisPerpetual);
5964                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5965                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5966                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5967                                     return;
5968                                 }
5969                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5970                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5971                                 // Now check for perpetual chases
5972                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5973                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5974                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5975                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5976                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5977                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5978                                         return;
5979                                     }
5980                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5981                                         break; // Abort repetition-checking loop.
5982                                 }
5983                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5984                              }
5985                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5986                              return;
5987                         }
5988                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5989                              epStatus[forwardMostMove] = EP_REP_DRAW;
5990                     }
5991                 }
5992
5993                 /* Now we test for 50-move draws. Determine ply count */
5994                 count = forwardMostMove;
5995                 /* look for last irreversble move */
5996                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5997                     count--;
5998                 /* if we hit starting position, add initial plies */
5999                 if( count == backwardMostMove )
6000                     count -= initialRulePlies;
6001                 count = forwardMostMove - count; 
6002                 if( count >= 100)
6003                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6004                          /* this is used to judge if draw claims are legal */
6005                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6006                          SendToProgram("force\n", cps->other); // suppress reply
6007                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6008                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6009                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6010                          return;
6011                 }
6012
6013                 /* if draw offer is pending, treat it as a draw claim
6014                  * when draw condition present, to allow engines a way to
6015                  * claim draws before making their move to avoid a race
6016                  * condition occurring after their move
6017                  */
6018                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6019                          char *p = NULL;
6020                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6021                              p = "Draw claim: 50-move rule";
6022                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6023                              p = "Draw claim: 3-fold repetition";
6024                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6025                              p = "Draw claim: insufficient mating material";
6026                          if( p != NULL ) {
6027                              SendToProgram("force\n", cps->other); // suppress reply
6028                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6029                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6030                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6031                              return;
6032                          }
6033                 }
6034
6035
6036                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6037                     SendToProgram("force\n", cps->other); // suppress reply
6038                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6039                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040
6041                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6042
6043                     return;
6044                 }
6045         }
6046
6047         bookHit = NULL;
6048         if (gameMode == TwoMachinesPlay) {
6049             /* [HGM] relaying draw offers moved to after reception of move */
6050             /* and interpreting offer as claim if it brings draw condition */
6051             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6052                 SendToProgram("draw\n", cps->other);
6053             }
6054             if (cps->other->sendTime) {
6055                 SendTimeRemaining(cps->other,
6056                                   cps->other->twoMachinesColor[0] == 'w');
6057             }
6058             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6059             if (firstMove && !bookHit) {
6060                 firstMove = FALSE;
6061                 if (cps->other->useColors) {
6062                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6063                 }
6064                 SendToProgram("go\n", cps->other);
6065             }
6066             cps->other->maybeThinking = TRUE;
6067         }
6068
6069         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6070         
6071         if (!pausing && appData.ringBellAfterMoves) {
6072             RingBell();
6073         }
6074
6075         /* 
6076          * Reenable menu items that were disabled while
6077          * machine was thinking
6078          */
6079         if (gameMode != TwoMachinesPlay)
6080             SetUserThinkingEnables();
6081
6082         // [HGM] book: after book hit opponent has received move and is now in force mode
6083         // force the book reply into it, and then fake that it outputted this move by jumping
6084         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6085         if(bookHit) {
6086                 static char bookMove[MSG_SIZ]; // a bit generous?
6087
6088                 strcpy(bookMove, "move ");
6089                 strcat(bookMove, bookHit);
6090                 message = bookMove;
6091                 cps = cps->other;
6092                 programStats.nodes = programStats.depth = programStats.time = 
6093                 programStats.score = programStats.got_only_move = 0;
6094                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6095
6096                 if(cps->lastPing != cps->lastPong) {
6097                     savedMessage = message; // args for deferred call
6098                     savedState = cps;
6099                     ScheduleDelayedEvent(DeferredBookMove, 10);
6100                     return;
6101                 }
6102                 goto FakeBookMove;
6103         }
6104
6105         return;
6106     }
6107
6108     /* Set special modes for chess engines.  Later something general
6109      *  could be added here; for now there is just one kludge feature,
6110      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6111      *  when "xboard" is given as an interactive command.
6112      */
6113     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6114         cps->useSigint = FALSE;
6115         cps->useSigterm = FALSE;
6116     }
6117
6118     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6119      * want this, I was asked to put it in, and obliged.
6120      */
6121     if (!strncmp(message, "setboard ", 9)) {
6122         Board initial_position; int i;
6123
6124         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6125
6126         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6127             DisplayError(_("Bad FEN received from engine"), 0);
6128             return ;
6129         } else {
6130            Reset(FALSE, FALSE);
6131            CopyBoard(boards[0], initial_position);
6132            initialRulePlies = FENrulePlies;
6133            epStatus[0] = FENepStatus;
6134            for( i=0; i<nrCastlingRights; i++ )
6135                 castlingRights[0][i] = FENcastlingRights[i];
6136            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6137            else gameMode = MachinePlaysBlack;                 
6138            DrawPosition(FALSE, boards[currentMove]);
6139         }
6140         return;
6141     }
6142
6143     /*
6144      * Look for communication commands
6145      */
6146     if (!strncmp(message, "telluser ", 9)) {
6147         DisplayNote(message + 9);
6148         return;
6149     }
6150     if (!strncmp(message, "tellusererror ", 14)) {
6151         DisplayError(message + 14, 0);
6152         return;
6153     }
6154     if (!strncmp(message, "tellopponent ", 13)) {
6155       if (appData.icsActive) {
6156         if (loggedOn) {
6157           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6158           SendToICS(buf1);
6159         }
6160       } else {
6161         DisplayNote(message + 13);
6162       }
6163       return;
6164     }
6165     if (!strncmp(message, "tellothers ", 11)) {
6166       if (appData.icsActive) {
6167         if (loggedOn) {
6168           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6169           SendToICS(buf1);
6170         }
6171       }
6172       return;
6173     }
6174     if (!strncmp(message, "tellall ", 8)) {
6175       if (appData.icsActive) {
6176         if (loggedOn) {
6177           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6178           SendToICS(buf1);
6179         }
6180       } else {
6181         DisplayNote(message + 8);
6182       }
6183       return;
6184     }
6185     if (strncmp(message, "warning", 7) == 0) {
6186         /* Undocumented feature, use tellusererror in new code */
6187         DisplayError(message, 0);
6188         return;
6189     }
6190     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6191         strcpy(realname, cps->tidy);
6192         strcat(realname, " query");
6193         AskQuestion(realname, buf2, buf1, cps->pr);
6194         return;
6195     }
6196     /* Commands from the engine directly to ICS.  We don't allow these to be 
6197      *  sent until we are logged on. Crafty kibitzes have been known to 
6198      *  interfere with the login process.
6199      */
6200     if (loggedOn) {
6201         if (!strncmp(message, "tellics ", 8)) {
6202             SendToICS(message + 8);
6203             SendToICS("\n");
6204             return;
6205         }
6206         if (!strncmp(message, "tellicsnoalias ", 15)) {
6207             SendToICS(ics_prefix);
6208             SendToICS(message + 15);
6209             SendToICS("\n");
6210             return;
6211         }
6212         /* The following are for backward compatibility only */
6213         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6214             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6215             SendToICS(ics_prefix);
6216             SendToICS(message);
6217             SendToICS("\n");
6218             return;
6219         }
6220     }
6221     if (strncmp(message, "feature ", 8) == 0) {
6222       ParseFeatures(message+8, cps);
6223     }
6224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6225         return;
6226     }
6227     /*
6228      * If the move is illegal, cancel it and redraw the board.
6229      * Also deal with other error cases.  Matching is rather loose
6230      * here to accommodate engines written before the spec.
6231      */
6232     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6233         strncmp(message, "Error", 5) == 0) {
6234         if (StrStr(message, "name") || 
6235             StrStr(message, "rating") || StrStr(message, "?") ||
6236             StrStr(message, "result") || StrStr(message, "board") ||
6237             StrStr(message, "bk") || StrStr(message, "computer") ||
6238             StrStr(message, "variant") || StrStr(message, "hint") ||
6239             StrStr(message, "random") || StrStr(message, "depth") ||
6240             StrStr(message, "accepted")) {
6241             return;
6242         }
6243         if (StrStr(message, "protover")) {
6244           /* Program is responding to input, so it's apparently done
6245              initializing, and this error message indicates it is
6246              protocol version 1.  So we don't need to wait any longer
6247              for it to initialize and send feature commands. */
6248           FeatureDone(cps, 1);
6249           cps->protocolVersion = 1;
6250           return;
6251         }
6252         cps->maybeThinking = FALSE;
6253
6254         if (StrStr(message, "draw")) {
6255             /* Program doesn't have "draw" command */
6256             cps->sendDrawOffers = 0;
6257             return;
6258         }
6259         if (cps->sendTime != 1 &&
6260             (StrStr(message, "time") || StrStr(message, "otim"))) {
6261           /* Program apparently doesn't have "time" or "otim" command */
6262           cps->sendTime = 0;
6263           return;
6264         }
6265         if (StrStr(message, "analyze")) {
6266             cps->analysisSupport = FALSE;
6267             cps->analyzing = FALSE;
6268             Reset(FALSE, TRUE);
6269             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6270             DisplayError(buf2, 0);
6271             return;
6272         }
6273         if (StrStr(message, "(no matching move)st")) {
6274           /* Special kludge for GNU Chess 4 only */
6275           cps->stKludge = TRUE;
6276           SendTimeControl(cps, movesPerSession, timeControl,
6277                           timeIncrement, appData.searchDepth,
6278                           searchTime);
6279           return;
6280         }
6281         if (StrStr(message, "(no matching move)sd")) {
6282           /* Special kludge for GNU Chess 4 only */
6283           cps->sdKludge = TRUE;
6284           SendTimeControl(cps, movesPerSession, timeControl,
6285                           timeIncrement, appData.searchDepth,
6286                           searchTime);
6287           return;
6288         }
6289         if (!StrStr(message, "llegal")) {
6290             return;
6291         }
6292         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6293             gameMode == IcsIdle) return;
6294         if (forwardMostMove <= backwardMostMove) return;
6295 #if 0
6296         /* Following removed: it caused a bug where a real illegal move
6297            message in analyze mored would be ignored. */
6298         if (cps == &first && programStats.ok_to_send == 0) {
6299             /* Bogus message from Crafty responding to "."  This filtering
6300                can miss some of the bad messages, but fortunately the bug 
6301                is fixed in current Crafty versions, so it doesn't matter. */
6302             return;
6303         }
6304 #endif
6305         if (pausing) PauseEvent();
6306         if (gameMode == PlayFromGameFile) {
6307             /* Stop reading this game file */
6308             gameMode = EditGame;
6309             ModeHighlight();
6310         }
6311         currentMove = --forwardMostMove;
6312         DisplayMove(currentMove-1); /* before DisplayMoveError */
6313         SwitchClocks();
6314         DisplayBothClocks();
6315         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6316                 parseList[currentMove], cps->which);
6317         DisplayMoveError(buf1);
6318         DrawPosition(FALSE, boards[currentMove]);
6319
6320         /* [HGM] illegal-move claim should forfeit game when Xboard */
6321         /* only passes fully legal moves                            */
6322         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6323             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6324                                 "False illegal-move claim", GE_XBOARD );
6325         }
6326         return;
6327     }
6328     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6329         /* Program has a broken "time" command that
6330            outputs a string not ending in newline.
6331            Don't use it. */
6332         cps->sendTime = 0;
6333     }
6334     
6335     /*
6336      * If chess program startup fails, exit with an error message.
6337      * Attempts to recover here are futile.
6338      */
6339     if ((StrStr(message, "unknown host") != NULL)
6340         || (StrStr(message, "No remote directory") != NULL)
6341         || (StrStr(message, "not found") != NULL)
6342         || (StrStr(message, "No such file") != NULL)
6343         || (StrStr(message, "can't alloc") != NULL)
6344         || (StrStr(message, "Permission denied") != NULL)) {
6345
6346         cps->maybeThinking = FALSE;
6347         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6348                 cps->which, cps->program, cps->host, message);
6349         RemoveInputSource(cps->isr);
6350         DisplayFatalError(buf1, 0, 1);
6351         return;
6352     }
6353     
6354     /* 
6355      * Look for hint output
6356      */
6357     if (sscanf(message, "Hint: %s", buf1) == 1) {
6358         if (cps == &first && hintRequested) {
6359             hintRequested = FALSE;
6360             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6361                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6362                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6363                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6364                                     fromY, fromX, toY, toX, promoChar, buf1);
6365                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6366                 DisplayInformation(buf2);
6367             } else {
6368                 /* Hint move could not be parsed!? */
6369               snprintf(buf2, sizeof(buf2),
6370                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6371                         buf1, cps->which);
6372                 DisplayError(buf2, 0);
6373             }
6374         } else {
6375             strcpy(lastHint, buf1);
6376         }
6377         return;
6378     }
6379
6380     /*
6381      * Ignore other messages if game is not in progress
6382      */
6383     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6384         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6385
6386     /*
6387      * look for win, lose, draw, or draw offer
6388      */
6389     if (strncmp(message, "1-0", 3) == 0) {
6390         char *p, *q, *r = "";
6391         p = strchr(message, '{');
6392         if (p) {
6393             q = strchr(p, '}');
6394             if (q) {
6395                 *q = NULLCHAR;
6396                 r = p + 1;
6397             }
6398         }
6399         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6400         return;
6401     } else if (strncmp(message, "0-1", 3) == 0) {
6402         char *p, *q, *r = "";
6403         p = strchr(message, '{');
6404         if (p) {
6405             q = strchr(p, '}');
6406             if (q) {
6407                 *q = NULLCHAR;
6408                 r = p + 1;
6409             }
6410         }
6411         /* Kludge for Arasan 4.1 bug */
6412         if (strcmp(r, "Black resigns") == 0) {
6413             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6414             return;
6415         }
6416         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6417         return;
6418     } else if (strncmp(message, "1/2", 3) == 0) {
6419         char *p, *q, *r = "";
6420         p = strchr(message, '{');
6421         if (p) {
6422             q = strchr(p, '}');
6423             if (q) {
6424                 *q = NULLCHAR;
6425                 r = p + 1;
6426             }
6427         }
6428             
6429         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6430         return;
6431
6432     } else if (strncmp(message, "White resign", 12) == 0) {
6433         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6434         return;
6435     } else if (strncmp(message, "Black resign", 12) == 0) {
6436         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6437         return;
6438     } else if (strncmp(message, "White matches", 13) == 0 ||
6439                strncmp(message, "Black matches", 13) == 0   ) {
6440         /* [HGM] ignore GNUShogi noises */
6441         return;
6442     } else if (strncmp(message, "White", 5) == 0 &&
6443                message[5] != '(' &&
6444                StrStr(message, "Black") == NULL) {
6445         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "Black", 5) == 0 &&
6448                message[5] != '(') {
6449         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6450         return;
6451     } else if (strcmp(message, "resign") == 0 ||
6452                strcmp(message, "computer resigns") == 0) {
6453         switch (gameMode) {
6454           case MachinePlaysBlack:
6455           case IcsPlayingBlack:
6456             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6457             break;
6458           case MachinePlaysWhite:
6459           case IcsPlayingWhite:
6460             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6461             break;
6462           case TwoMachinesPlay:
6463             if (cps->twoMachinesColor[0] == 'w')
6464               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6465             else
6466               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6467             break;
6468           default:
6469             /* can't happen */
6470             break;
6471         }
6472         return;
6473     } else if (strncmp(message, "opponent mates", 14) == 0) {
6474         switch (gameMode) {
6475           case MachinePlaysBlack:
6476           case IcsPlayingBlack:
6477             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6478             break;
6479           case MachinePlaysWhite:
6480           case IcsPlayingWhite:
6481             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6482             break;
6483           case TwoMachinesPlay:
6484             if (cps->twoMachinesColor[0] == 'w')
6485               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6486             else
6487               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6488             break;
6489           default:
6490             /* can't happen */
6491             break;
6492         }
6493         return;
6494     } else if (strncmp(message, "computer mates", 14) == 0) {
6495         switch (gameMode) {
6496           case MachinePlaysBlack:
6497           case IcsPlayingBlack:
6498             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6499             break;
6500           case MachinePlaysWhite:
6501           case IcsPlayingWhite:
6502             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6503             break;
6504           case TwoMachinesPlay:
6505             if (cps->twoMachinesColor[0] == 'w')
6506               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6507             else
6508               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6509             break;
6510           default:
6511             /* can't happen */
6512             break;
6513         }
6514         return;
6515     } else if (strncmp(message, "checkmate", 9) == 0) {
6516         if (WhiteOnMove(forwardMostMove)) {
6517             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6518         } else {
6519             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520         }
6521         return;
6522     } else if (strstr(message, "Draw") != NULL ||
6523                strstr(message, "game is a draw") != NULL) {
6524         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6525         return;
6526     } else if (strstr(message, "offer") != NULL &&
6527                strstr(message, "draw") != NULL) {
6528 #if ZIPPY
6529         if (appData.zippyPlay && first.initDone) {
6530             /* Relay offer to ICS */
6531             SendToICS(ics_prefix);
6532             SendToICS("draw\n");
6533         }
6534 #endif
6535         cps->offeredDraw = 2; /* valid until this engine moves twice */
6536         if (gameMode == TwoMachinesPlay) {
6537             if (cps->other->offeredDraw) {
6538                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6539             /* [HGM] in two-machine mode we delay relaying draw offer      */
6540             /* until after we also have move, to see if it is really claim */
6541             }
6542 #if 0
6543               else {
6544                 if (cps->other->sendDrawOffers) {
6545                     SendToProgram("draw\n", cps->other);
6546                 }
6547             }
6548 #endif
6549         } else if (gameMode == MachinePlaysWhite ||
6550                    gameMode == MachinePlaysBlack) {
6551           if (userOfferedDraw) {
6552             DisplayInformation(_("Machine accepts your draw offer"));
6553             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6554           } else {
6555             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6556           }
6557         }
6558     }
6559
6560     
6561     /*
6562      * Look for thinking output
6563      */
6564     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6565           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6566                                 ) {
6567         int plylev, mvleft, mvtot, curscore, time;
6568         char mvname[MOVE_LEN];
6569         u64 nodes; // [DM]
6570         char plyext;
6571         int ignore = FALSE;
6572         int prefixHint = FALSE;
6573         mvname[0] = NULLCHAR;
6574
6575         switch (gameMode) {
6576           case MachinePlaysBlack:
6577           case IcsPlayingBlack:
6578             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6579             break;
6580           case MachinePlaysWhite:
6581           case IcsPlayingWhite:
6582             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6583             break;
6584           case AnalyzeMode:
6585           case AnalyzeFile:
6586             break;
6587           case IcsObserving: /* [DM] icsEngineAnalyze */
6588             if (!appData.icsEngineAnalyze) ignore = TRUE;
6589             break;
6590           case TwoMachinesPlay:
6591             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6592                 ignore = TRUE;
6593             }
6594             break;
6595           default:
6596             ignore = TRUE;
6597             break;
6598         }
6599
6600         if (!ignore) {
6601             buf1[0] = NULLCHAR;
6602             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6603                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6604
6605                 if (plyext != ' ' && plyext != '\t') {
6606                     time *= 100;
6607                 }
6608
6609                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6610                 if( cps->scoreIsAbsolute && 
6611                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6612                 {
6613                     curscore = -curscore;
6614                 }
6615
6616
6617                 programStats.depth = plylev;
6618                 programStats.nodes = nodes;
6619                 programStats.time = time;
6620                 programStats.score = curscore;
6621                 programStats.got_only_move = 0;
6622
6623                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6624                         int ticklen;
6625
6626                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6627                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6628                         if(WhiteOnMove(forwardMostMove)) 
6629                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6630                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6631                 }
6632
6633                 /* Buffer overflow protection */
6634                 if (buf1[0] != NULLCHAR) {
6635                     if (strlen(buf1) >= sizeof(programStats.movelist)
6636                         && appData.debugMode) {
6637                         fprintf(debugFP,
6638                                 "PV is too long; using the first %d bytes.\n",
6639                                 sizeof(programStats.movelist) - 1);
6640                     }
6641
6642                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6643                 } else {
6644                     sprintf(programStats.movelist, " no PV\n");
6645                 }
6646
6647                 if (programStats.seen_stat) {
6648                     programStats.ok_to_send = 1;
6649                 }
6650
6651                 if (strchr(programStats.movelist, '(') != NULL) {
6652                     programStats.line_is_book = 1;
6653                     programStats.nr_moves = 0;
6654                     programStats.moves_left = 0;
6655                 } else {
6656                     programStats.line_is_book = 0;
6657                 }
6658
6659                 SendProgramStatsToFrontend( cps, &programStats );
6660
6661                 /* 
6662                     [AS] Protect the thinkOutput buffer from overflow... this
6663                     is only useful if buf1 hasn't overflowed first!
6664                 */
6665                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6666                         plylev, 
6667                         (gameMode == TwoMachinesPlay ?
6668                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6669                         ((double) curscore) / 100.0,
6670                         prefixHint ? lastHint : "",
6671                         prefixHint ? " " : "" );
6672
6673                 if( buf1[0] != NULLCHAR ) {
6674                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6675
6676                     if( strlen(buf1) > max_len ) {
6677                         if( appData.debugMode) {
6678                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6679                         }
6680                         buf1[max_len+1] = '\0';
6681                     }
6682
6683                     strcat( thinkOutput, buf1 );
6684                 }
6685
6686                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6687                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6688                     DisplayMove(currentMove - 1);
6689                     DisplayAnalysis();
6690                 }
6691                 return;
6692
6693             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6694                 /* crafty (9.25+) says "(only move) <move>"
6695                  * if there is only 1 legal move
6696                  */
6697                 sscanf(p, "(only move) %s", buf1);
6698                 sprintf(thinkOutput, "%s (only move)", buf1);
6699                 sprintf(programStats.movelist, "%s (only move)", buf1);
6700                 programStats.depth = 1;
6701                 programStats.nr_moves = 1;
6702                 programStats.moves_left = 1;
6703                 programStats.nodes = 1;
6704                 programStats.time = 1;
6705                 programStats.got_only_move = 1;
6706
6707                 /* Not really, but we also use this member to
6708                    mean "line isn't going to change" (Crafty
6709                    isn't searching, so stats won't change) */
6710                 programStats.line_is_book = 1;
6711
6712                 SendProgramStatsToFrontend( cps, &programStats );
6713                 
6714                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6715                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6716                     DisplayMove(currentMove - 1);
6717                     DisplayAnalysis();
6718                 }
6719                 return;
6720             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6721                               &time, &nodes, &plylev, &mvleft,
6722                               &mvtot, mvname) >= 5) {
6723                 /* The stat01: line is from Crafty (9.29+) in response
6724                    to the "." command */
6725                 programStats.seen_stat = 1;
6726                 cps->maybeThinking = TRUE;
6727
6728                 if (programStats.got_only_move || !appData.periodicUpdates)
6729                   return;
6730
6731                 programStats.depth = plylev;
6732                 programStats.time = time;
6733                 programStats.nodes = nodes;
6734                 programStats.moves_left = mvleft;
6735                 programStats.nr_moves = mvtot;
6736                 strcpy(programStats.move_name, mvname);
6737                 programStats.ok_to_send = 1;
6738                 programStats.movelist[0] = '\0';
6739
6740                 SendProgramStatsToFrontend( cps, &programStats );
6741
6742                 DisplayAnalysis();
6743                 return;
6744
6745             } else if (strncmp(message,"++",2) == 0) {
6746                 /* Crafty 9.29+ outputs this */
6747                 programStats.got_fail = 2;
6748                 return;
6749
6750             } else if (strncmp(message,"--",2) == 0) {
6751                 /* Crafty 9.29+ outputs this */
6752                 programStats.got_fail = 1;
6753                 return;
6754
6755             } else if (thinkOutput[0] != NULLCHAR &&
6756                        strncmp(message, "    ", 4) == 0) {
6757                 unsigned message_len;
6758
6759                 p = message;
6760                 while (*p && *p == ' ') p++;
6761
6762                 message_len = strlen( p );
6763
6764                 /* [AS] Avoid buffer overflow */
6765                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6766                     strcat(thinkOutput, " ");
6767                     strcat(thinkOutput, p);
6768                 }
6769
6770                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6771                     strcat(programStats.movelist, " ");
6772                     strcat(programStats.movelist, p);
6773                 }
6774
6775                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6776                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6777                     DisplayMove(currentMove - 1);
6778                     DisplayAnalysis();
6779                 }
6780                 return;
6781             }
6782         }
6783         else {
6784             buf1[0] = NULLCHAR;
6785
6786             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6787                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6788             {
6789                 ChessProgramStats cpstats;
6790
6791                 if (plyext != ' ' && plyext != '\t') {
6792                     time *= 100;
6793                 }
6794
6795                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6796                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6797                     curscore = -curscore;
6798                 }
6799
6800                 cpstats.depth = plylev;
6801                 cpstats.nodes = nodes;
6802                 cpstats.time = time;
6803                 cpstats.score = curscore;
6804                 cpstats.got_only_move = 0;
6805                 cpstats.movelist[0] = '\0';
6806
6807                 if (buf1[0] != NULLCHAR) {
6808                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6809                 }
6810
6811                 cpstats.ok_to_send = 0;
6812                 cpstats.line_is_book = 0;
6813                 cpstats.nr_moves = 0;
6814                 cpstats.moves_left = 0;
6815
6816                 SendProgramStatsToFrontend( cps, &cpstats );
6817             }
6818         }
6819     }
6820 }
6821
6822
6823 /* Parse a game score from the character string "game", and
6824    record it as the history of the current game.  The game
6825    score is NOT assumed to start from the standard position. 
6826    The display is not updated in any way.
6827    */
6828 void
6829 ParseGameHistory(game)
6830      char *game;
6831 {
6832     ChessMove moveType;
6833     int fromX, fromY, toX, toY, boardIndex;
6834     char promoChar;
6835     char *p, *q;
6836     char buf[MSG_SIZ];
6837
6838     if (appData.debugMode)
6839       fprintf(debugFP, "Parsing game history: %s\n", game);
6840
6841     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6842     gameInfo.site = StrSave(appData.icsHost);
6843     gameInfo.date = PGNDate();
6844     gameInfo.round = StrSave("-");
6845
6846     /* Parse out names of players */
6847     while (*game == ' ') game++;
6848     p = buf;
6849     while (*game != ' ') *p++ = *game++;
6850     *p = NULLCHAR;
6851     gameInfo.white = StrSave(buf);
6852     while (*game == ' ') game++;
6853     p = buf;
6854     while (*game != ' ' && *game != '\n') *p++ = *game++;
6855     *p = NULLCHAR;
6856     gameInfo.black = StrSave(buf);
6857
6858     /* Parse moves */
6859     boardIndex = blackPlaysFirst ? 1 : 0;
6860     yynewstr(game);
6861     for (;;) {
6862         yyboardindex = boardIndex;
6863         moveType = (ChessMove) yylex();
6864         switch (moveType) {
6865           case IllegalMove:             /* maybe suicide chess, etc. */
6866   if (appData.debugMode) {
6867     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6868     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6869     setbuf(debugFP, NULL);
6870   }
6871           case WhitePromotionChancellor:
6872           case BlackPromotionChancellor:
6873           case WhitePromotionArchbishop:
6874           case BlackPromotionArchbishop:
6875           case WhitePromotionQueen:
6876           case BlackPromotionQueen:
6877           case WhitePromotionRook:
6878           case BlackPromotionRook:
6879           case WhitePromotionBishop:
6880           case BlackPromotionBishop:
6881           case WhitePromotionKnight:
6882           case BlackPromotionKnight:
6883           case WhitePromotionKing:
6884           case BlackPromotionKing:
6885           case NormalMove:
6886           case WhiteCapturesEnPassant:
6887           case BlackCapturesEnPassant:
6888           case WhiteKingSideCastle:
6889           case WhiteQueenSideCastle:
6890           case BlackKingSideCastle:
6891           case BlackQueenSideCastle:
6892           case WhiteKingSideCastleWild:
6893           case WhiteQueenSideCastleWild:
6894           case BlackKingSideCastleWild:
6895           case BlackQueenSideCastleWild:
6896           /* PUSH Fabien */
6897           case WhiteHSideCastleFR:
6898           case WhiteASideCastleFR:
6899           case BlackHSideCastleFR:
6900           case BlackASideCastleFR:
6901           /* POP Fabien */
6902             fromX = currentMoveString[0] - AAA;
6903             fromY = currentMoveString[1] - ONE;
6904             toX = currentMoveString[2] - AAA;
6905             toY = currentMoveString[3] - ONE;
6906             promoChar = currentMoveString[4];
6907             break;
6908           case WhiteDrop:
6909           case BlackDrop:
6910             fromX = moveType == WhiteDrop ?
6911               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6912             (int) CharToPiece(ToLower(currentMoveString[0]));
6913             fromY = DROP_RANK;
6914             toX = currentMoveString[2] - AAA;
6915             toY = currentMoveString[3] - ONE;
6916             promoChar = NULLCHAR;
6917             break;
6918           case AmbiguousMove:
6919             /* bug? */
6920             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6921   if (appData.debugMode) {
6922     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6923     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6924     setbuf(debugFP, NULL);
6925   }
6926             DisplayError(buf, 0);
6927             return;
6928           case ImpossibleMove:
6929             /* bug? */
6930             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6931   if (appData.debugMode) {
6932     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6933     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6934     setbuf(debugFP, NULL);
6935   }
6936             DisplayError(buf, 0);
6937             return;
6938           case (ChessMove) 0:   /* end of file */
6939             if (boardIndex < backwardMostMove) {
6940                 /* Oops, gap.  How did that happen? */
6941                 DisplayError(_("Gap in move list"), 0);
6942                 return;
6943             }
6944             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6945             if (boardIndex > forwardMostMove) {
6946                 forwardMostMove = boardIndex;
6947             }
6948             return;
6949           case ElapsedTime:
6950             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6951                 strcat(parseList[boardIndex-1], " ");
6952                 strcat(parseList[boardIndex-1], yy_text);
6953             }
6954             continue;
6955           case Comment:
6956           case PGNTag:
6957           case NAG:
6958           default:
6959             /* ignore */
6960             continue;
6961           case WhiteWins:
6962           case BlackWins:
6963           case GameIsDrawn:
6964           case GameUnfinished:
6965             if (gameMode == IcsExamining) {
6966                 if (boardIndex < backwardMostMove) {
6967                     /* Oops, gap.  How did that happen? */
6968                     return;
6969                 }
6970                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6971                 return;
6972             }
6973             gameInfo.result = moveType;
6974             p = strchr(yy_text, '{');
6975             if (p == NULL) p = strchr(yy_text, '(');
6976             if (p == NULL) {
6977                 p = yy_text;
6978                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6979             } else {
6980                 q = strchr(p, *p == '{' ? '}' : ')');
6981                 if (q != NULL) *q = NULLCHAR;
6982                 p++;
6983             }
6984             gameInfo.resultDetails = StrSave(p);
6985             continue;
6986         }
6987         if (boardIndex >= forwardMostMove &&
6988             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6989             backwardMostMove = blackPlaysFirst ? 1 : 0;
6990             return;
6991         }
6992         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6993                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6994                                  parseList[boardIndex]);
6995         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6996         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6997         /* currentMoveString is set as a side-effect of yylex */
6998         strcpy(moveList[boardIndex], currentMoveString);
6999         strcat(moveList[boardIndex], "\n");
7000         boardIndex++;
7001         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7002                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7003         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7004                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7005           case MT_NONE:
7006           case MT_STALEMATE:
7007           default:
7008             break;
7009           case MT_CHECK:
7010             if(gameInfo.variant != VariantShogi)
7011                 strcat(parseList[boardIndex - 1], "+");
7012             break;
7013           case MT_CHECKMATE:
7014           case MT_STAINMATE:
7015             strcat(parseList[boardIndex - 1], "#");
7016             break;
7017         }
7018     }
7019 }
7020
7021
7022 /* Apply a move to the given board  */
7023 void
7024 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7025      int fromX, fromY, toX, toY;
7026      int promoChar;
7027      Board board;
7028      char *castling;
7029      char *ep;
7030 {
7031   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7032
7033     /* [HGM] compute & store e.p. status and castling rights for new position */
7034     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7035     { int i;
7036
7037       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7038       oldEP = *ep;
7039       *ep = EP_NONE;
7040
7041       if( board[toY][toX] != EmptySquare ) 
7042            *ep = EP_CAPTURE;  
7043
7044       if( board[fromY][fromX] == WhitePawn ) {
7045            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7046                *ep = EP_PAWN_MOVE;
7047            if( toY-fromY==2) {
7048                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7049                         gameInfo.variant != VariantBerolina || toX < fromX)
7050                       *ep = toX | berolina;
7051                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7052                         gameInfo.variant != VariantBerolina || toX > fromX) 
7053                       *ep = toX;
7054            }
7055       } else 
7056       if( board[fromY][fromX] == BlackPawn ) {
7057            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7058                *ep = EP_PAWN_MOVE; 
7059            if( toY-fromY== -2) {
7060                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7061                         gameInfo.variant != VariantBerolina || toX < fromX)
7062                       *ep = toX | berolina;
7063                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7064                         gameInfo.variant != VariantBerolina || toX > fromX) 
7065                       *ep = toX;
7066            }
7067        }
7068
7069        for(i=0; i<nrCastlingRights; i++) {
7070            if(castling[i] == fromX && castlingRank[i] == fromY ||
7071               castling[i] == toX   && castlingRank[i] == toY   
7072              ) castling[i] = -1; // revoke for moved or captured piece
7073        }
7074
7075     }
7076
7077   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7078   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7079        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7080          
7081   if (fromX == toX && fromY == toY) return;
7082
7083   if (fromY == DROP_RANK) {
7084         /* must be first */
7085         piece = board[toY][toX] = (ChessSquare) fromX;
7086   } else {
7087      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7088      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7089      if(gameInfo.variant == VariantKnightmate)
7090          king += (int) WhiteUnicorn - (int) WhiteKing;
7091
7092     /* Code added by Tord: */
7093     /* FRC castling assumed when king captures friendly rook. */
7094     if (board[fromY][fromX] == WhiteKing &&
7095              board[toY][toX] == WhiteRook) {
7096       board[fromY][fromX] = EmptySquare;
7097       board[toY][toX] = EmptySquare;
7098       if(toX > fromX) {
7099         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7100       } else {
7101         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7102       }
7103     } else if (board[fromY][fromX] == BlackKing &&
7104                board[toY][toX] == BlackRook) {
7105       board[fromY][fromX] = EmptySquare;
7106       board[toY][toX] = EmptySquare;
7107       if(toX > fromX) {
7108         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7109       } else {
7110         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7111       }
7112     /* End of code added by Tord */
7113
7114     } else if (board[fromY][fromX] == king
7115         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7116         && toY == fromY && toX > fromX+1) {
7117         board[fromY][fromX] = EmptySquare;
7118         board[toY][toX] = king;
7119         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7120         board[fromY][BOARD_RGHT-1] = EmptySquare;
7121     } else if (board[fromY][fromX] == king
7122         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7123                && toY == fromY && toX < fromX-1) {
7124         board[fromY][fromX] = EmptySquare;
7125         board[toY][toX] = king;
7126         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7127         board[fromY][BOARD_LEFT] = EmptySquare;
7128     } else if (board[fromY][fromX] == WhitePawn
7129                && toY == BOARD_HEIGHT-1
7130                && gameInfo.variant != VariantXiangqi
7131                ) {
7132         /* white pawn promotion */
7133         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7134         if (board[toY][toX] == EmptySquare) {
7135             board[toY][toX] = WhiteQueen;
7136         }
7137         if(gameInfo.variant==VariantBughouse ||
7138            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7139             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7140         board[fromY][fromX] = EmptySquare;
7141     } else if ((fromY == BOARD_HEIGHT-4)
7142                && (toX != fromX)
7143                && gameInfo.variant != VariantXiangqi
7144                && gameInfo.variant != VariantBerolina
7145                && (board[fromY][fromX] == WhitePawn)
7146                && (board[toY][toX] == EmptySquare)) {
7147         board[fromY][fromX] = EmptySquare;
7148         board[toY][toX] = WhitePawn;
7149         captured = board[toY - 1][toX];
7150         board[toY - 1][toX] = EmptySquare;
7151     } else if ((fromY == BOARD_HEIGHT-4)
7152                && (toX == fromX)
7153                && gameInfo.variant == VariantBerolina
7154                && (board[fromY][fromX] == WhitePawn)
7155                && (board[toY][toX] == EmptySquare)) {
7156         board[fromY][fromX] = EmptySquare;
7157         board[toY][toX] = WhitePawn;
7158         if(oldEP & EP_BEROLIN_A) {
7159                 captured = board[fromY][fromX-1];
7160                 board[fromY][fromX-1] = EmptySquare;
7161         }else{  captured = board[fromY][fromX+1];
7162                 board[fromY][fromX+1] = EmptySquare;
7163         }
7164     } else if (board[fromY][fromX] == king
7165         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7166                && toY == fromY && toX > fromX+1) {
7167         board[fromY][fromX] = EmptySquare;
7168         board[toY][toX] = king;
7169         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7170         board[fromY][BOARD_RGHT-1] = EmptySquare;
7171     } else if (board[fromY][fromX] == king
7172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7173                && toY == fromY && toX < fromX-1) {
7174         board[fromY][fromX] = EmptySquare;
7175         board[toY][toX] = king;
7176         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7177         board[fromY][BOARD_LEFT] = EmptySquare;
7178     } else if (fromY == 7 && fromX == 3
7179                && board[fromY][fromX] == BlackKing
7180                && toY == 7 && toX == 5) {
7181         board[fromY][fromX] = EmptySquare;
7182         board[toY][toX] = BlackKing;
7183         board[fromY][7] = EmptySquare;
7184         board[toY][4] = BlackRook;
7185     } else if (fromY == 7 && fromX == 3
7186                && board[fromY][fromX] == BlackKing
7187                && toY == 7 && toX == 1) {
7188         board[fromY][fromX] = EmptySquare;
7189         board[toY][toX] = BlackKing;
7190         board[fromY][0] = EmptySquare;
7191         board[toY][2] = BlackRook;
7192     } else if (board[fromY][fromX] == BlackPawn
7193                && toY == 0
7194                && gameInfo.variant != VariantXiangqi
7195                ) {
7196         /* black pawn promotion */
7197         board[0][toX] = CharToPiece(ToLower(promoChar));
7198         if (board[0][toX] == EmptySquare) {
7199             board[0][toX] = BlackQueen;
7200         }
7201         if(gameInfo.variant==VariantBughouse ||
7202            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7203             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7204         board[fromY][fromX] = EmptySquare;
7205     } else if ((fromY == 3)
7206                && (toX != fromX)
7207                && gameInfo.variant != VariantXiangqi
7208                && gameInfo.variant != VariantBerolina
7209                && (board[fromY][fromX] == BlackPawn)
7210                && (board[toY][toX] == EmptySquare)) {
7211         board[fromY][fromX] = EmptySquare;
7212         board[toY][toX] = BlackPawn;
7213         captured = board[toY + 1][toX];
7214         board[toY + 1][toX] = EmptySquare;
7215     } else if ((fromY == 3)
7216                && (toX == fromX)
7217                && gameInfo.variant == VariantBerolina
7218                && (board[fromY][fromX] == BlackPawn)
7219                && (board[toY][toX] == EmptySquare)) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = BlackPawn;
7222         if(oldEP & EP_BEROLIN_A) {
7223                 captured = board[fromY][fromX-1];
7224                 board[fromY][fromX-1] = EmptySquare;
7225         }else{  captured = board[fromY][fromX+1];
7226                 board[fromY][fromX+1] = EmptySquare;
7227         }
7228     } else {
7229         board[toY][toX] = board[fromY][fromX];
7230         board[fromY][fromX] = EmptySquare;
7231     }
7232
7233     /* [HGM] now we promote for Shogi, if needed */
7234     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7235         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7236   }
7237
7238     if (gameInfo.holdingsWidth != 0) {
7239
7240       /* !!A lot more code needs to be written to support holdings  */
7241       /* [HGM] OK, so I have written it. Holdings are stored in the */
7242       /* penultimate board files, so they are automaticlly stored   */
7243       /* in the game history.                                       */
7244       if (fromY == DROP_RANK) {
7245         /* Delete from holdings, by decreasing count */
7246         /* and erasing image if necessary            */
7247         p = (int) fromX;
7248         if(p < (int) BlackPawn) { /* white drop */
7249              p -= (int)WhitePawn;
7250              if(p >= gameInfo.holdingsSize) p = 0;
7251              if(--board[p][BOARD_WIDTH-2] == 0)
7252                   board[p][BOARD_WIDTH-1] = EmptySquare;
7253         } else {                  /* black drop */
7254              p -= (int)BlackPawn;
7255              if(p >= gameInfo.holdingsSize) p = 0;
7256              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7257                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7258         }
7259       }
7260       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7261           && gameInfo.variant != VariantBughouse        ) {
7262         /* [HGM] holdings: Add to holdings, if holdings exist */
7263         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7264                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7265                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7266         }
7267         p = (int) captured;
7268         if (p >= (int) BlackPawn) {
7269           p -= (int)BlackPawn;
7270           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7271                   /* in Shogi restore piece to its original  first */
7272                   captured = (ChessSquare) (DEMOTED captured);
7273                   p = DEMOTED p;
7274           }
7275           p = PieceToNumber((ChessSquare)p);
7276           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7277           board[p][BOARD_WIDTH-2]++;
7278           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7279         } else {
7280           p -= (int)WhitePawn;
7281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7282                   captured = (ChessSquare) (DEMOTED captured);
7283                   p = DEMOTED p;
7284           }
7285           p = PieceToNumber((ChessSquare)p);
7286           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7287           board[BOARD_HEIGHT-1-p][1]++;
7288           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7289         }
7290       }
7291
7292     } else if (gameInfo.variant == VariantAtomic) {
7293       if (captured != EmptySquare) {
7294         int y, x;
7295         for (y = toY-1; y <= toY+1; y++) {
7296           for (x = toX-1; x <= toX+1; x++) {
7297             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7298                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7299               board[y][x] = EmptySquare;
7300             }
7301           }
7302         }
7303         board[toY][toX] = EmptySquare;
7304       }
7305     }
7306     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7307         /* [HGM] Shogi promotions */
7308         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7309     }
7310
7311     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7312                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7313         // [HGM] superchess: take promotion piece out of holdings
7314         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7315         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7316             if(!--board[k][BOARD_WIDTH-2])
7317                 board[k][BOARD_WIDTH-1] = EmptySquare;
7318         } else {
7319             if(!--board[BOARD_HEIGHT-1-k][1])
7320                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7321         }
7322     }
7323
7324 }
7325
7326 /* Updates forwardMostMove */
7327 void
7328 MakeMove(fromX, fromY, toX, toY, promoChar)
7329      int fromX, fromY, toX, toY;
7330      int promoChar;
7331 {
7332 //    forwardMostMove++; // [HGM] bare: moved downstream
7333
7334     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7335         int timeLeft; static int lastLoadFlag=0; int king, piece;
7336         piece = boards[forwardMostMove][fromY][fromX];
7337         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7338         if(gameInfo.variant == VariantKnightmate)
7339             king += (int) WhiteUnicorn - (int) WhiteKing;
7340         if(forwardMostMove == 0) {
7341             if(blackPlaysFirst) 
7342                 fprintf(serverMoves, "%s;", second.tidy);
7343             fprintf(serverMoves, "%s;", first.tidy);
7344             if(!blackPlaysFirst) 
7345                 fprintf(serverMoves, "%s;", second.tidy);
7346         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7347         lastLoadFlag = loadFlag;
7348         // print base move
7349         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7350         // print castling suffix
7351         if( toY == fromY && piece == king ) {
7352             if(toX-fromX > 1)
7353                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7354             if(fromX-toX >1)
7355                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7356         }
7357         // e.p. suffix
7358         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7359              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7360              boards[forwardMostMove][toY][toX] == EmptySquare
7361              && fromX != toX )
7362                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7363         // promotion suffix
7364         if(promoChar != NULLCHAR)
7365                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7366         if(!loadFlag) {
7367             fprintf(serverMoves, "/%d/%d",
7368                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7369             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7370             else                      timeLeft = blackTimeRemaining/1000;
7371             fprintf(serverMoves, "/%d", timeLeft);
7372         }
7373         fflush(serverMoves);
7374     }
7375
7376     if (forwardMostMove+1 >= MAX_MOVES) {
7377       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7378                         0, 1);
7379       return;
7380     }
7381     SwitchClocks();
7382     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7383     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7384     if (commentList[forwardMostMove+1] != NULL) {
7385         free(commentList[forwardMostMove+1]);
7386         commentList[forwardMostMove+1] = NULL;
7387     }
7388     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7389     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7390     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7391                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7392     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7393     gameInfo.result = GameUnfinished;
7394     if (gameInfo.resultDetails != NULL) {
7395         free(gameInfo.resultDetails);
7396         gameInfo.resultDetails = NULL;
7397     }
7398     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7399                               moveList[forwardMostMove - 1]);
7400     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7401                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7402                              fromY, fromX, toY, toX, promoChar,
7403                              parseList[forwardMostMove - 1]);
7404     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7405                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7406                             castlingRights[forwardMostMove]) ) {
7407       case MT_NONE:
7408       case MT_STALEMATE:
7409       default:
7410         break;
7411       case MT_CHECK:
7412         if(gameInfo.variant != VariantShogi)
7413             strcat(parseList[forwardMostMove - 1], "+");
7414         break;
7415       case MT_CHECKMATE:
7416       case MT_STAINMATE:
7417         strcat(parseList[forwardMostMove - 1], "#");
7418         break;
7419     }
7420     if (appData.debugMode) {
7421         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7422     }
7423
7424 }
7425
7426 /* Updates currentMove if not pausing */
7427 void
7428 ShowMove(fromX, fromY, toX, toY)
7429 {
7430     int instant = (gameMode == PlayFromGameFile) ?
7431         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7432     if(appData.noGUI) return;
7433     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7434         if (!instant) {
7435             if (forwardMostMove == currentMove + 1) {
7436                 AnimateMove(boards[forwardMostMove - 1],
7437                             fromX, fromY, toX, toY);
7438             }
7439             if (appData.highlightLastMove) {
7440                 SetHighlights(fromX, fromY, toX, toY);
7441             }
7442         }
7443         currentMove = forwardMostMove;
7444     }
7445
7446     if (instant) return;
7447
7448     DisplayMove(currentMove - 1);
7449     DrawPosition(FALSE, boards[currentMove]);
7450     DisplayBothClocks();
7451     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7452 }
7453
7454 void SendEgtPath(ChessProgramState *cps)
7455 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7456         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7457
7458         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7459
7460         while(*p) {
7461             char c, *q = name+1, *r, *s;
7462
7463             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7464             while(*p && *p != ',') *q++ = *p++;
7465             *q++ = ':'; *q = 0;
7466             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7467                 strcmp(name, ",nalimov:") == 0 ) {
7468                 // take nalimov path from the menu-changeable option first, if it is defined
7469                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7470                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7471             } else
7472             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7473                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7474                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7475                 s = r = StrStr(s, ":") + 1; // beginning of path info
7476                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7477                 c = *r; *r = 0;             // temporarily null-terminate path info
7478                     *--q = 0;               // strip of trailig ':' from name
7479                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7480                 *r = c;
7481                 SendToProgram(buf,cps);     // send egtbpath command for this format
7482             }
7483             if(*p == ',') p++; // read away comma to position for next format name
7484         }
7485 }
7486
7487 void
7488 InitChessProgram(cps, setup)
7489      ChessProgramState *cps;
7490      int setup; /* [HGM] needed to setup FRC opening position */
7491 {
7492     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7493     if (appData.noChessProgram) return;
7494     hintRequested = FALSE;
7495     bookRequested = FALSE;
7496
7497     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7498     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7499     if(cps->memSize) { /* [HGM] memory */
7500         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7501         SendToProgram(buf, cps);
7502     }
7503     SendEgtPath(cps); /* [HGM] EGT */
7504     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7505         sprintf(buf, "cores %d\n", appData.smpCores);
7506         SendToProgram(buf, cps);
7507     }
7508
7509     SendToProgram(cps->initString, cps);
7510     if (gameInfo.variant != VariantNormal &&
7511         gameInfo.variant != VariantLoadable
7512         /* [HGM] also send variant if board size non-standard */
7513         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7514                                             ) {
7515       char *v = VariantName(gameInfo.variant);
7516       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7517         /* [HGM] in protocol 1 we have to assume all variants valid */
7518         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7519         DisplayFatalError(buf, 0, 1);
7520         return;
7521       }
7522
7523       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7524       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7525       if( gameInfo.variant == VariantXiangqi )
7526            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7527       if( gameInfo.variant == VariantShogi )
7528            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7529       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7530            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7531       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7532                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7533            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7534       if( gameInfo.variant == VariantCourier )
7535            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536       if( gameInfo.variant == VariantSuper )
7537            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7538       if( gameInfo.variant == VariantGreat )
7539            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7540
7541       if(overruled) {
7542            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7543                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7544            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7545            if(StrStr(cps->variants, b) == NULL) { 
7546                // specific sized variant not known, check if general sizing allowed
7547                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7548                    if(StrStr(cps->variants, "boardsize") == NULL) {
7549                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7550                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7551                        DisplayFatalError(buf, 0, 1);
7552                        return;
7553                    }
7554                    /* [HGM] here we really should compare with the maximum supported board size */
7555                }
7556            }
7557       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7558       sprintf(buf, "variant %s\n", b);
7559       SendToProgram(buf, cps);
7560     }
7561     currentlyInitializedVariant = gameInfo.variant;
7562
7563     /* [HGM] send opening position in FRC to first engine */
7564     if(setup) {
7565           SendToProgram("force\n", cps);
7566           SendBoard(cps, 0);
7567           /* engine is now in force mode! Set flag to wake it up after first move. */
7568           setboardSpoiledMachineBlack = 1;
7569     }
7570
7571     if (cps->sendICS) {
7572       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7573       SendToProgram(buf, cps);
7574     }
7575     cps->maybeThinking = FALSE;
7576     cps->offeredDraw = 0;
7577     if (!appData.icsActive) {
7578         SendTimeControl(cps, movesPerSession, timeControl,
7579                         timeIncrement, appData.searchDepth,
7580                         searchTime);
7581     }
7582     if (appData.showThinking 
7583         // [HGM] thinking: four options require thinking output to be sent
7584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7585                                 ) {
7586         SendToProgram("post\n", cps);
7587     }
7588     SendToProgram("hard\n", cps);
7589     if (!appData.ponderNextMove) {
7590         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7591            it without being sure what state we are in first.  "hard"
7592            is not a toggle, so that one is OK.
7593          */
7594         SendToProgram("easy\n", cps);
7595     }
7596     if (cps->usePing) {
7597       sprintf(buf, "ping %d\n", ++cps->lastPing);
7598       SendToProgram(buf, cps);
7599     }
7600     cps->initDone = TRUE;
7601 }   
7602
7603
7604 void
7605 StartChessProgram(cps)
7606      ChessProgramState *cps;
7607 {
7608     char buf[MSG_SIZ];
7609     int err;
7610
7611     if (appData.noChessProgram) return;
7612     cps->initDone = FALSE;
7613
7614     if (strcmp(cps->host, "localhost") == 0) {
7615         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7616     } else if (*appData.remoteShell == NULLCHAR) {
7617         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7618     } else {
7619         if (*appData.remoteUser == NULLCHAR) {
7620           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7621                     cps->program);
7622         } else {
7623           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7624                     cps->host, appData.remoteUser, cps->program);
7625         }
7626         err = StartChildProcess(buf, "", &cps->pr);
7627     }
7628     
7629     if (err != 0) {
7630         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7631         DisplayFatalError(buf, err, 1);
7632         cps->pr = NoProc;
7633         cps->isr = NULL;
7634         return;
7635     }
7636     
7637     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7638     if (cps->protocolVersion > 1) {
7639       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7640       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7641       cps->comboCnt = 0;  //                and values of combo boxes
7642       SendToProgram(buf, cps);
7643     } else {
7644       SendToProgram("xboard\n", cps);
7645     }
7646 }
7647
7648
7649 void
7650 TwoMachinesEventIfReady P((void))
7651 {
7652   if (first.lastPing != first.lastPong) {
7653     DisplayMessage("", _("Waiting for first chess program"));
7654     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7655     return;
7656   }
7657   if (second.lastPing != second.lastPong) {
7658     DisplayMessage("", _("Waiting for second chess program"));
7659     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7660     return;
7661   }
7662   ThawUI();
7663   TwoMachinesEvent();
7664 }
7665
7666 void
7667 NextMatchGame P((void))
7668 {
7669     int index; /* [HGM] autoinc: step lod index during match */
7670     Reset(FALSE, TRUE);
7671     if (*appData.loadGameFile != NULLCHAR) {
7672         index = appData.loadGameIndex;
7673         if(index < 0) { // [HGM] autoinc
7674             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7675             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7676         } 
7677         LoadGameFromFile(appData.loadGameFile,
7678                          index,
7679                          appData.loadGameFile, FALSE);
7680     } else if (*appData.loadPositionFile != NULLCHAR) {
7681         index = appData.loadPositionIndex;
7682         if(index < 0) { // [HGM] autoinc
7683             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7684             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7685         } 
7686         LoadPositionFromFile(appData.loadPositionFile,
7687                              index,
7688                              appData.loadPositionFile);
7689     }
7690     TwoMachinesEventIfReady();
7691 }
7692
7693 void UserAdjudicationEvent( int result )
7694 {
7695     ChessMove gameResult = GameIsDrawn;
7696
7697     if( result > 0 ) {
7698         gameResult = WhiteWins;
7699     }
7700     else if( result < 0 ) {
7701         gameResult = BlackWins;
7702     }
7703
7704     if( gameMode == TwoMachinesPlay ) {
7705         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7706     }
7707 }
7708
7709
7710 void
7711 GameEnds(result, resultDetails, whosays)
7712      ChessMove result;
7713      char *resultDetails;
7714      int whosays;
7715 {
7716     GameMode nextGameMode;
7717     int isIcsGame;
7718     char buf[MSG_SIZ];
7719
7720     if(endingGame) return; /* [HGM] crash: forbid recursion */
7721     endingGame = 1;
7722
7723     if (appData.debugMode) {
7724       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7725               result, resultDetails ? resultDetails : "(null)", whosays);
7726     }
7727
7728     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7729         /* If we are playing on ICS, the server decides when the
7730            game is over, but the engine can offer to draw, claim 
7731            a draw, or resign. 
7732          */
7733 #if ZIPPY
7734         if (appData.zippyPlay && first.initDone) {
7735             if (result == GameIsDrawn) {
7736                 /* In case draw still needs to be claimed */
7737                 SendToICS(ics_prefix);
7738                 SendToICS("draw\n");
7739             } else if (StrCaseStr(resultDetails, "resign")) {
7740                 SendToICS(ics_prefix);
7741                 SendToICS("resign\n");
7742             }
7743         }
7744 #endif
7745         endingGame = 0; /* [HGM] crash */
7746         return;
7747     }
7748
7749     /* If we're loading the game from a file, stop */
7750     if (whosays == GE_FILE) {
7751       (void) StopLoadGameTimer();
7752       gameFileFP = NULL;
7753     }
7754
7755     /* Cancel draw offers */
7756     first.offeredDraw = second.offeredDraw = 0;
7757
7758     /* If this is an ICS game, only ICS can really say it's done;
7759        if not, anyone can. */
7760     isIcsGame = (gameMode == IcsPlayingWhite || 
7761                  gameMode == IcsPlayingBlack || 
7762                  gameMode == IcsObserving    || 
7763                  gameMode == IcsExamining);
7764
7765     if (!isIcsGame || whosays == GE_ICS) {
7766         /* OK -- not an ICS game, or ICS said it was done */
7767         StopClocks();
7768         if (!isIcsGame && !appData.noChessProgram) 
7769           SetUserThinkingEnables();
7770     
7771         /* [HGM] if a machine claims the game end we verify this claim */
7772         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7773             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7774                 char claimer;
7775                 ChessMove trueResult = (ChessMove) -1;
7776
7777                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7778                                             first.twoMachinesColor[0] :
7779                                             second.twoMachinesColor[0] ;
7780
7781                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7782                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7783                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7784                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7785                 } else
7786                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7787                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7788                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7789                 } else
7790                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7791                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7792                 }
7793
7794                 // now verify win claims, but not in drop games, as we don't understand those yet
7795                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7796                                                  || gameInfo.variant == VariantGreat) &&
7797                     (result == WhiteWins && claimer == 'w' ||
7798                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7799                       if (appData.debugMode) {
7800                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7801                                 result, epStatus[forwardMostMove], forwardMostMove);
7802                       }
7803                       if(result != trueResult) {
7804                               sprintf(buf, "False win claim: '%s'", resultDetails);
7805                               result = claimer == 'w' ? BlackWins : WhiteWins;
7806                               resultDetails = buf;
7807                       }
7808                 } else
7809                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7810                     && (forwardMostMove <= backwardMostMove ||
7811                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7812                         (claimer=='b')==(forwardMostMove&1))
7813                                                                                   ) {
7814                       /* [HGM] verify: draws that were not flagged are false claims */
7815                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7816                       result = claimer == 'w' ? BlackWins : WhiteWins;
7817                       resultDetails = buf;
7818                 }
7819                 /* (Claiming a loss is accepted no questions asked!) */
7820             }
7821             /* [HGM] bare: don't allow bare King to win */
7822             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7823                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7824                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7825                && result != GameIsDrawn)
7826             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7827                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7828                         int p = (int)boards[forwardMostMove][i][j] - color;
7829                         if(p >= 0 && p <= (int)WhiteKing) k++;
7830                 }
7831                 if (appData.debugMode) {
7832                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7833                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7834                 }
7835                 if(k <= 1) {
7836                         result = GameIsDrawn;
7837                         sprintf(buf, "%s but bare king", resultDetails);
7838                         resultDetails = buf;
7839                 }
7840             }
7841         }
7842
7843
7844         if(serverMoves != NULL && !loadFlag) { char c = '=';
7845             if(result==WhiteWins) c = '+';
7846             if(result==BlackWins) c = '-';
7847             if(resultDetails != NULL)
7848                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7849         }
7850         if (resultDetails != NULL) {
7851             gameInfo.result = result;
7852             gameInfo.resultDetails = StrSave(resultDetails);
7853
7854             /* display last move only if game was not loaded from file */
7855             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7856                 DisplayMove(currentMove - 1);
7857     
7858             if (forwardMostMove != 0) {
7859                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7860                     if (*appData.saveGameFile != NULLCHAR) {
7861                         SaveGameToFile(appData.saveGameFile, TRUE);
7862                     } else if (appData.autoSaveGames) {
7863                         AutoSaveGame();
7864                     }
7865                     if (*appData.savePositionFile != NULLCHAR) {
7866                         SavePositionToFile(appData.savePositionFile);
7867                     }
7868                 }
7869             }
7870
7871             /* Tell program how game ended in case it is learning */
7872             /* [HGM] Moved this to after saving the PGN, just in case */
7873             /* engine died and we got here through time loss. In that */
7874             /* case we will get a fatal error writing the pipe, which */
7875             /* would otherwise lose us the PGN.                       */
7876             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7877             /* output during GameEnds should never be fatal anymore   */
7878             if (gameMode == MachinePlaysWhite ||
7879                 gameMode == MachinePlaysBlack ||
7880                 gameMode == TwoMachinesPlay ||
7881                 gameMode == IcsPlayingWhite ||
7882                 gameMode == IcsPlayingBlack ||
7883                 gameMode == BeginningOfGame) {
7884                 char buf[MSG_SIZ];
7885                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7886                         resultDetails);
7887                 if (first.pr != NoProc) {
7888                     SendToProgram(buf, &first);
7889                 }
7890                 if (second.pr != NoProc &&
7891                     gameMode == TwoMachinesPlay) {
7892                     SendToProgram(buf, &second);
7893                 }
7894             }
7895         }
7896
7897         if (appData.icsActive) {
7898             if (appData.quietPlay &&
7899                 (gameMode == IcsPlayingWhite ||
7900                  gameMode == IcsPlayingBlack)) {
7901                 SendToICS(ics_prefix);
7902                 SendToICS("set shout 1\n");
7903             }
7904             nextGameMode = IcsIdle;
7905             ics_user_moved = FALSE;
7906             /* clean up premove.  It's ugly when the game has ended and the
7907              * premove highlights are still on the board.
7908              */
7909             if (gotPremove) {
7910               gotPremove = FALSE;
7911               ClearPremoveHighlights();
7912               DrawPosition(FALSE, boards[currentMove]);
7913             }
7914             if (whosays == GE_ICS) {
7915                 switch (result) {
7916                 case WhiteWins:
7917                     if (gameMode == IcsPlayingWhite)
7918                         PlayIcsWinSound();
7919                     else if(gameMode == IcsPlayingBlack)
7920                         PlayIcsLossSound();
7921                     break;
7922                 case BlackWins:
7923                     if (gameMode == IcsPlayingBlack)
7924                         PlayIcsWinSound();
7925                     else if(gameMode == IcsPlayingWhite)
7926                         PlayIcsLossSound();
7927                     break;
7928                 case GameIsDrawn:
7929                     PlayIcsDrawSound();
7930                     break;
7931                 default:
7932                     PlayIcsUnfinishedSound();
7933                 }
7934             }
7935         } else if (gameMode == EditGame ||
7936                    gameMode == PlayFromGameFile || 
7937                    gameMode == AnalyzeMode || 
7938                    gameMode == AnalyzeFile) {
7939             nextGameMode = gameMode;
7940         } else {
7941             nextGameMode = EndOfGame;
7942         }
7943         pausing = FALSE;
7944         ModeHighlight();
7945     } else {
7946         nextGameMode = gameMode;
7947     }
7948
7949     if (appData.noChessProgram) {
7950         gameMode = nextGameMode;
7951         ModeHighlight();
7952         endingGame = 0; /* [HGM] crash */
7953         return;
7954     }
7955
7956     if (first.reuse) {
7957         /* Put first chess program into idle state */
7958         if (first.pr != NoProc &&
7959             (gameMode == MachinePlaysWhite ||
7960              gameMode == MachinePlaysBlack ||
7961              gameMode == TwoMachinesPlay ||
7962              gameMode == IcsPlayingWhite ||
7963              gameMode == IcsPlayingBlack ||
7964              gameMode == BeginningOfGame)) {
7965             SendToProgram("force\n", &first);
7966             if (first.usePing) {
7967               char buf[MSG_SIZ];
7968               sprintf(buf, "ping %d\n", ++first.lastPing);
7969               SendToProgram(buf, &first);
7970             }
7971         }
7972     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7973         /* Kill off first chess program */
7974         if (first.isr != NULL)
7975           RemoveInputSource(first.isr);
7976         first.isr = NULL;
7977     
7978         if (first.pr != NoProc) {
7979             ExitAnalyzeMode();
7980             DoSleep( appData.delayBeforeQuit );
7981             SendToProgram("quit\n", &first);
7982             DoSleep( appData.delayAfterQuit );
7983             DestroyChildProcess(first.pr, first.useSigterm);
7984         }
7985         first.pr = NoProc;
7986     }
7987     if (second.reuse) {
7988         /* Put second chess program into idle state */
7989         if (second.pr != NoProc &&
7990             gameMode == TwoMachinesPlay) {
7991             SendToProgram("force\n", &second);
7992             if (second.usePing) {
7993               char buf[MSG_SIZ];
7994               sprintf(buf, "ping %d\n", ++second.lastPing);
7995               SendToProgram(buf, &second);
7996             }
7997         }
7998     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7999         /* Kill off second chess program */
8000         if (second.isr != NULL)
8001           RemoveInputSource(second.isr);
8002         second.isr = NULL;
8003     
8004         if (second.pr != NoProc) {
8005             DoSleep( appData.delayBeforeQuit );
8006             SendToProgram("quit\n", &second);
8007             DoSleep( appData.delayAfterQuit );
8008             DestroyChildProcess(second.pr, second.useSigterm);
8009         }
8010         second.pr = NoProc;
8011     }
8012
8013     if (matchMode && gameMode == TwoMachinesPlay) {
8014         switch (result) {
8015         case WhiteWins:
8016           if (first.twoMachinesColor[0] == 'w') {
8017             first.matchWins++;
8018           } else {
8019             second.matchWins++;
8020           }
8021           break;
8022         case BlackWins:
8023           if (first.twoMachinesColor[0] == 'b') {
8024             first.matchWins++;
8025           } else {
8026             second.matchWins++;
8027           }
8028           break;
8029         default:
8030           break;
8031         }
8032         if (matchGame < appData.matchGames) {
8033             char *tmp;
8034             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8035                 tmp = first.twoMachinesColor;
8036                 first.twoMachinesColor = second.twoMachinesColor;
8037                 second.twoMachinesColor = tmp;
8038             }
8039             gameMode = nextGameMode;
8040             matchGame++;
8041             if(appData.matchPause>10000 || appData.matchPause<10)
8042                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8043             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8044             endingGame = 0; /* [HGM] crash */
8045             return;
8046         } else {
8047             char buf[MSG_SIZ];
8048             gameMode = nextGameMode;
8049             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8050                     first.tidy, second.tidy,
8051                     first.matchWins, second.matchWins,
8052                     appData.matchGames - (first.matchWins + second.matchWins));
8053             DisplayFatalError(buf, 0, 0);
8054         }
8055     }
8056     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8057         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8058       ExitAnalyzeMode();
8059     gameMode = nextGameMode;
8060     ModeHighlight();
8061     endingGame = 0;  /* [HGM] crash */
8062 }
8063
8064 /* Assumes program was just initialized (initString sent).
8065    Leaves program in force mode. */
8066 void
8067 FeedMovesToProgram(cps, upto) 
8068      ChessProgramState *cps;
8069      int upto;
8070 {
8071     int i;
8072     
8073     if (appData.debugMode)
8074       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8075               startedFromSetupPosition ? "position and " : "",
8076               backwardMostMove, upto, cps->which);
8077     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8078         // [HGM] variantswitch: make engine aware of new variant
8079         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8080                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8081         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8082         SendToProgram(buf, cps);
8083         currentlyInitializedVariant = gameInfo.variant;
8084     }
8085     SendToProgram("force\n", cps);
8086     if (startedFromSetupPosition) {
8087         SendBoard(cps, backwardMostMove);
8088     if (appData.debugMode) {
8089         fprintf(debugFP, "feedMoves\n");
8090     }
8091     }
8092     for (i = backwardMostMove; i < upto; i++) {
8093         SendMoveToProgram(i, cps);
8094     }
8095 }
8096
8097
8098 void
8099 ResurrectChessProgram()
8100 {
8101      /* The chess program may have exited.
8102         If so, restart it and feed it all the moves made so far. */
8103
8104     if (appData.noChessProgram || first.pr != NoProc) return;
8105     
8106     StartChessProgram(&first);
8107     InitChessProgram(&first, FALSE);
8108     FeedMovesToProgram(&first, currentMove);
8109
8110     if (!first.sendTime) {
8111         /* can't tell gnuchess what its clock should read,
8112            so we bow to its notion. */
8113         ResetClocks();
8114         timeRemaining[0][currentMove] = whiteTimeRemaining;
8115         timeRemaining[1][currentMove] = blackTimeRemaining;
8116     }
8117
8118     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8119                 appData.icsEngineAnalyze) && first.analysisSupport) {
8120       SendToProgram("analyze\n", &first);
8121       first.analyzing = TRUE;
8122     }
8123 }
8124
8125 /*
8126  * Button procedures
8127  */
8128 void
8129 Reset(redraw, init)
8130      int redraw, init;
8131 {
8132     int i;
8133
8134     if (appData.debugMode) {
8135         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8136                 redraw, init, gameMode);
8137     }
8138     pausing = pauseExamInvalid = FALSE;
8139     startedFromSetupPosition = blackPlaysFirst = FALSE;
8140     firstMove = TRUE;
8141     whiteFlag = blackFlag = FALSE;
8142     userOfferedDraw = FALSE;
8143     hintRequested = bookRequested = FALSE;
8144     first.maybeThinking = FALSE;
8145     second.maybeThinking = FALSE;
8146     first.bookSuspend = FALSE; // [HGM] book
8147     second.bookSuspend = FALSE;
8148     thinkOutput[0] = NULLCHAR;
8149     lastHint[0] = NULLCHAR;
8150     ClearGameInfo(&gameInfo);
8151     gameInfo.variant = StringToVariant(appData.variant);
8152     ics_user_moved = ics_clock_paused = FALSE;
8153     ics_getting_history = H_FALSE;
8154     ics_gamenum = -1;
8155     white_holding[0] = black_holding[0] = NULLCHAR;
8156     ClearProgramStats();
8157     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8158     
8159     ResetFrontEnd();
8160     ClearHighlights();
8161     flipView = appData.flipView;
8162     ClearPremoveHighlights();
8163     gotPremove = FALSE;
8164     alarmSounded = FALSE;
8165
8166     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8167     if(appData.serverMovesName != NULL) {
8168         /* [HGM] prepare to make moves file for broadcasting */
8169         clock_t t = clock();
8170         if(serverMoves != NULL) fclose(serverMoves);
8171         serverMoves = fopen(appData.serverMovesName, "r");
8172         if(serverMoves != NULL) {
8173             fclose(serverMoves);
8174             /* delay 15 sec before overwriting, so all clients can see end */
8175             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8176         }
8177         serverMoves = fopen(appData.serverMovesName, "w");
8178     }
8179
8180     ExitAnalyzeMode();
8181     gameMode = BeginningOfGame;
8182     ModeHighlight();
8183     if(appData.icsActive) gameInfo.variant = VariantNormal;
8184     InitPosition(redraw);
8185     for (i = 0; i < MAX_MOVES; i++) {
8186         if (commentList[i] != NULL) {
8187             free(commentList[i]);
8188             commentList[i] = NULL;
8189         }
8190     }
8191     ResetClocks();
8192     timeRemaining[0][0] = whiteTimeRemaining;
8193     timeRemaining[1][0] = blackTimeRemaining;
8194     if (first.pr == NULL) {
8195         StartChessProgram(&first);
8196     }
8197     if (init) {
8198             InitChessProgram(&first, startedFromSetupPosition);
8199     }
8200     DisplayTitle("");
8201     DisplayMessage("", "");
8202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8203 }
8204
8205 void
8206 AutoPlayGameLoop()
8207 {
8208     for (;;) {
8209         if (!AutoPlayOneMove())
8210           return;
8211         if (matchMode || appData.timeDelay == 0)
8212           continue;
8213         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8214           return;
8215         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8216         break;
8217     }
8218 }
8219
8220
8221 int
8222 AutoPlayOneMove()
8223 {
8224     int fromX, fromY, toX, toY;
8225
8226     if (appData.debugMode) {
8227       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8228     }
8229
8230     if (gameMode != PlayFromGameFile)
8231       return FALSE;
8232
8233     if (currentMove >= forwardMostMove) {
8234       gameMode = EditGame;
8235       ModeHighlight();
8236
8237       /* [AS] Clear current move marker at the end of a game */
8238       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8239
8240       return FALSE;
8241     }
8242     
8243     toX = moveList[currentMove][2] - AAA;
8244     toY = moveList[currentMove][3] - ONE;
8245
8246     if (moveList[currentMove][1] == '@') {
8247         if (appData.highlightLastMove) {
8248             SetHighlights(-1, -1, toX, toY);
8249         }
8250     } else {
8251         fromX = moveList[currentMove][0] - AAA;
8252         fromY = moveList[currentMove][1] - ONE;
8253
8254         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8255
8256         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8257
8258         if (appData.highlightLastMove) {
8259             SetHighlights(fromX, fromY, toX, toY);
8260         }
8261     }
8262     DisplayMove(currentMove);
8263     SendMoveToProgram(currentMove++, &first);
8264     DisplayBothClocks();
8265     DrawPosition(FALSE, boards[currentMove]);
8266     // [HGM] PV info: always display, routine tests if empty
8267     DisplayComment(currentMove - 1, commentList[currentMove]);
8268     return TRUE;
8269 }
8270
8271
8272 int
8273 LoadGameOneMove(readAhead)
8274      ChessMove readAhead;
8275 {
8276     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8277     char promoChar = NULLCHAR;
8278     ChessMove moveType;
8279     char move[MSG_SIZ];
8280     char *p, *q;
8281     
8282     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8283         gameMode != AnalyzeMode && gameMode != Training) {
8284         gameFileFP = NULL;
8285         return FALSE;
8286     }
8287     
8288     yyboardindex = forwardMostMove;
8289     if (readAhead != (ChessMove)0) {
8290       moveType = readAhead;
8291     } else {
8292       if (gameFileFP == NULL)
8293           return FALSE;
8294       moveType = (ChessMove) yylex();
8295     }
8296     
8297     done = FALSE;
8298     switch (moveType) {
8299       case Comment:
8300         if (appData.debugMode) 
8301           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8302         p = yy_text;
8303         if (*p == '{' || *p == '[' || *p == '(') {
8304             p[strlen(p) - 1] = NULLCHAR;
8305             p++;
8306         }
8307
8308         /* append the comment but don't display it */
8309         while (*p == '\n') p++;
8310         AppendComment(currentMove, p);
8311         return TRUE;
8312
8313       case WhiteCapturesEnPassant:
8314       case BlackCapturesEnPassant:
8315       case WhitePromotionChancellor:
8316       case BlackPromotionChancellor:
8317       case WhitePromotionArchbishop:
8318       case BlackPromotionArchbishop:
8319       case WhitePromotionCentaur:
8320       case BlackPromotionCentaur:
8321       case WhitePromotionQueen:
8322       case BlackPromotionQueen:
8323       case WhitePromotionRook:
8324       case BlackPromotionRook:
8325       case WhitePromotionBishop:
8326       case BlackPromotionBishop:
8327       case WhitePromotionKnight:
8328       case BlackPromotionKnight:
8329       case WhitePromotionKing:
8330       case BlackPromotionKing:
8331       case NormalMove:
8332       case WhiteKingSideCastle:
8333       case WhiteQueenSideCastle:
8334       case BlackKingSideCastle:
8335       case BlackQueenSideCastle:
8336       case WhiteKingSideCastleWild:
8337       case WhiteQueenSideCastleWild:
8338       case BlackKingSideCastleWild:
8339       case BlackQueenSideCastleWild:
8340       /* PUSH Fabien */
8341       case WhiteHSideCastleFR:
8342       case WhiteASideCastleFR:
8343       case BlackHSideCastleFR:
8344       case BlackASideCastleFR:
8345       /* POP Fabien */
8346         if (appData.debugMode)
8347           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8348         fromX = currentMoveString[0] - AAA;
8349         fromY = currentMoveString[1] - ONE;
8350         toX = currentMoveString[2] - AAA;
8351         toY = currentMoveString[3] - ONE;
8352         promoChar = currentMoveString[4];
8353         break;
8354
8355       case WhiteDrop:
8356       case BlackDrop:
8357         if (appData.debugMode)
8358           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8359         fromX = moveType == WhiteDrop ?
8360           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8361         (int) CharToPiece(ToLower(currentMoveString[0]));
8362         fromY = DROP_RANK;
8363         toX = currentMoveString[2] - AAA;
8364         toY = currentMoveString[3] - ONE;
8365         break;
8366
8367       case WhiteWins:
8368       case BlackWins:
8369       case GameIsDrawn:
8370       case GameUnfinished:
8371         if (appData.debugMode)
8372           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8373         p = strchr(yy_text, '{');
8374         if (p == NULL) p = strchr(yy_text, '(');
8375         if (p == NULL) {
8376             p = yy_text;
8377             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8378         } else {
8379             q = strchr(p, *p == '{' ? '}' : ')');
8380             if (q != NULL) *q = NULLCHAR;
8381             p++;
8382         }
8383         GameEnds(moveType, p, GE_FILE);
8384         done = TRUE;
8385         if (cmailMsgLoaded) {
8386             ClearHighlights();
8387             flipView = WhiteOnMove(currentMove);
8388             if (moveType == GameUnfinished) flipView = !flipView;
8389             if (appData.debugMode)
8390               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8391         }
8392         break;
8393
8394       case (ChessMove) 0:       /* end of file */
8395         if (appData.debugMode)
8396           fprintf(debugFP, "Parser hit end of file\n");
8397         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8398                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8399           case MT_NONE:
8400           case MT_CHECK:
8401             break;
8402           case MT_CHECKMATE:
8403           case MT_STAINMATE:
8404             if (WhiteOnMove(currentMove)) {
8405                 GameEnds(BlackWins, "Black mates", GE_FILE);
8406             } else {
8407                 GameEnds(WhiteWins, "White mates", GE_FILE);
8408             }
8409             break;
8410           case MT_STALEMATE:
8411             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8412             break;
8413         }
8414         done = TRUE;
8415         break;
8416
8417       case MoveNumberOne:
8418         if (lastLoadGameStart == GNUChessGame) {
8419             /* GNUChessGames have numbers, but they aren't move numbers */
8420             if (appData.debugMode)
8421               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8422                       yy_text, (int) moveType);
8423             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8424         }
8425         /* else fall thru */
8426
8427       case XBoardGame:
8428       case GNUChessGame:
8429       case PGNTag:
8430         /* Reached start of next game in file */
8431         if (appData.debugMode)
8432           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8433         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8434                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8435           case MT_NONE:
8436           case MT_CHECK:
8437             break;
8438           case MT_CHECKMATE:
8439           case MT_STAINMATE:
8440             if (WhiteOnMove(currentMove)) {
8441                 GameEnds(BlackWins, "Black mates", GE_FILE);
8442             } else {
8443                 GameEnds(WhiteWins, "White mates", GE_FILE);
8444             }
8445             break;
8446           case MT_STALEMATE:
8447             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8448             break;
8449         }
8450         done = TRUE;
8451         break;
8452
8453       case PositionDiagram:     /* should not happen; ignore */
8454       case ElapsedTime:         /* ignore */
8455       case NAG:                 /* ignore */
8456         if (appData.debugMode)
8457           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8458                   yy_text, (int) moveType);
8459         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8460
8461       case IllegalMove:
8462         if (appData.testLegality) {
8463             if (appData.debugMode)
8464               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8465             sprintf(move, _("Illegal move: %d.%s%s"),
8466                     (forwardMostMove / 2) + 1,
8467                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8468             DisplayError(move, 0);
8469             done = TRUE;
8470         } else {
8471             if (appData.debugMode)
8472               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8473                       yy_text, currentMoveString);
8474             fromX = currentMoveString[0] - AAA;
8475             fromY = currentMoveString[1] - ONE;
8476             toX = currentMoveString[2] - AAA;
8477             toY = currentMoveString[3] - ONE;
8478             promoChar = currentMoveString[4];
8479         }
8480         break;
8481
8482       case AmbiguousMove:
8483         if (appData.debugMode)
8484           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8485         sprintf(move, _("Ambiguous move: %d.%s%s"),
8486                 (forwardMostMove / 2) + 1,
8487                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8488         DisplayError(move, 0);
8489         done = TRUE;
8490         break;
8491
8492       default:
8493       case ImpossibleMove:
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8496         sprintf(move, _("Illegal move: %d.%s%s"),
8497                 (forwardMostMove / 2) + 1,
8498                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8499         DisplayError(move, 0);
8500         done = TRUE;
8501         break;
8502     }
8503
8504     if (done) {
8505         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8506             DrawPosition(FALSE, boards[currentMove]);
8507             DisplayBothClocks();
8508             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8509               DisplayComment(currentMove - 1, commentList[currentMove]);
8510         }
8511         (void) StopLoadGameTimer();
8512         gameFileFP = NULL;
8513         cmailOldMove = forwardMostMove;
8514         return FALSE;
8515     } else {
8516         /* currentMoveString is set as a side-effect of yylex */
8517         strcat(currentMoveString, "\n");
8518         strcpy(moveList[forwardMostMove], currentMoveString);
8519         
8520         thinkOutput[0] = NULLCHAR;
8521         MakeMove(fromX, fromY, toX, toY, promoChar);
8522         currentMove = forwardMostMove;
8523         return TRUE;
8524     }
8525 }
8526
8527 /* Load the nth game from the given file */
8528 int
8529 LoadGameFromFile(filename, n, title, useList)
8530      char *filename;
8531      int n;
8532      char *title;
8533      /*Boolean*/ int useList;
8534 {
8535     FILE *f;
8536     char buf[MSG_SIZ];
8537
8538     if (strcmp(filename, "-") == 0) {
8539         f = stdin;
8540         title = "stdin";
8541     } else {
8542         f = fopen(filename, "rb");
8543         if (f == NULL) {
8544           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8545             DisplayError(buf, errno);
8546             return FALSE;
8547         }
8548     }
8549     if (fseek(f, 0, 0) == -1) {
8550         /* f is not seekable; probably a pipe */
8551         useList = FALSE;
8552     }
8553     if (useList && n == 0) {
8554         int error = GameListBuild(f);
8555         if (error) {
8556             DisplayError(_("Cannot build game list"), error);
8557         } else if (!ListEmpty(&gameList) &&
8558                    ((ListGame *) gameList.tailPred)->number > 1) {
8559             GameListPopUp(f, title);
8560             return TRUE;
8561         }
8562         GameListDestroy();
8563         n = 1;
8564     }
8565     if (n == 0) n = 1;
8566     return LoadGame(f, n, title, FALSE);
8567 }
8568
8569
8570 void
8571 MakeRegisteredMove()
8572 {
8573     int fromX, fromY, toX, toY;
8574     char promoChar;
8575     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8576         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8577           case CMAIL_MOVE:
8578           case CMAIL_DRAW:
8579             if (appData.debugMode)
8580               fprintf(debugFP, "Restoring %s for game %d\n",
8581                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8582     
8583             thinkOutput[0] = NULLCHAR;
8584             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8585             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8586             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8587             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8588             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8589             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8590             MakeMove(fromX, fromY, toX, toY, promoChar);
8591             ShowMove(fromX, fromY, toX, toY);
8592               
8593             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8594                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8595               case MT_NONE:
8596               case MT_CHECK:
8597                 break;
8598                 
8599               case MT_CHECKMATE:
8600               case MT_STAINMATE:
8601                 if (WhiteOnMove(currentMove)) {
8602                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8603                 } else {
8604                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8605                 }
8606                 break;
8607                 
8608               case MT_STALEMATE:
8609                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8610                 break;
8611             }
8612
8613             break;
8614             
8615           case CMAIL_RESIGN:
8616             if (WhiteOnMove(currentMove)) {
8617                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8618             } else {
8619                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8620             }
8621             break;
8622             
8623           case CMAIL_ACCEPT:
8624             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8625             break;
8626               
8627           default:
8628             break;
8629         }
8630     }
8631
8632     return;
8633 }
8634
8635 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8636 int
8637 CmailLoadGame(f, gameNumber, title, useList)
8638      FILE *f;
8639      int gameNumber;
8640      char *title;
8641      int useList;
8642 {
8643     int retVal;
8644
8645     if (gameNumber > nCmailGames) {
8646         DisplayError(_("No more games in this message"), 0);
8647         return FALSE;
8648     }
8649     if (f == lastLoadGameFP) {
8650         int offset = gameNumber - lastLoadGameNumber;
8651         if (offset == 0) {
8652             cmailMsg[0] = NULLCHAR;
8653             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8654                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8655                 nCmailMovesRegistered--;
8656             }
8657             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8658             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8659                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8660             }
8661         } else {
8662             if (! RegisterMove()) return FALSE;
8663         }
8664     }
8665
8666     retVal = LoadGame(f, gameNumber, title, useList);
8667
8668     /* Make move registered during previous look at this game, if any */
8669     MakeRegisteredMove();
8670
8671     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8672         commentList[currentMove]
8673           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8674         DisplayComment(currentMove - 1, commentList[currentMove]);
8675     }
8676
8677     return retVal;
8678 }
8679
8680 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8681 int
8682 ReloadGame(offset)
8683      int offset;
8684 {
8685     int gameNumber = lastLoadGameNumber + offset;
8686     if (lastLoadGameFP == NULL) {
8687         DisplayError(_("No game has been loaded yet"), 0);
8688         return FALSE;
8689     }
8690     if (gameNumber <= 0) {
8691         DisplayError(_("Can't back up any further"), 0);
8692         return FALSE;
8693     }
8694     if (cmailMsgLoaded) {
8695         return CmailLoadGame(lastLoadGameFP, gameNumber,
8696                              lastLoadGameTitle, lastLoadGameUseList);
8697     } else {
8698         return LoadGame(lastLoadGameFP, gameNumber,
8699                         lastLoadGameTitle, lastLoadGameUseList);
8700     }
8701 }
8702
8703
8704
8705 /* Load the nth game from open file f */
8706 int
8707 LoadGame(f, gameNumber, title, useList)
8708      FILE *f;
8709      int gameNumber;
8710      char *title;
8711      int useList;
8712 {
8713     ChessMove cm;
8714     char buf[MSG_SIZ];
8715     int gn = gameNumber;
8716     ListGame *lg = NULL;
8717     int numPGNTags = 0;
8718     int err;
8719     GameMode oldGameMode;
8720     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8721
8722     if (appData.debugMode) 
8723         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8724
8725     if (gameMode == Training )
8726         SetTrainingModeOff();
8727
8728     oldGameMode = gameMode;
8729     if (gameMode != BeginningOfGame) {
8730       Reset(FALSE, TRUE);
8731     }
8732
8733     gameFileFP = f;
8734     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8735         fclose(lastLoadGameFP);
8736     }
8737
8738     if (useList) {
8739         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8740         
8741         if (lg) {
8742             fseek(f, lg->offset, 0);
8743             GameListHighlight(gameNumber);
8744             gn = 1;
8745         }
8746         else {
8747             DisplayError(_("Game number out of range"), 0);
8748             return FALSE;
8749         }
8750     } else {
8751         GameListDestroy();
8752         if (fseek(f, 0, 0) == -1) {
8753             if (f == lastLoadGameFP ?
8754                 gameNumber == lastLoadGameNumber + 1 :
8755                 gameNumber == 1) {
8756                 gn = 1;
8757             } else {
8758                 DisplayError(_("Can't seek on game file"), 0);
8759                 return FALSE;
8760             }
8761         }
8762     }
8763     lastLoadGameFP = f;
8764     lastLoadGameNumber = gameNumber;
8765     strcpy(lastLoadGameTitle, title);
8766     lastLoadGameUseList = useList;
8767
8768     yynewfile(f);
8769
8770     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8771       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8772                 lg->gameInfo.black);
8773             DisplayTitle(buf);
8774     } else if (*title != NULLCHAR) {
8775         if (gameNumber > 1) {
8776             sprintf(buf, "%s %d", title, gameNumber);
8777             DisplayTitle(buf);
8778         } else {
8779             DisplayTitle(title);
8780         }
8781     }
8782
8783     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8784         gameMode = PlayFromGameFile;
8785         ModeHighlight();
8786     }
8787
8788     currentMove = forwardMostMove = backwardMostMove = 0;
8789     CopyBoard(boards[0], initialPosition);
8790     StopClocks();
8791
8792     /*
8793      * Skip the first gn-1 games in the file.
8794      * Also skip over anything that precedes an identifiable 
8795      * start of game marker, to avoid being confused by 
8796      * garbage at the start of the file.  Currently 
8797      * recognized start of game markers are the move number "1",
8798      * the pattern "gnuchess .* game", the pattern
8799      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8800      * A game that starts with one of the latter two patterns
8801      * will also have a move number 1, possibly
8802      * following a position diagram.
8803      * 5-4-02: Let's try being more lenient and allowing a game to
8804      * start with an unnumbered move.  Does that break anything?
8805      */
8806     cm = lastLoadGameStart = (ChessMove) 0;
8807     while (gn > 0) {
8808         yyboardindex = forwardMostMove;
8809         cm = (ChessMove) yylex();
8810         switch (cm) {
8811           case (ChessMove) 0:
8812             if (cmailMsgLoaded) {
8813                 nCmailGames = CMAIL_MAX_GAMES - gn;
8814             } else {
8815                 Reset(TRUE, TRUE);
8816                 DisplayError(_("Game not found in file"), 0);
8817             }
8818             return FALSE;
8819
8820           case GNUChessGame:
8821           case XBoardGame:
8822             gn--;
8823             lastLoadGameStart = cm;
8824             break;
8825             
8826           case MoveNumberOne:
8827             switch (lastLoadGameStart) {
8828               case GNUChessGame:
8829               case XBoardGame:
8830               case PGNTag:
8831                 break;
8832               case MoveNumberOne:
8833               case (ChessMove) 0:
8834                 gn--;           /* count this game */
8835                 lastLoadGameStart = cm;
8836                 break;
8837               default:
8838                 /* impossible */
8839                 break;
8840             }
8841             break;
8842
8843           case PGNTag:
8844             switch (lastLoadGameStart) {
8845               case GNUChessGame:
8846               case PGNTag:
8847               case MoveNumberOne:
8848               case (ChessMove) 0:
8849                 gn--;           /* count this game */
8850                 lastLoadGameStart = cm;
8851                 break;
8852               case XBoardGame:
8853                 lastLoadGameStart = cm; /* game counted already */
8854                 break;
8855               default:
8856                 /* impossible */
8857                 break;
8858             }
8859             if (gn > 0) {
8860                 do {
8861                     yyboardindex = forwardMostMove;
8862                     cm = (ChessMove) yylex();
8863                 } while (cm == PGNTag || cm == Comment);
8864             }
8865             break;
8866
8867           case WhiteWins:
8868           case BlackWins:
8869           case GameIsDrawn:
8870             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8871                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8872                     != CMAIL_OLD_RESULT) {
8873                     nCmailResults ++ ;
8874                     cmailResult[  CMAIL_MAX_GAMES
8875                                 - gn - 1] = CMAIL_OLD_RESULT;
8876                 }
8877             }
8878             break;
8879
8880           case NormalMove:
8881             /* Only a NormalMove can be at the start of a game
8882              * without a position diagram. */
8883             if (lastLoadGameStart == (ChessMove) 0) {
8884               gn--;
8885               lastLoadGameStart = MoveNumberOne;
8886             }
8887             break;
8888
8889           default:
8890             break;
8891         }
8892     }
8893     
8894     if (appData.debugMode)
8895       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8896
8897     if (cm == XBoardGame) {
8898         /* Skip any header junk before position diagram and/or move 1 */
8899         for (;;) {
8900             yyboardindex = forwardMostMove;
8901             cm = (ChessMove) yylex();
8902
8903             if (cm == (ChessMove) 0 ||
8904                 cm == GNUChessGame || cm == XBoardGame) {
8905                 /* Empty game; pretend end-of-file and handle later */
8906                 cm = (ChessMove) 0;
8907                 break;
8908             }
8909
8910             if (cm == MoveNumberOne || cm == PositionDiagram ||
8911                 cm == PGNTag || cm == Comment)
8912               break;
8913         }
8914     } else if (cm == GNUChessGame) {
8915         if (gameInfo.event != NULL) {
8916             free(gameInfo.event);
8917         }
8918         gameInfo.event = StrSave(yy_text);
8919     }   
8920
8921     startedFromSetupPosition = FALSE;
8922     while (cm == PGNTag) {
8923         if (appData.debugMode) 
8924           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8925         err = ParsePGNTag(yy_text, &gameInfo);
8926         if (!err) numPGNTags++;
8927
8928         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8929         if(gameInfo.variant != oldVariant) {
8930             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8931             InitPosition(TRUE);
8932             oldVariant = gameInfo.variant;
8933             if (appData.debugMode) 
8934               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8935         }
8936
8937
8938         if (gameInfo.fen != NULL) {
8939           Board initial_position;
8940           startedFromSetupPosition = TRUE;
8941           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8942             Reset(TRUE, TRUE);
8943             DisplayError(_("Bad FEN position in file"), 0);
8944             return FALSE;
8945           }
8946           CopyBoard(boards[0], initial_position);
8947           if (blackPlaysFirst) {
8948             currentMove = forwardMostMove = backwardMostMove = 1;
8949             CopyBoard(boards[1], initial_position);
8950             strcpy(moveList[0], "");
8951             strcpy(parseList[0], "");
8952             timeRemaining[0][1] = whiteTimeRemaining;
8953             timeRemaining[1][1] = blackTimeRemaining;
8954             if (commentList[0] != NULL) {
8955               commentList[1] = commentList[0];
8956               commentList[0] = NULL;
8957             }
8958           } else {
8959             currentMove = forwardMostMove = backwardMostMove = 0;
8960           }
8961           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8962           {   int i;
8963               initialRulePlies = FENrulePlies;
8964               epStatus[forwardMostMove] = FENepStatus;
8965               for( i=0; i< nrCastlingRights; i++ )
8966                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8967           }
8968           yyboardindex = forwardMostMove;
8969           free(gameInfo.fen);
8970           gameInfo.fen = NULL;
8971         }
8972
8973         yyboardindex = forwardMostMove;
8974         cm = (ChessMove) yylex();
8975
8976         /* Handle comments interspersed among the tags */
8977         while (cm == Comment) {
8978             char *p;
8979             if (appData.debugMode) 
8980               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8981             p = yy_text;
8982             if (*p == '{' || *p == '[' || *p == '(') {
8983                 p[strlen(p) - 1] = NULLCHAR;
8984                 p++;
8985             }
8986             while (*p == '\n') p++;
8987             AppendComment(currentMove, p);
8988             yyboardindex = forwardMostMove;
8989             cm = (ChessMove) yylex();
8990         }
8991     }
8992
8993     /* don't rely on existence of Event tag since if game was
8994      * pasted from clipboard the Event tag may not exist
8995      */
8996     if (numPGNTags > 0){
8997         char *tags;
8998         if (gameInfo.variant == VariantNormal) {
8999           gameInfo.variant = StringToVariant(gameInfo.event);
9000         }
9001         if (!matchMode) {
9002           if( appData.autoDisplayTags ) {
9003             tags = PGNTags(&gameInfo);
9004             TagsPopUp(tags, CmailMsg());
9005             free(tags);
9006           }
9007         }
9008     } else {
9009         /* Make something up, but don't display it now */
9010         SetGameInfo();
9011         TagsPopDown();
9012     }
9013
9014     if (cm == PositionDiagram) {
9015         int i, j;
9016         char *p;
9017         Board initial_position;
9018
9019         if (appData.debugMode)
9020           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9021
9022         if (!startedFromSetupPosition) {
9023             p = yy_text;
9024             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9025               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9026                 switch (*p) {
9027                   case '[':
9028                   case '-':
9029                   case ' ':
9030                   case '\t':
9031                   case '\n':
9032                   case '\r':
9033                     break;
9034                   default:
9035                     initial_position[i][j++] = CharToPiece(*p);
9036                     break;
9037                 }
9038             while (*p == ' ' || *p == '\t' ||
9039                    *p == '\n' || *p == '\r') p++;
9040         
9041             if (strncmp(p, "black", strlen("black"))==0)
9042               blackPlaysFirst = TRUE;
9043             else
9044               blackPlaysFirst = FALSE;
9045             startedFromSetupPosition = TRUE;
9046         
9047             CopyBoard(boards[0], initial_position);
9048             if (blackPlaysFirst) {
9049                 currentMove = forwardMostMove = backwardMostMove = 1;
9050                 CopyBoard(boards[1], initial_position);
9051                 strcpy(moveList[0], "");
9052                 strcpy(parseList[0], "");
9053                 timeRemaining[0][1] = whiteTimeRemaining;
9054                 timeRemaining[1][1] = blackTimeRemaining;
9055                 if (commentList[0] != NULL) {
9056                     commentList[1] = commentList[0];
9057                     commentList[0] = NULL;
9058                 }
9059             } else {
9060                 currentMove = forwardMostMove = backwardMostMove = 0;
9061             }
9062         }
9063         yyboardindex = forwardMostMove;
9064         cm = (ChessMove) yylex();
9065     }
9066
9067     if (first.pr == NoProc) {
9068         StartChessProgram(&first);
9069     }
9070     InitChessProgram(&first, FALSE);
9071     SendToProgram("force\n", &first);
9072     if (startedFromSetupPosition) {
9073         SendBoard(&first, forwardMostMove);
9074     if (appData.debugMode) {
9075         fprintf(debugFP, "Load Game\n");
9076     }
9077         DisplayBothClocks();
9078     }      
9079
9080     /* [HGM] server: flag to write setup moves in broadcast file as one */
9081     loadFlag = appData.suppressLoadMoves;
9082
9083     while (cm == Comment) {
9084         char *p;
9085         if (appData.debugMode) 
9086           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9087         p = yy_text;
9088         if (*p == '{' || *p == '[' || *p == '(') {
9089             p[strlen(p) - 1] = NULLCHAR;
9090             p++;
9091         }
9092         while (*p == '\n') p++;
9093         AppendComment(currentMove, p);
9094         yyboardindex = forwardMostMove;
9095         cm = (ChessMove) yylex();
9096     }
9097
9098     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9099         cm == WhiteWins || cm == BlackWins ||
9100         cm == GameIsDrawn || cm == GameUnfinished) {
9101         DisplayMessage("", _("No moves in game"));
9102         if (cmailMsgLoaded) {
9103             if (appData.debugMode)
9104               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9105             ClearHighlights();
9106             flipView = FALSE;
9107         }
9108         DrawPosition(FALSE, boards[currentMove]);
9109         DisplayBothClocks();
9110         gameMode = EditGame;
9111         ModeHighlight();
9112         gameFileFP = NULL;
9113         cmailOldMove = 0;
9114         return TRUE;
9115     }
9116
9117     // [HGM] PV info: routine tests if comment empty
9118     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9119         DisplayComment(currentMove - 1, commentList[currentMove]);
9120     }
9121     if (!matchMode && appData.timeDelay != 0) 
9122       DrawPosition(FALSE, boards[currentMove]);
9123
9124     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9125       programStats.ok_to_send = 1;
9126     }
9127
9128     /* if the first token after the PGN tags is a move
9129      * and not move number 1, retrieve it from the parser 
9130      */
9131     if (cm != MoveNumberOne)
9132         LoadGameOneMove(cm);
9133
9134     /* load the remaining moves from the file */
9135     while (LoadGameOneMove((ChessMove)0)) {
9136       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9137       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9138     }
9139
9140     /* rewind to the start of the game */
9141     currentMove = backwardMostMove;
9142
9143     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9144
9145     if (oldGameMode == AnalyzeFile ||
9146         oldGameMode == AnalyzeMode) {
9147       AnalyzeFileEvent();
9148     }
9149
9150     if (matchMode || appData.timeDelay == 0) {
9151       ToEndEvent();
9152       gameMode = EditGame;
9153       ModeHighlight();
9154     } else if (appData.timeDelay > 0) {
9155       AutoPlayGameLoop();
9156     }
9157
9158     if (appData.debugMode) 
9159         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9160
9161     loadFlag = 0; /* [HGM] true game starts */
9162     return TRUE;
9163 }
9164
9165 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9166 int
9167 ReloadPosition(offset)
9168      int offset;
9169 {
9170     int positionNumber = lastLoadPositionNumber + offset;
9171     if (lastLoadPositionFP == NULL) {
9172         DisplayError(_("No position has been loaded yet"), 0);
9173         return FALSE;
9174     }
9175     if (positionNumber <= 0) {
9176         DisplayError(_("Can't back up any further"), 0);
9177         return FALSE;
9178     }
9179     return LoadPosition(lastLoadPositionFP, positionNumber,
9180                         lastLoadPositionTitle);
9181 }
9182
9183 /* Load the nth position from the given file */
9184 int
9185 LoadPositionFromFile(filename, n, title)
9186      char *filename;
9187      int n;
9188      char *title;
9189 {
9190     FILE *f;
9191     char buf[MSG_SIZ];
9192
9193     if (strcmp(filename, "-") == 0) {
9194         return LoadPosition(stdin, n, "stdin");
9195     } else {
9196         f = fopen(filename, "rb");
9197         if (f == NULL) {
9198             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9199             DisplayError(buf, errno);
9200             return FALSE;
9201         } else {
9202             return LoadPosition(f, n, title);
9203         }
9204     }
9205 }
9206
9207 /* Load the nth position from the given open file, and close it */
9208 int
9209 LoadPosition(f, positionNumber, title)
9210      FILE *f;
9211      int positionNumber;
9212      char *title;
9213 {
9214     char *p, line[MSG_SIZ];
9215     Board initial_position;
9216     int i, j, fenMode, pn;
9217     
9218     if (gameMode == Training )
9219         SetTrainingModeOff();
9220
9221     if (gameMode != BeginningOfGame) {
9222         Reset(FALSE, TRUE);
9223     }
9224     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9225         fclose(lastLoadPositionFP);
9226     }
9227     if (positionNumber == 0) positionNumber = 1;
9228     lastLoadPositionFP = f;
9229     lastLoadPositionNumber = positionNumber;
9230     strcpy(lastLoadPositionTitle, title);
9231     if (first.pr == NoProc) {
9232       StartChessProgram(&first);
9233       InitChessProgram(&first, FALSE);
9234     }    
9235     pn = positionNumber;
9236     if (positionNumber < 0) {
9237         /* Negative position number means to seek to that byte offset */
9238         if (fseek(f, -positionNumber, 0) == -1) {
9239             DisplayError(_("Can't seek on position file"), 0);
9240             return FALSE;
9241         };
9242         pn = 1;
9243     } else {
9244         if (fseek(f, 0, 0) == -1) {
9245             if (f == lastLoadPositionFP ?
9246                 positionNumber == lastLoadPositionNumber + 1 :
9247                 positionNumber == 1) {
9248                 pn = 1;
9249             } else {
9250                 DisplayError(_("Can't seek on position file"), 0);
9251                 return FALSE;
9252             }
9253         }
9254     }
9255     /* See if this file is FEN or old-style xboard */
9256     if (fgets(line, MSG_SIZ, f) == NULL) {
9257         DisplayError(_("Position not found in file"), 0);
9258         return FALSE;
9259     }
9260 #if 0
9261     switch (line[0]) {
9262       case '#':  case 'x':
9263       default:
9264         fenMode = FALSE;
9265         break;
9266       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9267       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9268       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9269       case '7':  case '8':  case '9':
9270       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9271       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9272       case 'C':  case 'W':             case 'c':  case 'w': 
9273         fenMode = TRUE;
9274         break;
9275     }
9276 #else
9277     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9278     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9279 #endif
9280
9281     if (pn >= 2) {
9282         if (fenMode || line[0] == '#') pn--;
9283         while (pn > 0) {
9284             /* skip positions before number pn */
9285             if (fgets(line, MSG_SIZ, f) == NULL) {
9286                 Reset(TRUE, TRUE);
9287                 DisplayError(_("Position not found in file"), 0);
9288                 return FALSE;
9289             }
9290             if (fenMode || line[0] == '#') pn--;
9291         }
9292     }
9293
9294     if (fenMode) {
9295         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9296             DisplayError(_("Bad FEN position in file"), 0);
9297             return FALSE;
9298         }
9299     } else {
9300         (void) fgets(line, MSG_SIZ, f);
9301         (void) fgets(line, MSG_SIZ, f);
9302     
9303         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9304             (void) fgets(line, MSG_SIZ, f);
9305             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9306                 if (*p == ' ')
9307                   continue;
9308                 initial_position[i][j++] = CharToPiece(*p);
9309             }
9310         }
9311     
9312         blackPlaysFirst = FALSE;
9313         if (!feof(f)) {
9314             (void) fgets(line, MSG_SIZ, f);
9315             if (strncmp(line, "black", strlen("black"))==0)
9316               blackPlaysFirst = TRUE;
9317         }
9318     }
9319     startedFromSetupPosition = TRUE;
9320     
9321     SendToProgram("force\n", &first);
9322     CopyBoard(boards[0], initial_position);
9323     if (blackPlaysFirst) {
9324         currentMove = forwardMostMove = backwardMostMove = 1;
9325         strcpy(moveList[0], "");
9326         strcpy(parseList[0], "");
9327         CopyBoard(boards[1], initial_position);
9328         DisplayMessage("", _("Black to play"));
9329     } else {
9330         currentMove = forwardMostMove = backwardMostMove = 0;
9331         DisplayMessage("", _("White to play"));
9332     }
9333           /* [HGM] copy FEN attributes as well */
9334           {   int i;
9335               initialRulePlies = FENrulePlies;
9336               epStatus[forwardMostMove] = FENepStatus;
9337               for( i=0; i< nrCastlingRights; i++ )
9338                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9339           }
9340     SendBoard(&first, forwardMostMove);
9341     if (appData.debugMode) {
9342 int i, j;
9343   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9344   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9345         fprintf(debugFP, "Load Position\n");
9346     }
9347
9348     if (positionNumber > 1) {
9349         sprintf(line, "%s %d", title, positionNumber);
9350         DisplayTitle(line);
9351     } else {
9352         DisplayTitle(title);
9353     }
9354     gameMode = EditGame;
9355     ModeHighlight();
9356     ResetClocks();
9357     timeRemaining[0][1] = whiteTimeRemaining;
9358     timeRemaining[1][1] = blackTimeRemaining;
9359     DrawPosition(FALSE, boards[currentMove]);
9360    
9361     return TRUE;
9362 }
9363
9364
9365 void
9366 CopyPlayerNameIntoFileName(dest, src)
9367      char **dest, *src;
9368 {
9369     while (*src != NULLCHAR && *src != ',') {
9370         if (*src == ' ') {
9371             *(*dest)++ = '_';
9372             src++;
9373         } else {
9374             *(*dest)++ = *src++;
9375         }
9376     }
9377 }
9378
9379 char *DefaultFileName(ext)
9380      char *ext;
9381 {
9382     static char def[MSG_SIZ];
9383     char *p;
9384
9385     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9386         p = def;
9387         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9388         *p++ = '-';
9389         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9390         *p++ = '.';
9391         strcpy(p, ext);
9392     } else {
9393         def[0] = NULLCHAR;
9394     }
9395     return def;
9396 }
9397
9398 /* Save the current game to the given file */
9399 int
9400 SaveGameToFile(filename, append)
9401      char *filename;
9402      int append;
9403 {
9404     FILE *f;
9405     char buf[MSG_SIZ];
9406
9407     if (strcmp(filename, "-") == 0) {
9408         return SaveGame(stdout, 0, NULL);
9409     } else {
9410         f = fopen(filename, append ? "a" : "w");
9411         if (f == NULL) {
9412             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9413             DisplayError(buf, errno);
9414             return FALSE;
9415         } else {
9416             return SaveGame(f, 0, NULL);
9417         }
9418     }
9419 }
9420
9421 char *
9422 SavePart(str)
9423      char *str;
9424 {
9425     static char buf[MSG_SIZ];
9426     char *p;
9427     
9428     p = strchr(str, ' ');
9429     if (p == NULL) return str;
9430     strncpy(buf, str, p - str);
9431     buf[p - str] = NULLCHAR;
9432     return buf;
9433 }
9434
9435 #define PGN_MAX_LINE 75
9436
9437 #define PGN_SIDE_WHITE  0
9438 #define PGN_SIDE_BLACK  1
9439
9440 /* [AS] */
9441 static int FindFirstMoveOutOfBook( int side )
9442 {
9443     int result = -1;
9444
9445     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9446         int index = backwardMostMove;
9447         int has_book_hit = 0;
9448
9449         if( (index % 2) != side ) {
9450             index++;
9451         }
9452
9453         while( index < forwardMostMove ) {
9454             /* Check to see if engine is in book */
9455             int depth = pvInfoList[index].depth;
9456             int score = pvInfoList[index].score;
9457             int in_book = 0;
9458
9459             if( depth <= 2 ) {
9460                 in_book = 1;
9461             }
9462             else if( score == 0 && depth == 63 ) {
9463                 in_book = 1; /* Zappa */
9464             }
9465             else if( score == 2 && depth == 99 ) {
9466                 in_book = 1; /* Abrok */
9467             }
9468
9469             has_book_hit += in_book;
9470
9471             if( ! in_book ) {
9472                 result = index;
9473
9474                 break;
9475             }
9476
9477             index += 2;
9478         }
9479     }
9480
9481     return result;
9482 }
9483
9484 /* [AS] */
9485 void GetOutOfBookInfo( char * buf )
9486 {
9487     int oob[2];
9488     int i;
9489     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9490
9491     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9492     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9493
9494     *buf = '\0';
9495
9496     if( oob[0] >= 0 || oob[1] >= 0 ) {
9497         for( i=0; i<2; i++ ) {
9498             int idx = oob[i];
9499
9500             if( idx >= 0 ) {
9501                 if( i > 0 && oob[0] >= 0 ) {
9502                     strcat( buf, "   " );
9503                 }
9504
9505                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9506                 sprintf( buf+strlen(buf), "%s%.2f", 
9507                     pvInfoList[idx].score >= 0 ? "+" : "",
9508                     pvInfoList[idx].score / 100.0 );
9509             }
9510         }
9511     }
9512 }
9513
9514 /* Save game in PGN style and close the file */
9515 int
9516 SaveGamePGN(f)
9517      FILE *f;
9518 {
9519     int i, offset, linelen, newblock;
9520     time_t tm;
9521 //    char *movetext;
9522     char numtext[32];
9523     int movelen, numlen, blank;
9524     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9525
9526     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9527     
9528     tm = time((time_t *) NULL);
9529     
9530     PrintPGNTags(f, &gameInfo);
9531     
9532     if (backwardMostMove > 0 || startedFromSetupPosition) {
9533         char *fen = PositionToFEN(backwardMostMove, NULL);
9534         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9535         fprintf(f, "\n{--------------\n");
9536         PrintPosition(f, backwardMostMove);
9537         fprintf(f, "--------------}\n");
9538         free(fen);
9539     }
9540     else {
9541         /* [AS] Out of book annotation */
9542         if( appData.saveOutOfBookInfo ) {
9543             char buf[64];
9544
9545             GetOutOfBookInfo( buf );
9546
9547             if( buf[0] != '\0' ) {
9548                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9549             }
9550         }
9551
9552         fprintf(f, "\n");
9553     }
9554
9555     i = backwardMostMove;
9556     linelen = 0;
9557     newblock = TRUE;
9558
9559     while (i < forwardMostMove) {
9560         /* Print comments preceding this move */
9561         if (commentList[i] != NULL) {
9562             if (linelen > 0) fprintf(f, "\n");
9563             fprintf(f, "{\n%s}\n", commentList[i]);
9564             linelen = 0;
9565             newblock = TRUE;
9566         }
9567
9568         /* Format move number */
9569         if ((i % 2) == 0) {
9570             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9571         } else {
9572             if (newblock) {
9573                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9574             } else {
9575                 numtext[0] = NULLCHAR;
9576             }
9577         }
9578         numlen = strlen(numtext);
9579         newblock = FALSE;
9580
9581         /* Print move number */
9582         blank = linelen > 0 && numlen > 0;
9583         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9584             fprintf(f, "\n");
9585             linelen = 0;
9586             blank = 0;
9587         }
9588         if (blank) {
9589             fprintf(f, " ");
9590             linelen++;
9591         }
9592         fprintf(f, numtext);
9593         linelen += numlen;
9594
9595         /* Get move */
9596         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9597         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9598         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9599                 int p = movelen - 1;
9600                 if(move_buffer[p] == ' ') p--;
9601                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9602                     while(p && move_buffer[--p] != '(');
9603                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9604                 }
9605         }
9606
9607         /* Print move */
9608         blank = linelen > 0 && movelen > 0;
9609         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9610             fprintf(f, "\n");
9611             linelen = 0;
9612             blank = 0;
9613         }
9614         if (blank) {
9615             fprintf(f, " ");
9616             linelen++;
9617         }
9618         fprintf(f, move_buffer);
9619         linelen += movelen;
9620
9621         /* [AS] Add PV info if present */
9622         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9623             /* [HGM] add time */
9624             char buf[MSG_SIZ]; int seconds = 0;
9625
9626 #if 1
9627             if(i >= backwardMostMove) {
9628                 if(WhiteOnMove(i))
9629                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9630                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9631                 else
9632                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9633                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9634             }
9635             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9636 #else
9637             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9638 #endif
9639
9640             if( seconds <= 0) buf[0] = 0; else
9641             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9642                 seconds = (seconds + 4)/10; // round to full seconds
9643                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9644                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9645             }
9646
9647             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9648                 pvInfoList[i].score >= 0 ? "+" : "",
9649                 pvInfoList[i].score / 100.0,
9650                 pvInfoList[i].depth,
9651                 buf );
9652
9653             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9654
9655             /* Print score/depth */
9656             blank = linelen > 0 && movelen > 0;
9657             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9658                 fprintf(f, "\n");
9659                 linelen = 0;
9660                 blank = 0;
9661             }
9662             if (blank) {
9663                 fprintf(f, " ");
9664                 linelen++;
9665             }
9666             fprintf(f, move_buffer);
9667             linelen += movelen;
9668         }
9669
9670         i++;
9671     }
9672     
9673     /* Start a new line */
9674     if (linelen > 0) fprintf(f, "\n");
9675
9676     /* Print comments after last move */
9677     if (commentList[i] != NULL) {
9678         fprintf(f, "{\n%s}\n", commentList[i]);
9679     }
9680
9681     /* Print result */
9682     if (gameInfo.resultDetails != NULL &&
9683         gameInfo.resultDetails[0] != NULLCHAR) {
9684         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9685                 PGNResult(gameInfo.result));
9686     } else {
9687         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9688     }
9689
9690     fclose(f);
9691     return TRUE;
9692 }
9693
9694 /* Save game in old style and close the file */
9695 int
9696 SaveGameOldStyle(f)
9697      FILE *f;
9698 {
9699     int i, offset;
9700     time_t tm;
9701     
9702     tm = time((time_t *) NULL);
9703     
9704     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9705     PrintOpponents(f);
9706     
9707     if (backwardMostMove > 0 || startedFromSetupPosition) {
9708         fprintf(f, "\n[--------------\n");
9709         PrintPosition(f, backwardMostMove);
9710         fprintf(f, "--------------]\n");
9711     } else {
9712         fprintf(f, "\n");
9713     }
9714
9715     i = backwardMostMove;
9716     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9717
9718     while (i < forwardMostMove) {
9719         if (commentList[i] != NULL) {
9720             fprintf(f, "[%s]\n", commentList[i]);
9721         }
9722
9723         if ((i % 2) == 1) {
9724             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9725             i++;
9726         } else {
9727             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9728             i++;
9729             if (commentList[i] != NULL) {
9730                 fprintf(f, "\n");
9731                 continue;
9732             }
9733             if (i >= forwardMostMove) {
9734                 fprintf(f, "\n");
9735                 break;
9736             }
9737             fprintf(f, "%s\n", parseList[i]);
9738             i++;
9739         }
9740     }
9741     
9742     if (commentList[i] != NULL) {
9743         fprintf(f, "[%s]\n", commentList[i]);
9744     }
9745
9746     /* This isn't really the old style, but it's close enough */
9747     if (gameInfo.resultDetails != NULL &&
9748         gameInfo.resultDetails[0] != NULLCHAR) {
9749         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9750                 gameInfo.resultDetails);
9751     } else {
9752         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9753     }
9754
9755     fclose(f);
9756     return TRUE;
9757 }
9758
9759 /* Save the current game to open file f and close the file */
9760 int
9761 SaveGame(f, dummy, dummy2)
9762      FILE *f;
9763      int dummy;
9764      char *dummy2;
9765 {
9766     if (gameMode == EditPosition) EditPositionDone();
9767     if (appData.oldSaveStyle)
9768       return SaveGameOldStyle(f);
9769     else
9770       return SaveGamePGN(f);
9771 }
9772
9773 /* Save the current position to the given file */
9774 int
9775 SavePositionToFile(filename)
9776      char *filename;
9777 {
9778     FILE *f;
9779     char buf[MSG_SIZ];
9780
9781     if (strcmp(filename, "-") == 0) {
9782         return SavePosition(stdout, 0, NULL);
9783     } else {
9784         f = fopen(filename, "a");
9785         if (f == NULL) {
9786             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9787             DisplayError(buf, errno);
9788             return FALSE;
9789         } else {
9790             SavePosition(f, 0, NULL);
9791             return TRUE;
9792         }
9793     }
9794 }
9795
9796 /* Save the current position to the given open file and close the file */
9797 int
9798 SavePosition(f, dummy, dummy2)
9799      FILE *f;
9800      int dummy;
9801      char *dummy2;
9802 {
9803     time_t tm;
9804     char *fen;
9805     
9806     if (appData.oldSaveStyle) {
9807         tm = time((time_t *) NULL);
9808     
9809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9810         PrintOpponents(f);
9811         fprintf(f, "[--------------\n");
9812         PrintPosition(f, currentMove);
9813         fprintf(f, "--------------]\n");
9814     } else {
9815         fen = PositionToFEN(currentMove, NULL);
9816         fprintf(f, "%s\n", fen);
9817         free(fen);
9818     }
9819     fclose(f);
9820     return TRUE;
9821 }
9822
9823 void
9824 ReloadCmailMsgEvent(unregister)
9825      int unregister;
9826 {
9827 #if !WIN32
9828     static char *inFilename = NULL;
9829     static char *outFilename;
9830     int i;
9831     struct stat inbuf, outbuf;
9832     int status;
9833     
9834     /* Any registered moves are unregistered if unregister is set, */
9835     /* i.e. invoked by the signal handler */
9836     if (unregister) {
9837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838             cmailMoveRegistered[i] = FALSE;
9839             if (cmailCommentList[i] != NULL) {
9840                 free(cmailCommentList[i]);
9841                 cmailCommentList[i] = NULL;
9842             }
9843         }
9844         nCmailMovesRegistered = 0;
9845     }
9846
9847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9848         cmailResult[i] = CMAIL_NOT_RESULT;
9849     }
9850     nCmailResults = 0;
9851
9852     if (inFilename == NULL) {
9853         /* Because the filenames are static they only get malloced once  */
9854         /* and they never get freed                                      */
9855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9857
9858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9859         sprintf(outFilename, "%s.out", appData.cmailGameName);
9860     }
9861     
9862     status = stat(outFilename, &outbuf);
9863     if (status < 0) {
9864         cmailMailedMove = FALSE;
9865     } else {
9866         status = stat(inFilename, &inbuf);
9867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9868     }
9869     
9870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9871        counts the games, notes how each one terminated, etc.
9872        
9873        It would be nice to remove this kludge and instead gather all
9874        the information while building the game list.  (And to keep it
9875        in the game list nodes instead of having a bunch of fixed-size
9876        parallel arrays.)  Note this will require getting each game's
9877        termination from the PGN tags, as the game list builder does
9878        not process the game moves.  --mann
9879        */
9880     cmailMsgLoaded = TRUE;
9881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9882     
9883     /* Load first game in the file or popup game menu */
9884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9885
9886 #endif /* !WIN32 */
9887     return;
9888 }
9889
9890 int
9891 RegisterMove()
9892 {
9893     FILE *f;
9894     char string[MSG_SIZ];
9895
9896     if (   cmailMailedMove
9897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9898         return TRUE;            /* Allow free viewing  */
9899     }
9900
9901     /* Unregister move to ensure that we don't leave RegisterMove        */
9902     /* with the move registered when the conditions for registering no   */
9903     /* longer hold                                                       */
9904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9906         nCmailMovesRegistered --;
9907
9908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9909           {
9910               free(cmailCommentList[lastLoadGameNumber - 1]);
9911               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9912           }
9913     }
9914
9915     if (cmailOldMove == -1) {
9916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9917         return FALSE;
9918     }
9919
9920     if (currentMove > cmailOldMove + 1) {
9921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9922         return FALSE;
9923     }
9924
9925     if (currentMove < cmailOldMove) {
9926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9927         return FALSE;
9928     }
9929
9930     if (forwardMostMove > currentMove) {
9931         /* Silently truncate extra moves */
9932         TruncateGame();
9933     }
9934
9935     if (   (currentMove == cmailOldMove + 1)
9936         || (   (currentMove == cmailOldMove)
9937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9939         if (gameInfo.result != GameUnfinished) {
9940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9941         }
9942
9943         if (commentList[currentMove] != NULL) {
9944             cmailCommentList[lastLoadGameNumber - 1]
9945               = StrSave(commentList[currentMove]);
9946         }
9947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9948
9949         if (appData.debugMode)
9950           fprintf(debugFP, "Saving %s for game %d\n",
9951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9952
9953         sprintf(string,
9954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9955         
9956         f = fopen(string, "w");
9957         if (appData.oldSaveStyle) {
9958             SaveGameOldStyle(f); /* also closes the file */
9959             
9960             sprintf(string, "%s.pos.out", appData.cmailGameName);
9961             f = fopen(string, "w");
9962             SavePosition(f, 0, NULL); /* also closes the file */
9963         } else {
9964             fprintf(f, "{--------------\n");
9965             PrintPosition(f, currentMove);
9966             fprintf(f, "--------------}\n\n");
9967             
9968             SaveGame(f, 0, NULL); /* also closes the file*/
9969         }
9970         
9971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9972         nCmailMovesRegistered ++;
9973     } else if (nCmailGames == 1) {
9974         DisplayError(_("You have not made a move yet"), 0);
9975         return FALSE;
9976     }
9977
9978     return TRUE;
9979 }
9980
9981 void
9982 MailMoveEvent()
9983 {
9984 #if !WIN32
9985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9986     FILE *commandOutput;
9987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9989     int nBuffers;
9990     int i;
9991     int archived;
9992     char *arcDir;
9993
9994     if (! cmailMsgLoaded) {
9995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9996         return;
9997     }
9998
9999     if (nCmailGames == nCmailResults) {
10000         DisplayError(_("No unfinished games"), 0);
10001         return;
10002     }
10003
10004 #if CMAIL_PROHIBIT_REMAIL
10005     if (cmailMailedMove) {
10006         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);
10007         DisplayError(msg, 0);
10008         return;
10009     }
10010 #endif
10011
10012     if (! (cmailMailedMove || RegisterMove())) return;
10013     
10014     if (   cmailMailedMove
10015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10016         sprintf(string, partCommandString,
10017                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10018         commandOutput = popen(string, "r");
10019
10020         if (commandOutput == NULL) {
10021             DisplayError(_("Failed to invoke cmail"), 0);
10022         } else {
10023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10025             }
10026             if (nBuffers > 1) {
10027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10029                 nBytes = MSG_SIZ - 1;
10030             } else {
10031                 (void) memcpy(msg, buffer, nBytes);
10032             }
10033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10034
10035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10037
10038                 archived = TRUE;
10039                 for (i = 0; i < nCmailGames; i ++) {
10040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10041                         archived = FALSE;
10042                     }
10043                 }
10044                 if (   archived
10045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10046                         != NULL)) {
10047                     sprintf(buffer, "%s/%s.%s.archive",
10048                             arcDir,
10049                             appData.cmailGameName,
10050                             gameInfo.date);
10051                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10052                     cmailMsgLoaded = FALSE;
10053                 }
10054             }
10055
10056             DisplayInformation(msg);
10057             pclose(commandOutput);
10058         }
10059     } else {
10060         if ((*cmailMsg) != '\0') {
10061             DisplayInformation(cmailMsg);
10062         }
10063     }
10064
10065     return;
10066 #endif /* !WIN32 */
10067 }
10068
10069 char *
10070 CmailMsg()
10071 {
10072 #if WIN32
10073     return NULL;
10074 #else
10075     int  prependComma = 0;
10076     char number[5];
10077     char string[MSG_SIZ];       /* Space for game-list */
10078     int  i;
10079     
10080     if (!cmailMsgLoaded) return "";
10081
10082     if (cmailMailedMove) {
10083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10084     } else {
10085         /* Create a list of games left */
10086         sprintf(string, "[");
10087         for (i = 0; i < nCmailGames; i ++) {
10088             if (! (   cmailMoveRegistered[i]
10089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10090                 if (prependComma) {
10091                     sprintf(number, ",%d", i + 1);
10092                 } else {
10093                     sprintf(number, "%d", i + 1);
10094                     prependComma = 1;
10095                 }
10096                 
10097                 strcat(string, number);
10098             }
10099         }
10100         strcat(string, "]");
10101
10102         if (nCmailMovesRegistered + nCmailResults == 0) {
10103             switch (nCmailGames) {
10104               case 1:
10105                 sprintf(cmailMsg,
10106                         _("Still need to make move for game\n"));
10107                 break;
10108                 
10109               case 2:
10110                 sprintf(cmailMsg,
10111                         _("Still need to make moves for both games\n"));
10112                 break;
10113                 
10114               default:
10115                 sprintf(cmailMsg,
10116                         _("Still need to make moves for all %d games\n"),
10117                         nCmailGames);
10118                 break;
10119             }
10120         } else {
10121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10122               case 1:
10123                 sprintf(cmailMsg,
10124                         _("Still need to make a move for game %s\n"),
10125                         string);
10126                 break;
10127                 
10128               case 0:
10129                 if (nCmailResults == nCmailGames) {
10130                     sprintf(cmailMsg, _("No unfinished games\n"));
10131                 } else {
10132                     sprintf(cmailMsg, _("Ready to send mail\n"));
10133                 }
10134                 break;
10135                 
10136               default:
10137                 sprintf(cmailMsg,
10138                         _("Still need to make moves for games %s\n"),
10139                         string);
10140             }
10141         }
10142     }
10143     return cmailMsg;
10144 #endif /* WIN32 */
10145 }
10146
10147 void
10148 ResetGameEvent()
10149 {
10150     if (gameMode == Training)
10151       SetTrainingModeOff();
10152
10153     Reset(TRUE, TRUE);
10154     cmailMsgLoaded = FALSE;
10155     if (appData.icsActive) {
10156       SendToICS(ics_prefix);
10157       SendToICS("refresh\n");
10158     }
10159 }
10160
10161 void
10162 ExitEvent(status)
10163      int status;
10164 {
10165     exiting++;
10166     if (exiting > 2) {
10167       /* Give up on clean exit */
10168       exit(status);
10169     }
10170     if (exiting > 1) {
10171       /* Keep trying for clean exit */
10172       return;
10173     }
10174
10175     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10176
10177     if (telnetISR != NULL) {
10178       RemoveInputSource(telnetISR);
10179     }
10180     if (icsPR != NoProc) {
10181       DestroyChildProcess(icsPR, TRUE);
10182     }
10183 #if 0
10184     /* Save game if resource set and not already saved by GameEnds() */
10185     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10186                              && forwardMostMove > 0) {
10187       if (*appData.saveGameFile != NULLCHAR) {
10188         SaveGameToFile(appData.saveGameFile, TRUE);
10189       } else if (appData.autoSaveGames) {
10190         AutoSaveGame();
10191       }
10192       if (*appData.savePositionFile != NULLCHAR) {
10193         SavePositionToFile(appData.savePositionFile);
10194       }
10195     }
10196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10197 #else
10198     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10199     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10200 #endif
10201     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10202     /* make sure this other one finishes before killing it!                  */
10203     if(endingGame) { int count = 0;
10204         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10205         while(endingGame && count++ < 10) DoSleep(1);
10206         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10207     }
10208
10209     /* Kill off chess programs */
10210     if (first.pr != NoProc) {
10211         ExitAnalyzeMode();
10212         
10213         DoSleep( appData.delayBeforeQuit );
10214         SendToProgram("quit\n", &first);
10215         DoSleep( appData.delayAfterQuit );
10216         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10217     }
10218     if (second.pr != NoProc) {
10219         DoSleep( appData.delayBeforeQuit );
10220         SendToProgram("quit\n", &second);
10221         DoSleep( appData.delayAfterQuit );
10222         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10223     }
10224     if (first.isr != NULL) {
10225         RemoveInputSource(first.isr);
10226     }
10227     if (second.isr != NULL) {
10228         RemoveInputSource(second.isr);
10229     }
10230
10231     ShutDownFrontEnd();
10232     exit(status);
10233 }
10234
10235 void
10236 PauseEvent()
10237 {
10238     if (appData.debugMode)
10239         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10240     if (pausing) {
10241         pausing = FALSE;
10242         ModeHighlight();
10243         if (gameMode == MachinePlaysWhite ||
10244             gameMode == MachinePlaysBlack) {
10245             StartClocks();
10246         } else {
10247             DisplayBothClocks();
10248         }
10249         if (gameMode == PlayFromGameFile) {
10250             if (appData.timeDelay >= 0) 
10251                 AutoPlayGameLoop();
10252         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10253             Reset(FALSE, TRUE);
10254             SendToICS(ics_prefix);
10255             SendToICS("refresh\n");
10256         } else if (currentMove < forwardMostMove) {
10257             ForwardInner(forwardMostMove);
10258         }
10259         pauseExamInvalid = FALSE;
10260     } else {
10261         switch (gameMode) {
10262           default:
10263             return;
10264           case IcsExamining:
10265             pauseExamForwardMostMove = forwardMostMove;
10266             pauseExamInvalid = FALSE;
10267             /* fall through */
10268           case IcsObserving:
10269           case IcsPlayingWhite:
10270           case IcsPlayingBlack:
10271             pausing = TRUE;
10272             ModeHighlight();
10273             return;
10274           case PlayFromGameFile:
10275             (void) StopLoadGameTimer();
10276             pausing = TRUE;
10277             ModeHighlight();
10278             break;
10279           case BeginningOfGame:
10280             if (appData.icsActive) return;
10281             /* else fall through */
10282           case MachinePlaysWhite:
10283           case MachinePlaysBlack:
10284           case TwoMachinesPlay:
10285             if (forwardMostMove == 0)
10286               return;           /* don't pause if no one has moved */
10287             if ((gameMode == MachinePlaysWhite &&
10288                  !WhiteOnMove(forwardMostMove)) ||
10289                 (gameMode == MachinePlaysBlack &&
10290                  WhiteOnMove(forwardMostMove))) {
10291                 StopClocks();
10292             }
10293             pausing = TRUE;
10294             ModeHighlight();
10295             break;
10296         }
10297     }
10298 }
10299
10300 void
10301 EditCommentEvent()
10302 {
10303     char title[MSG_SIZ];
10304
10305     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10306         strcpy(title, _("Edit comment"));
10307     } else {
10308         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10309                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10310                 parseList[currentMove - 1]);
10311     }
10312
10313     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10314 }
10315
10316
10317 void
10318 EditTagsEvent()
10319 {
10320     char *tags = PGNTags(&gameInfo);
10321     EditTagsPopUp(tags);
10322     free(tags);
10323 }
10324
10325 void
10326 AnalyzeModeEvent()
10327 {
10328     if (appData.noChessProgram || gameMode == AnalyzeMode)
10329       return;
10330
10331     if (gameMode != AnalyzeFile) {
10332         if (!appData.icsEngineAnalyze) {
10333                EditGameEvent();
10334                if (gameMode != EditGame) return;
10335         }
10336         ResurrectChessProgram();
10337         SendToProgram("analyze\n", &first);
10338         first.analyzing = TRUE;
10339         /*first.maybeThinking = TRUE;*/
10340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10341         AnalysisPopUp(_("Analysis"),
10342                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10343     }
10344     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10345     pausing = FALSE;
10346     ModeHighlight();
10347     SetGameInfo();
10348
10349     StartAnalysisClock();
10350     GetTimeMark(&lastNodeCountTime);
10351     lastNodeCount = 0;
10352 }
10353
10354 void
10355 AnalyzeFileEvent()
10356 {
10357     if (appData.noChessProgram || gameMode == AnalyzeFile)
10358       return;
10359
10360     if (gameMode != AnalyzeMode) {
10361         EditGameEvent();
10362         if (gameMode != EditGame) return;
10363         ResurrectChessProgram();
10364         SendToProgram("analyze\n", &first);
10365         first.analyzing = TRUE;
10366         /*first.maybeThinking = TRUE;*/
10367         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10368         AnalysisPopUp(_("Analysis"),
10369                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10370     }
10371     gameMode = AnalyzeFile;
10372     pausing = FALSE;
10373     ModeHighlight();
10374     SetGameInfo();
10375
10376     StartAnalysisClock();
10377     GetTimeMark(&lastNodeCountTime);
10378     lastNodeCount = 0;
10379 }
10380
10381 void
10382 MachineWhiteEvent()
10383 {
10384     char buf[MSG_SIZ];
10385     char *bookHit = NULL;
10386
10387     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10388       return;
10389
10390
10391     if (gameMode == PlayFromGameFile || 
10392         gameMode == TwoMachinesPlay  || 
10393         gameMode == Training         || 
10394         gameMode == AnalyzeMode      || 
10395         gameMode == EndOfGame)
10396         EditGameEvent();
10397
10398     if (gameMode == EditPosition) 
10399         EditPositionDone();
10400
10401     if (!WhiteOnMove(currentMove)) {
10402         DisplayError(_("It is not White's turn"), 0);
10403         return;
10404     }
10405   
10406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10407       ExitAnalyzeMode();
10408
10409     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10410         gameMode == AnalyzeFile)
10411         TruncateGame();
10412
10413     ResurrectChessProgram();    /* in case it isn't running */
10414     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10415         gameMode = MachinePlaysWhite;
10416         ResetClocks();
10417     } else
10418     gameMode = MachinePlaysWhite;
10419     pausing = FALSE;
10420     ModeHighlight();
10421     SetGameInfo();
10422     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10423     DisplayTitle(buf);
10424     if (first.sendName) {
10425       sprintf(buf, "name %s\n", gameInfo.black);
10426       SendToProgram(buf, &first);
10427     }
10428     if (first.sendTime) {
10429       if (first.useColors) {
10430         SendToProgram("black\n", &first); /*gnu kludge*/
10431       }
10432       SendTimeRemaining(&first, TRUE);
10433     }
10434     if (first.useColors) {
10435       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10436     }
10437     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10438     SetMachineThinkingEnables();
10439     first.maybeThinking = TRUE;
10440     StartClocks();
10441
10442     if (appData.autoFlipView && !flipView) {
10443       flipView = !flipView;
10444       DrawPosition(FALSE, NULL);
10445       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10446     }
10447
10448     if(bookHit) { // [HGM] book: simulate book reply
10449         static char bookMove[MSG_SIZ]; // a bit generous?
10450
10451         programStats.nodes = programStats.depth = programStats.time = 
10452         programStats.score = programStats.got_only_move = 0;
10453         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10454
10455         strcpy(bookMove, "move ");
10456         strcat(bookMove, bookHit);
10457         HandleMachineMove(bookMove, &first);
10458     }
10459 }
10460
10461 void
10462 MachineBlackEvent()
10463 {
10464     char buf[MSG_SIZ];
10465    char *bookHit = NULL;
10466
10467     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10468         return;
10469
10470
10471     if (gameMode == PlayFromGameFile || 
10472         gameMode == TwoMachinesPlay  || 
10473         gameMode == Training         || 
10474         gameMode == AnalyzeMode      || 
10475         gameMode == EndOfGame)
10476         EditGameEvent();
10477
10478     if (gameMode == EditPosition) 
10479         EditPositionDone();
10480
10481     if (WhiteOnMove(currentMove)) {
10482         DisplayError(_("It is not Black's turn"), 0);
10483         return;
10484     }
10485     
10486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10487       ExitAnalyzeMode();
10488
10489     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10490         gameMode == AnalyzeFile)
10491         TruncateGame();
10492
10493     ResurrectChessProgram();    /* in case it isn't running */
10494     gameMode = MachinePlaysBlack;
10495     pausing = FALSE;
10496     ModeHighlight();
10497     SetGameInfo();
10498     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10499     DisplayTitle(buf);
10500     if (first.sendName) {
10501       sprintf(buf, "name %s\n", gameInfo.white);
10502       SendToProgram(buf, &first);
10503     }
10504     if (first.sendTime) {
10505       if (first.useColors) {
10506         SendToProgram("white\n", &first); /*gnu kludge*/
10507       }
10508       SendTimeRemaining(&first, FALSE);
10509     }
10510     if (first.useColors) {
10511       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10512     }
10513     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10514     SetMachineThinkingEnables();
10515     first.maybeThinking = TRUE;
10516     StartClocks();
10517
10518     if (appData.autoFlipView && flipView) {
10519       flipView = !flipView;
10520       DrawPosition(FALSE, NULL);
10521       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10522     }
10523     if(bookHit) { // [HGM] book: simulate book reply
10524         static char bookMove[MSG_SIZ]; // a bit generous?
10525
10526         programStats.nodes = programStats.depth = programStats.time = 
10527         programStats.score = programStats.got_only_move = 0;
10528         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10529
10530         strcpy(bookMove, "move ");
10531         strcat(bookMove, bookHit);
10532         HandleMachineMove(bookMove, &first);
10533     }
10534 }
10535
10536
10537 void
10538 DisplayTwoMachinesTitle()
10539 {
10540     char buf[MSG_SIZ];
10541     if (appData.matchGames > 0) {
10542         if (first.twoMachinesColor[0] == 'w') {
10543             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10544                     gameInfo.white, gameInfo.black,
10545                     first.matchWins, second.matchWins,
10546                     matchGame - 1 - (first.matchWins + second.matchWins));
10547         } else {
10548             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10549                     gameInfo.white, gameInfo.black,
10550                     second.matchWins, first.matchWins,
10551                     matchGame - 1 - (first.matchWins + second.matchWins));
10552         }
10553     } else {
10554         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10555     }
10556     DisplayTitle(buf);
10557 }
10558
10559 void
10560 TwoMachinesEvent P((void))
10561 {
10562     int i;
10563     char buf[MSG_SIZ];
10564     ChessProgramState *onmove;
10565     char *bookHit = NULL;
10566     
10567     if (appData.noChessProgram) return;
10568
10569     switch (gameMode) {
10570       case TwoMachinesPlay:
10571         return;
10572       case MachinePlaysWhite:
10573       case MachinePlaysBlack:
10574         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10575             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10576             return;
10577         }
10578         /* fall through */
10579       case BeginningOfGame:
10580       case PlayFromGameFile:
10581       case EndOfGame:
10582         EditGameEvent();
10583         if (gameMode != EditGame) return;
10584         break;
10585       case EditPosition:
10586         EditPositionDone();
10587         break;
10588       case AnalyzeMode:
10589       case AnalyzeFile:
10590         ExitAnalyzeMode();
10591         break;
10592       case EditGame:
10593       default:
10594         break;
10595     }
10596
10597     forwardMostMove = currentMove;
10598     ResurrectChessProgram();    /* in case first program isn't running */
10599
10600     if (second.pr == NULL) {
10601         StartChessProgram(&second);
10602         if (second.protocolVersion == 1) {
10603           TwoMachinesEventIfReady();
10604         } else {
10605           /* kludge: allow timeout for initial "feature" command */
10606           FreezeUI();
10607           DisplayMessage("", _("Starting second chess program"));
10608           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10609         }
10610         return;
10611     }
10612     DisplayMessage("", "");
10613     InitChessProgram(&second, FALSE);
10614     SendToProgram("force\n", &second);
10615     if (startedFromSetupPosition) {
10616         SendBoard(&second, backwardMostMove);
10617     if (appData.debugMode) {
10618         fprintf(debugFP, "Two Machines\n");
10619     }
10620     }
10621     for (i = backwardMostMove; i < forwardMostMove; i++) {
10622         SendMoveToProgram(i, &second);
10623     }
10624
10625     gameMode = TwoMachinesPlay;
10626     pausing = FALSE;
10627     ModeHighlight();
10628     SetGameInfo();
10629     DisplayTwoMachinesTitle();
10630     firstMove = TRUE;
10631     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10632         onmove = &first;
10633     } else {
10634         onmove = &second;
10635     }
10636
10637     SendToProgram(first.computerString, &first);
10638     if (first.sendName) {
10639       sprintf(buf, "name %s\n", second.tidy);
10640       SendToProgram(buf, &first);
10641     }
10642     SendToProgram(second.computerString, &second);
10643     if (second.sendName) {
10644       sprintf(buf, "name %s\n", first.tidy);
10645       SendToProgram(buf, &second);
10646     }
10647
10648     ResetClocks();
10649     if (!first.sendTime || !second.sendTime) {
10650         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10651         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10652     }
10653     if (onmove->sendTime) {
10654       if (onmove->useColors) {
10655         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10656       }
10657       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10658     }
10659     if (onmove->useColors) {
10660       SendToProgram(onmove->twoMachinesColor, onmove);
10661     }
10662     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10663 //    SendToProgram("go\n", onmove);
10664     onmove->maybeThinking = TRUE;
10665     SetMachineThinkingEnables();
10666
10667     StartClocks();
10668
10669     if(bookHit) { // [HGM] book: simulate book reply
10670         static char bookMove[MSG_SIZ]; // a bit generous?
10671
10672         programStats.nodes = programStats.depth = programStats.time = 
10673         programStats.score = programStats.got_only_move = 0;
10674         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10675
10676         strcpy(bookMove, "move ");
10677         strcat(bookMove, bookHit);
10678         HandleMachineMove(bookMove, &first);
10679     }
10680 }
10681
10682 void
10683 TrainingEvent()
10684 {
10685     if (gameMode == Training) {
10686       SetTrainingModeOff();
10687       gameMode = PlayFromGameFile;
10688       DisplayMessage("", _("Training mode off"));
10689     } else {
10690       gameMode = Training;
10691       animateTraining = appData.animate;
10692
10693       /* make sure we are not already at the end of the game */
10694       if (currentMove < forwardMostMove) {
10695         SetTrainingModeOn();
10696         DisplayMessage("", _("Training mode on"));
10697       } else {
10698         gameMode = PlayFromGameFile;
10699         DisplayError(_("Already at end of game"), 0);
10700       }
10701     }
10702     ModeHighlight();
10703 }
10704
10705 void
10706 IcsClientEvent()
10707 {
10708     if (!appData.icsActive) return;
10709     switch (gameMode) {
10710       case IcsPlayingWhite:
10711       case IcsPlayingBlack:
10712       case IcsObserving:
10713       case IcsIdle:
10714       case BeginningOfGame:
10715       case IcsExamining:
10716         return;
10717
10718       case EditGame:
10719         break;
10720
10721       case EditPosition:
10722         EditPositionDone();
10723         break;
10724
10725       case AnalyzeMode:
10726       case AnalyzeFile:
10727         ExitAnalyzeMode();
10728         break;
10729         
10730       default:
10731         EditGameEvent();
10732         break;
10733     }
10734
10735     gameMode = IcsIdle;
10736     ModeHighlight();
10737     return;
10738 }
10739
10740
10741 void
10742 EditGameEvent()
10743 {
10744     int i;
10745
10746     switch (gameMode) {
10747       case Training:
10748         SetTrainingModeOff();
10749         break;
10750       case MachinePlaysWhite:
10751       case MachinePlaysBlack:
10752       case BeginningOfGame:
10753         SendToProgram("force\n", &first);
10754         SetUserThinkingEnables();
10755         break;
10756       case PlayFromGameFile:
10757         (void) StopLoadGameTimer();
10758         if (gameFileFP != NULL) {
10759             gameFileFP = NULL;
10760         }
10761         break;
10762       case EditPosition:
10763         EditPositionDone();
10764         break;
10765       case AnalyzeMode:
10766       case AnalyzeFile:
10767         ExitAnalyzeMode();
10768         SendToProgram("force\n", &first);
10769         break;
10770       case TwoMachinesPlay:
10771         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10772         ResurrectChessProgram();
10773         SetUserThinkingEnables();
10774         break;
10775       case EndOfGame:
10776         ResurrectChessProgram();
10777         break;
10778       case IcsPlayingBlack:
10779       case IcsPlayingWhite:
10780         DisplayError(_("Warning: You are still playing a game"), 0);
10781         break;
10782       case IcsObserving:
10783         DisplayError(_("Warning: You are still observing a game"), 0);
10784         break;
10785       case IcsExamining:
10786         DisplayError(_("Warning: You are still examining a game"), 0);
10787         break;
10788       case IcsIdle:
10789         break;
10790       case EditGame:
10791       default:
10792         return;
10793     }
10794     
10795     pausing = FALSE;
10796     StopClocks();
10797     first.offeredDraw = second.offeredDraw = 0;
10798
10799     if (gameMode == PlayFromGameFile) {
10800         whiteTimeRemaining = timeRemaining[0][currentMove];
10801         blackTimeRemaining = timeRemaining[1][currentMove];
10802         DisplayTitle("");
10803     }
10804
10805     if (gameMode == MachinePlaysWhite ||
10806         gameMode == MachinePlaysBlack ||
10807         gameMode == TwoMachinesPlay ||
10808         gameMode == EndOfGame) {
10809         i = forwardMostMove;
10810         while (i > currentMove) {
10811             SendToProgram("undo\n", &first);
10812             i--;
10813         }
10814         whiteTimeRemaining = timeRemaining[0][currentMove];
10815         blackTimeRemaining = timeRemaining[1][currentMove];
10816         DisplayBothClocks();
10817         if (whiteFlag || blackFlag) {
10818             whiteFlag = blackFlag = 0;
10819         }
10820         DisplayTitle("");
10821     }           
10822     
10823     gameMode = EditGame;
10824     ModeHighlight();
10825     SetGameInfo();
10826 }
10827
10828
10829 void
10830 EditPositionEvent()
10831 {
10832     if (gameMode == EditPosition) {
10833         EditGameEvent();
10834         return;
10835     }
10836     
10837     EditGameEvent();
10838     if (gameMode != EditGame) return;
10839     
10840     gameMode = EditPosition;
10841     ModeHighlight();
10842     SetGameInfo();
10843     if (currentMove > 0)
10844       CopyBoard(boards[0], boards[currentMove]);
10845     
10846     blackPlaysFirst = !WhiteOnMove(currentMove);
10847     ResetClocks();
10848     currentMove = forwardMostMove = backwardMostMove = 0;
10849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10850     DisplayMove(-1);
10851 }
10852
10853 void
10854 ExitAnalyzeMode()
10855 {
10856     /* [DM] icsEngineAnalyze - possible call from other functions */
10857     if (appData.icsEngineAnalyze) {
10858         appData.icsEngineAnalyze = FALSE;
10859
10860         DisplayMessage("",_("Close ICS engine analyze..."));
10861     }
10862     if (first.analysisSupport && first.analyzing) {
10863       SendToProgram("exit\n", &first);
10864       first.analyzing = FALSE;
10865     }
10866     AnalysisPopDown();
10867     thinkOutput[0] = NULLCHAR;
10868 }
10869
10870 void
10871 EditPositionDone()
10872 {
10873     startedFromSetupPosition = TRUE;
10874     InitChessProgram(&first, FALSE);
10875     SendToProgram("force\n", &first);
10876     if (blackPlaysFirst) {
10877         strcpy(moveList[0], "");
10878         strcpy(parseList[0], "");
10879         currentMove = forwardMostMove = backwardMostMove = 1;
10880         CopyBoard(boards[1], boards[0]);
10881         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10882         { int i;
10883           epStatus[1] = epStatus[0];
10884           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10885         }
10886     } else {
10887         currentMove = forwardMostMove = backwardMostMove = 0;
10888     }
10889     SendBoard(&first, forwardMostMove);
10890     if (appData.debugMode) {
10891         fprintf(debugFP, "EditPosDone\n");
10892     }
10893     DisplayTitle("");
10894     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10895     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10896     gameMode = EditGame;
10897     ModeHighlight();
10898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899     ClearHighlights(); /* [AS] */
10900 }
10901
10902 /* Pause for `ms' milliseconds */
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10904 void
10905 TimeDelay(ms)
10906      long ms;
10907 {
10908     TimeMark m1, m2;
10909
10910     GetTimeMark(&m1);
10911     do {
10912         GetTimeMark(&m2);
10913     } while (SubtractTimeMarks(&m2, &m1) < ms);
10914 }
10915
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10917 void
10918 SendMultiLineToICS(buf)
10919      char *buf;
10920 {
10921     char temp[MSG_SIZ+1], *p;
10922     int len;
10923
10924     len = strlen(buf);
10925     if (len > MSG_SIZ)
10926       len = MSG_SIZ;
10927   
10928     strncpy(temp, buf, len);
10929     temp[len] = 0;
10930
10931     p = temp;
10932     while (*p) {
10933         if (*p == '\n' || *p == '\r')
10934           *p = ' ';
10935         ++p;
10936     }
10937
10938     strcat(temp, "\n");
10939     SendToICS(temp);
10940     SendToPlayer(temp, strlen(temp));
10941 }
10942
10943 void
10944 SetWhiteToPlayEvent()
10945 {
10946     if (gameMode == EditPosition) {
10947         blackPlaysFirst = FALSE;
10948         DisplayBothClocks();    /* works because currentMove is 0 */
10949     } else if (gameMode == IcsExamining) {
10950         SendToICS(ics_prefix);
10951         SendToICS("tomove white\n");
10952     }
10953 }
10954
10955 void
10956 SetBlackToPlayEvent()
10957 {
10958     if (gameMode == EditPosition) {
10959         blackPlaysFirst = TRUE;
10960         currentMove = 1;        /* kludge */
10961         DisplayBothClocks();
10962         currentMove = 0;
10963     } else if (gameMode == IcsExamining) {
10964         SendToICS(ics_prefix);
10965         SendToICS("tomove black\n");
10966     }
10967 }
10968
10969 void
10970 EditPositionMenuEvent(selection, x, y)
10971      ChessSquare selection;
10972      int x, y;
10973 {
10974     char buf[MSG_SIZ];
10975     ChessSquare piece = boards[0][y][x];
10976
10977     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10978
10979     switch (selection) {
10980       case ClearBoard:
10981         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10982             SendToICS(ics_prefix);
10983             SendToICS("bsetup clear\n");
10984         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10985             SendToICS(ics_prefix);
10986             SendToICS("clearboard\n");
10987         } else {
10988             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10989                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10990                 for (y = 0; y < BOARD_HEIGHT; y++) {
10991                     if (gameMode == IcsExamining) {
10992                         if (boards[currentMove][y][x] != EmptySquare) {
10993                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10994                                     AAA + x, ONE + y);
10995                             SendToICS(buf);
10996                         }
10997                     } else {
10998                         boards[0][y][x] = p;
10999                     }
11000                 }
11001             }
11002         }
11003         if (gameMode == EditPosition) {
11004             DrawPosition(FALSE, boards[0]);
11005         }
11006         break;
11007
11008       case WhitePlay:
11009         SetWhiteToPlayEvent();
11010         break;
11011
11012       case BlackPlay:
11013         SetBlackToPlayEvent();
11014         break;
11015
11016       case EmptySquare:
11017         if (gameMode == IcsExamining) {
11018             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11019             SendToICS(buf);
11020         } else {
11021             boards[0][y][x] = EmptySquare;
11022             DrawPosition(FALSE, boards[0]);
11023         }
11024         break;
11025
11026       case PromotePiece:
11027         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11028            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11029             selection = (ChessSquare) (PROMOTED piece);
11030         } else if(piece == EmptySquare) selection = WhiteSilver;
11031         else selection = (ChessSquare)((int)piece - 1);
11032         goto defaultlabel;
11033
11034       case DemotePiece:
11035         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11036            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11037             selection = (ChessSquare) (DEMOTED piece);
11038         } else if(piece == EmptySquare) selection = BlackSilver;
11039         else selection = (ChessSquare)((int)piece + 1);       
11040         goto defaultlabel;
11041
11042       case WhiteQueen:
11043       case BlackQueen:
11044         if(gameInfo.variant == VariantShatranj ||
11045            gameInfo.variant == VariantXiangqi  ||
11046            gameInfo.variant == VariantCourier    )
11047             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11048         goto defaultlabel;
11049
11050       case WhiteKing:
11051       case BlackKing:
11052         if(gameInfo.variant == VariantXiangqi)
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11054         if(gameInfo.variant == VariantKnightmate)
11055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11056       default:
11057         defaultlabel:
11058         if (gameMode == IcsExamining) {
11059             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11060                     PieceToChar(selection), AAA + x, ONE + y);
11061             SendToICS(buf);
11062         } else {
11063             boards[0][y][x] = selection;
11064             DrawPosition(FALSE, boards[0]);
11065         }
11066         break;
11067     }
11068 }
11069
11070
11071 void
11072 DropMenuEvent(selection, x, y)
11073      ChessSquare selection;
11074      int x, y;
11075 {
11076     ChessMove moveType;
11077
11078     switch (gameMode) {
11079       case IcsPlayingWhite:
11080       case MachinePlaysBlack:
11081         if (!WhiteOnMove(currentMove)) {
11082             DisplayMoveError(_("It is Black's turn"));
11083             return;
11084         }
11085         moveType = WhiteDrop;
11086         break;
11087       case IcsPlayingBlack:
11088       case MachinePlaysWhite:
11089         if (WhiteOnMove(currentMove)) {
11090             DisplayMoveError(_("It is White's turn"));
11091             return;
11092         }
11093         moveType = BlackDrop;
11094         break;
11095       case EditGame:
11096         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11097         break;
11098       default:
11099         return;
11100     }
11101
11102     if (moveType == BlackDrop && selection < BlackPawn) {
11103       selection = (ChessSquare) ((int) selection
11104                                  + (int) BlackPawn - (int) WhitePawn);
11105     }
11106     if (boards[currentMove][y][x] != EmptySquare) {
11107         DisplayMoveError(_("That square is occupied"));
11108         return;
11109     }
11110
11111     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11112 }
11113
11114 void
11115 AcceptEvent()
11116 {
11117     /* Accept a pending offer of any kind from opponent */
11118     
11119     if (appData.icsActive) {
11120         SendToICS(ics_prefix);
11121         SendToICS("accept\n");
11122     } else if (cmailMsgLoaded) {
11123         if (currentMove == cmailOldMove &&
11124             commentList[cmailOldMove] != NULL &&
11125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11126                    "Black offers a draw" : "White offers a draw")) {
11127             TruncateGame();
11128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11130         } else {
11131             DisplayError(_("There is no pending offer on this move"), 0);
11132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11133         }
11134     } else {
11135         /* Not used for offers from chess program */
11136     }
11137 }
11138
11139 void
11140 DeclineEvent()
11141 {
11142     /* Decline a pending offer of any kind from opponent */
11143     
11144     if (appData.icsActive) {
11145         SendToICS(ics_prefix);
11146         SendToICS("decline\n");
11147     } else if (cmailMsgLoaded) {
11148         if (currentMove == cmailOldMove &&
11149             commentList[cmailOldMove] != NULL &&
11150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11151                    "Black offers a draw" : "White offers a draw")) {
11152 #ifdef NOTDEF
11153             AppendComment(cmailOldMove, "Draw declined");
11154             DisplayComment(cmailOldMove - 1, "Draw declined");
11155 #endif /*NOTDEF*/
11156         } else {
11157             DisplayError(_("There is no pending offer on this move"), 0);
11158         }
11159     } else {
11160         /* Not used for offers from chess program */
11161     }
11162 }
11163
11164 void
11165 RematchEvent()
11166 {
11167     /* Issue ICS rematch command */
11168     if (appData.icsActive) {
11169         SendToICS(ics_prefix);
11170         SendToICS("rematch\n");
11171     }
11172 }
11173
11174 void
11175 CallFlagEvent()
11176 {
11177     /* Call your opponent's flag (claim a win on time) */
11178     if (appData.icsActive) {
11179         SendToICS(ics_prefix);
11180         SendToICS("flag\n");
11181     } else {
11182         switch (gameMode) {
11183           default:
11184             return;
11185           case MachinePlaysWhite:
11186             if (whiteFlag) {
11187                 if (blackFlag)
11188                   GameEnds(GameIsDrawn, "Both players ran out of time",
11189                            GE_PLAYER);
11190                 else
11191                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11192             } else {
11193                 DisplayError(_("Your opponent is not out of time"), 0);
11194             }
11195             break;
11196           case MachinePlaysBlack:
11197             if (blackFlag) {
11198                 if (whiteFlag)
11199                   GameEnds(GameIsDrawn, "Both players ran out of time",
11200                            GE_PLAYER);
11201                 else
11202                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11203             } else {
11204                 DisplayError(_("Your opponent is not out of time"), 0);
11205             }
11206             break;
11207         }
11208     }
11209 }
11210
11211 void
11212 DrawEvent()
11213 {
11214     /* Offer draw or accept pending draw offer from opponent */
11215     
11216     if (appData.icsActive) {
11217         /* Note: tournament rules require draw offers to be
11218            made after you make your move but before you punch
11219            your clock.  Currently ICS doesn't let you do that;
11220            instead, you immediately punch your clock after making
11221            a move, but you can offer a draw at any time. */
11222         
11223         SendToICS(ics_prefix);
11224         SendToICS("draw\n");
11225     } else if (cmailMsgLoaded) {
11226         if (currentMove == cmailOldMove &&
11227             commentList[cmailOldMove] != NULL &&
11228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229                    "Black offers a draw" : "White offers a draw")) {
11230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11232         } else if (currentMove == cmailOldMove + 1) {
11233             char *offer = WhiteOnMove(cmailOldMove) ?
11234               "White offers a draw" : "Black offers a draw";
11235             AppendComment(currentMove, offer);
11236             DisplayComment(currentMove - 1, offer);
11237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11238         } else {
11239             DisplayError(_("You must make your move before offering a draw"), 0);
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11241         }
11242     } else if (first.offeredDraw) {
11243         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11244     } else {
11245         if (first.sendDrawOffers) {
11246             SendToProgram("draw\n", &first);
11247             userOfferedDraw = TRUE;
11248         }
11249     }
11250 }
11251
11252 void
11253 AdjournEvent()
11254 {
11255     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11256     
11257     if (appData.icsActive) {
11258         SendToICS(ics_prefix);
11259         SendToICS("adjourn\n");
11260     } else {
11261         /* Currently GNU Chess doesn't offer or accept Adjourns */
11262     }
11263 }
11264
11265
11266 void
11267 AbortEvent()
11268 {
11269     /* Offer Abort or accept pending Abort offer from opponent */
11270     
11271     if (appData.icsActive) {
11272         SendToICS(ics_prefix);
11273         SendToICS("abort\n");
11274     } else {
11275         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11276     }
11277 }
11278
11279 void
11280 ResignEvent()
11281 {
11282     /* Resign.  You can do this even if it's not your turn. */
11283     
11284     if (appData.icsActive) {
11285         SendToICS(ics_prefix);
11286         SendToICS("resign\n");
11287     } else {
11288         switch (gameMode) {
11289           case MachinePlaysWhite:
11290             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11291             break;
11292           case MachinePlaysBlack:
11293             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11294             break;
11295           case EditGame:
11296             if (cmailMsgLoaded) {
11297                 TruncateGame();
11298                 if (WhiteOnMove(cmailOldMove)) {
11299                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11300                 } else {
11301                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11302                 }
11303                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11304             }
11305             break;
11306           default:
11307             break;
11308         }
11309     }
11310 }
11311
11312
11313 void
11314 StopObservingEvent()
11315 {
11316     /* Stop observing current games */
11317     SendToICS(ics_prefix);
11318     SendToICS("unobserve\n");
11319 }
11320
11321 void
11322 StopExaminingEvent()
11323 {
11324     /* Stop observing current game */
11325     SendToICS(ics_prefix);
11326     SendToICS("unexamine\n");
11327 }
11328
11329 void
11330 ForwardInner(target)
11331      int target;
11332 {
11333     int limit;
11334
11335     if (appData.debugMode)
11336         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11337                 target, currentMove, forwardMostMove);
11338
11339     if (gameMode == EditPosition)
11340       return;
11341
11342     if (gameMode == PlayFromGameFile && !pausing)
11343       PauseEvent();
11344     
11345     if (gameMode == IcsExamining && pausing)
11346       limit = pauseExamForwardMostMove;
11347     else
11348       limit = forwardMostMove;
11349     
11350     if (target > limit) target = limit;
11351
11352     if (target > 0 && moveList[target - 1][0]) {
11353         int fromX, fromY, toX, toY;
11354         toX = moveList[target - 1][2] - AAA;
11355         toY = moveList[target - 1][3] - ONE;
11356         if (moveList[target - 1][1] == '@') {
11357             if (appData.highlightLastMove) {
11358                 SetHighlights(-1, -1, toX, toY);
11359             }
11360         } else {
11361             fromX = moveList[target - 1][0] - AAA;
11362             fromY = moveList[target - 1][1] - ONE;
11363             if (target == currentMove + 1) {
11364                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11365             }
11366             if (appData.highlightLastMove) {
11367                 SetHighlights(fromX, fromY, toX, toY);
11368             }
11369         }
11370     }
11371     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11372         gameMode == Training || gameMode == PlayFromGameFile || 
11373         gameMode == AnalyzeFile) {
11374         while (currentMove < target) {
11375             SendMoveToProgram(currentMove++, &first);
11376         }
11377     } else {
11378         currentMove = target;
11379     }
11380     
11381     if (gameMode == EditGame || gameMode == EndOfGame) {
11382         whiteTimeRemaining = timeRemaining[0][currentMove];
11383         blackTimeRemaining = timeRemaining[1][currentMove];
11384     }
11385     DisplayBothClocks();
11386     DisplayMove(currentMove - 1);
11387     DrawPosition(FALSE, boards[currentMove]);
11388     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11389     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11390         DisplayComment(currentMove - 1, commentList[currentMove]);
11391     }
11392 }
11393
11394
11395 void
11396 ForwardEvent()
11397 {
11398     if (gameMode == IcsExamining && !pausing) {
11399         SendToICS(ics_prefix);
11400         SendToICS("forward\n");
11401     } else {
11402         ForwardInner(currentMove + 1);
11403     }
11404 }
11405
11406 void
11407 ToEndEvent()
11408 {
11409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11410         /* to optimze, we temporarily turn off analysis mode while we feed
11411          * the remaining moves to the engine. Otherwise we get analysis output
11412          * after each move.
11413          */ 
11414         if (first.analysisSupport) {
11415           SendToProgram("exit\nforce\n", &first);
11416           first.analyzing = FALSE;
11417         }
11418     }
11419         
11420     if (gameMode == IcsExamining && !pausing) {
11421         SendToICS(ics_prefix);
11422         SendToICS("forward 999999\n");
11423     } else {
11424         ForwardInner(forwardMostMove);
11425     }
11426
11427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11428         /* we have fed all the moves, so reactivate analysis mode */
11429         SendToProgram("analyze\n", &first);
11430         first.analyzing = TRUE;
11431         /*first.maybeThinking = TRUE;*/
11432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11433     }
11434 }
11435
11436 void
11437 BackwardInner(target)
11438      int target;
11439 {
11440     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11441
11442     if (appData.debugMode)
11443         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11444                 target, currentMove, forwardMostMove);
11445
11446     if (gameMode == EditPosition) return;
11447     if (currentMove <= backwardMostMove) {
11448         ClearHighlights();
11449         DrawPosition(full_redraw, boards[currentMove]);
11450         return;
11451     }
11452     if (gameMode == PlayFromGameFile && !pausing)
11453       PauseEvent();
11454     
11455     if (moveList[target][0]) {
11456         int fromX, fromY, toX, toY;
11457         toX = moveList[target][2] - AAA;
11458         toY = moveList[target][3] - ONE;
11459         if (moveList[target][1] == '@') {
11460             if (appData.highlightLastMove) {
11461                 SetHighlights(-1, -1, toX, toY);
11462             }
11463         } else {
11464             fromX = moveList[target][0] - AAA;
11465             fromY = moveList[target][1] - ONE;
11466             if (target == currentMove - 1) {
11467                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11468             }
11469             if (appData.highlightLastMove) {
11470                 SetHighlights(fromX, fromY, toX, toY);
11471             }
11472         }
11473     }
11474     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11475         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11476         while (currentMove > target) {
11477             SendToProgram("undo\n", &first);
11478             currentMove--;
11479         }
11480     } else {
11481         currentMove = target;
11482     }
11483     
11484     if (gameMode == EditGame || gameMode == EndOfGame) {
11485         whiteTimeRemaining = timeRemaining[0][currentMove];
11486         blackTimeRemaining = timeRemaining[1][currentMove];
11487     }
11488     DisplayBothClocks();
11489     DisplayMove(currentMove - 1);
11490     DrawPosition(full_redraw, boards[currentMove]);
11491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492     // [HGM] PV info: routine tests if comment empty
11493     DisplayComment(currentMove - 1, commentList[currentMove]);
11494 }
11495
11496 void
11497 BackwardEvent()
11498 {
11499     if (gameMode == IcsExamining && !pausing) {
11500         SendToICS(ics_prefix);
11501         SendToICS("backward\n");
11502     } else {
11503         BackwardInner(currentMove - 1);
11504     }
11505 }
11506
11507 void
11508 ToStartEvent()
11509 {
11510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11511         /* to optimze, we temporarily turn off analysis mode while we undo
11512          * all the moves. Otherwise we get analysis output after each undo.
11513          */ 
11514         if (first.analysisSupport) {
11515           SendToProgram("exit\nforce\n", &first);
11516           first.analyzing = FALSE;
11517         }
11518     }
11519
11520     if (gameMode == IcsExamining && !pausing) {
11521         SendToICS(ics_prefix);
11522         SendToICS("backward 999999\n");
11523     } else {
11524         BackwardInner(backwardMostMove);
11525     }
11526
11527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11528         /* we have fed all the moves, so reactivate analysis mode */
11529         SendToProgram("analyze\n", &first);
11530         first.analyzing = TRUE;
11531         /*first.maybeThinking = TRUE;*/
11532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11533     }
11534 }
11535
11536 void
11537 ToNrEvent(int to)
11538 {
11539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11540   if (to >= forwardMostMove) to = forwardMostMove;
11541   if (to <= backwardMostMove) to = backwardMostMove;
11542   if (to < currentMove) {
11543     BackwardInner(to);
11544   } else {
11545     ForwardInner(to);
11546   }
11547 }
11548
11549 void
11550 RevertEvent()
11551 {
11552     if (gameMode != IcsExamining) {
11553         DisplayError(_("You are not examining a game"), 0);
11554         return;
11555     }
11556     if (pausing) {
11557         DisplayError(_("You can't revert while pausing"), 0);
11558         return;
11559     }
11560     SendToICS(ics_prefix);
11561     SendToICS("revert\n");
11562 }
11563
11564 void
11565 RetractMoveEvent()
11566 {
11567     switch (gameMode) {
11568       case MachinePlaysWhite:
11569       case MachinePlaysBlack:
11570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11572             return;
11573         }
11574         if (forwardMostMove < 2) return;
11575         currentMove = forwardMostMove = forwardMostMove - 2;
11576         whiteTimeRemaining = timeRemaining[0][currentMove];
11577         blackTimeRemaining = timeRemaining[1][currentMove];
11578         DisplayBothClocks();
11579         DisplayMove(currentMove - 1);
11580         ClearHighlights();/*!! could figure this out*/
11581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11582         SendToProgram("remove\n", &first);
11583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11584         break;
11585
11586       case BeginningOfGame:
11587       default:
11588         break;
11589
11590       case IcsPlayingWhite:
11591       case IcsPlayingBlack:
11592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11593             SendToICS(ics_prefix);
11594             SendToICS("takeback 2\n");
11595         } else {
11596             SendToICS(ics_prefix);
11597             SendToICS("takeback 1\n");
11598         }
11599         break;
11600     }
11601 }
11602
11603 void
11604 MoveNowEvent()
11605 {
11606     ChessProgramState *cps;
11607
11608     switch (gameMode) {
11609       case MachinePlaysWhite:
11610         if (!WhiteOnMove(forwardMostMove)) {
11611             DisplayError(_("It is your turn"), 0);
11612             return;
11613         }
11614         cps = &first;
11615         break;
11616       case MachinePlaysBlack:
11617         if (WhiteOnMove(forwardMostMove)) {
11618             DisplayError(_("It is your turn"), 0);
11619             return;
11620         }
11621         cps = &first;
11622         break;
11623       case TwoMachinesPlay:
11624         if (WhiteOnMove(forwardMostMove) ==
11625             (first.twoMachinesColor[0] == 'w')) {
11626             cps = &first;
11627         } else {
11628             cps = &second;
11629         }
11630         break;
11631       case BeginningOfGame:
11632       default:
11633         return;
11634     }
11635     SendToProgram("?\n", cps);
11636 }
11637
11638 void
11639 TruncateGameEvent()
11640 {
11641     EditGameEvent();
11642     if (gameMode != EditGame) return;
11643     TruncateGame();
11644 }
11645
11646 void
11647 TruncateGame()
11648 {
11649     if (forwardMostMove > currentMove) {
11650         if (gameInfo.resultDetails != NULL) {
11651             free(gameInfo.resultDetails);
11652             gameInfo.resultDetails = NULL;
11653             gameInfo.result = GameUnfinished;
11654         }
11655         forwardMostMove = currentMove;
11656         HistorySet(parseList, backwardMostMove, forwardMostMove,
11657                    currentMove-1);
11658     }
11659 }
11660
11661 void
11662 HintEvent()
11663 {
11664     if (appData.noChessProgram) return;
11665     switch (gameMode) {
11666       case MachinePlaysWhite:
11667         if (WhiteOnMove(forwardMostMove)) {
11668             DisplayError(_("Wait until your turn"), 0);
11669             return;
11670         }
11671         break;
11672       case BeginningOfGame:
11673       case MachinePlaysBlack:
11674         if (!WhiteOnMove(forwardMostMove)) {
11675             DisplayError(_("Wait until your turn"), 0);
11676             return;
11677         }
11678         break;
11679       default:
11680         DisplayError(_("No hint available"), 0);
11681         return;
11682     }
11683     SendToProgram("hint\n", &first);
11684     hintRequested = TRUE;
11685 }
11686
11687 void
11688 BookEvent()
11689 {
11690     if (appData.noChessProgram) return;
11691     switch (gameMode) {
11692       case MachinePlaysWhite:
11693         if (WhiteOnMove(forwardMostMove)) {
11694             DisplayError(_("Wait until your turn"), 0);
11695             return;
11696         }
11697         break;
11698       case BeginningOfGame:
11699       case MachinePlaysBlack:
11700         if (!WhiteOnMove(forwardMostMove)) {
11701             DisplayError(_("Wait until your turn"), 0);
11702             return;
11703         }
11704         break;
11705       case EditPosition:
11706         EditPositionDone();
11707         break;
11708       case TwoMachinesPlay:
11709         return;
11710       default:
11711         break;
11712     }
11713     SendToProgram("bk\n", &first);
11714     bookOutput[0] = NULLCHAR;
11715     bookRequested = TRUE;
11716 }
11717
11718 void
11719 AboutGameEvent()
11720 {
11721     char *tags = PGNTags(&gameInfo);
11722     TagsPopUp(tags, CmailMsg());
11723     free(tags);
11724 }
11725
11726 /* end button procedures */
11727
11728 void
11729 PrintPosition(fp, move)
11730      FILE *fp;
11731      int move;
11732 {
11733     int i, j;
11734     
11735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11736         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11737             char c = PieceToChar(boards[move][i][j]);
11738             fputc(c == 'x' ? '.' : c, fp);
11739             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11740         }
11741     }
11742     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11743       fprintf(fp, "white to play\n");
11744     else
11745       fprintf(fp, "black to play\n");
11746 }
11747
11748 void
11749 PrintOpponents(fp)
11750      FILE *fp;
11751 {
11752     if (gameInfo.white != NULL) {
11753         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11754     } else {
11755         fprintf(fp, "\n");
11756     }
11757 }
11758
11759 /* Find last component of program's own name, using some heuristics */
11760 void
11761 TidyProgramName(prog, host, buf)
11762      char *prog, *host, buf[MSG_SIZ];
11763 {
11764     char *p, *q;
11765     int local = (strcmp(host, "localhost") == 0);
11766     while (!local && (p = strchr(prog, ';')) != NULL) {
11767         p++;
11768         while (*p == ' ') p++;
11769         prog = p;
11770     }
11771     if (*prog == '"' || *prog == '\'') {
11772         q = strchr(prog + 1, *prog);
11773     } else {
11774         q = strchr(prog, ' ');
11775     }
11776     if (q == NULL) q = prog + strlen(prog);
11777     p = q;
11778     while (p >= prog && *p != '/' && *p != '\\') p--;
11779     p++;
11780     if(p == prog && *p == '"') p++;
11781     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11782     memcpy(buf, p, q - p);
11783     buf[q - p] = NULLCHAR;
11784     if (!local) {
11785         strcat(buf, "@");
11786         strcat(buf, host);
11787     }
11788 }
11789
11790 char *
11791 TimeControlTagValue()
11792 {
11793     char buf[MSG_SIZ];
11794     if (!appData.clockMode) {
11795         strcpy(buf, "-");
11796     } else if (movesPerSession > 0) {
11797         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11798     } else if (timeIncrement == 0) {
11799         sprintf(buf, "%ld", timeControl/1000);
11800     } else {
11801         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11802     }
11803     return StrSave(buf);
11804 }
11805
11806 void
11807 SetGameInfo()
11808 {
11809     /* This routine is used only for certain modes */
11810     VariantClass v = gameInfo.variant;
11811     ClearGameInfo(&gameInfo);
11812     gameInfo.variant = v;
11813
11814     switch (gameMode) {
11815       case MachinePlaysWhite:
11816         gameInfo.event = StrSave( appData.pgnEventHeader );
11817         gameInfo.site = StrSave(HostName());
11818         gameInfo.date = PGNDate();
11819         gameInfo.round = StrSave("-");
11820         gameInfo.white = StrSave(first.tidy);
11821         gameInfo.black = StrSave(UserName());
11822         gameInfo.timeControl = TimeControlTagValue();
11823         break;
11824
11825       case MachinePlaysBlack:
11826         gameInfo.event = StrSave( appData.pgnEventHeader );
11827         gameInfo.site = StrSave(HostName());
11828         gameInfo.date = PGNDate();
11829         gameInfo.round = StrSave("-");
11830         gameInfo.white = StrSave(UserName());
11831         gameInfo.black = StrSave(first.tidy);
11832         gameInfo.timeControl = TimeControlTagValue();
11833         break;
11834
11835       case TwoMachinesPlay:
11836         gameInfo.event = StrSave( appData.pgnEventHeader );
11837         gameInfo.site = StrSave(HostName());
11838         gameInfo.date = PGNDate();
11839         if (matchGame > 0) {
11840             char buf[MSG_SIZ];
11841             sprintf(buf, "%d", matchGame);
11842             gameInfo.round = StrSave(buf);
11843         } else {
11844             gameInfo.round = StrSave("-");
11845         }
11846         if (first.twoMachinesColor[0] == 'w') {
11847             gameInfo.white = StrSave(first.tidy);
11848             gameInfo.black = StrSave(second.tidy);
11849         } else {
11850             gameInfo.white = StrSave(second.tidy);
11851             gameInfo.black = StrSave(first.tidy);
11852         }
11853         gameInfo.timeControl = TimeControlTagValue();
11854         break;
11855
11856       case EditGame:
11857         gameInfo.event = StrSave("Edited game");
11858         gameInfo.site = StrSave(HostName());
11859         gameInfo.date = PGNDate();
11860         gameInfo.round = StrSave("-");
11861         gameInfo.white = StrSave("-");
11862         gameInfo.black = StrSave("-");
11863         break;
11864
11865       case EditPosition:
11866         gameInfo.event = StrSave("Edited position");
11867         gameInfo.site = StrSave(HostName());
11868         gameInfo.date = PGNDate();
11869         gameInfo.round = StrSave("-");
11870         gameInfo.white = StrSave("-");
11871         gameInfo.black = StrSave("-");
11872         break;
11873
11874       case IcsPlayingWhite:
11875       case IcsPlayingBlack:
11876       case IcsObserving:
11877       case IcsExamining:
11878         break;
11879
11880       case PlayFromGameFile:
11881         gameInfo.event = StrSave("Game from non-PGN file");
11882         gameInfo.site = StrSave(HostName());
11883         gameInfo.date = PGNDate();
11884         gameInfo.round = StrSave("-");
11885         gameInfo.white = StrSave("?");
11886         gameInfo.black = StrSave("?");
11887         break;
11888
11889       default:
11890         break;
11891     }
11892 }
11893
11894 void
11895 ReplaceComment(index, text)
11896      int index;
11897      char *text;
11898 {
11899     int len;
11900
11901     while (*text == '\n') text++;
11902     len = strlen(text);
11903     while (len > 0 && text[len - 1] == '\n') len--;
11904
11905     if (commentList[index] != NULL)
11906       free(commentList[index]);
11907
11908     if (len == 0) {
11909         commentList[index] = NULL;
11910         return;
11911     }
11912     commentList[index] = (char *) malloc(len + 2);
11913     strncpy(commentList[index], text, len);
11914     commentList[index][len] = '\n';
11915     commentList[index][len + 1] = NULLCHAR;
11916 }
11917
11918 void
11919 CrushCRs(text)
11920      char *text;
11921 {
11922   char *p = text;
11923   char *q = text;
11924   char ch;
11925
11926   do {
11927     ch = *p++;
11928     if (ch == '\r') continue;
11929     *q++ = ch;
11930   } while (ch != '\0');
11931 }
11932
11933 void
11934 AppendComment(index, text)
11935      int index;
11936      char *text;
11937 {
11938     int oldlen, len;
11939     char *old;
11940
11941     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11942
11943     CrushCRs(text);
11944     while (*text == '\n') text++;
11945     len = strlen(text);
11946     while (len > 0 && text[len - 1] == '\n') len--;
11947
11948     if (len == 0) return;
11949
11950     if (commentList[index] != NULL) {
11951         old = commentList[index];
11952         oldlen = strlen(old);
11953         commentList[index] = (char *) malloc(oldlen + len + 2);
11954         strcpy(commentList[index], old);
11955         free(old);
11956         strncpy(&commentList[index][oldlen], text, len);
11957         commentList[index][oldlen + len] = '\n';
11958         commentList[index][oldlen + len + 1] = NULLCHAR;
11959     } else {
11960         commentList[index] = (char *) malloc(len + 2);
11961         strncpy(commentList[index], text, len);
11962         commentList[index][len] = '\n';
11963         commentList[index][len + 1] = NULLCHAR;
11964     }
11965 }
11966
11967 static char * FindStr( char * text, char * sub_text )
11968 {
11969     char * result = strstr( text, sub_text );
11970
11971     if( result != NULL ) {
11972         result += strlen( sub_text );
11973     }
11974
11975     return result;
11976 }
11977
11978 /* [AS] Try to extract PV info from PGN comment */
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11980 char *GetInfoFromComment( int index, char * text )
11981 {
11982     char * sep = text;
11983
11984     if( text != NULL && index > 0 ) {
11985         int score = 0;
11986         int depth = 0;
11987         int time = -1, sec = 0, deci;
11988         char * s_eval = FindStr( text, "[%eval " );
11989         char * s_emt = FindStr( text, "[%emt " );
11990
11991         if( s_eval != NULL || s_emt != NULL ) {
11992             /* New style */
11993             char delim;
11994
11995             if( s_eval != NULL ) {
11996                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11997                     return text;
11998                 }
11999
12000                 if( delim != ']' ) {
12001                     return text;
12002                 }
12003             }
12004
12005             if( s_emt != NULL ) {
12006             }
12007         }
12008         else {
12009             /* We expect something like: [+|-]nnn.nn/dd */
12010             int score_lo = 0;
12011
12012             sep = strchr( text, '/' );
12013             if( sep == NULL || sep < (text+4) ) {
12014                 return text;
12015             }
12016
12017             time = -1; sec = -1; deci = -1;
12018             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12019                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12020                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12021                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12022                 return text;
12023             }
12024
12025             if( score_lo < 0 || score_lo >= 100 ) {
12026                 return text;
12027             }
12028
12029             if(sec >= 0) time = 600*time + 10*sec; else
12030             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12031
12032             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12033
12034             /* [HGM] PV time: now locate end of PV info */
12035             while( *++sep >= '0' && *sep <= '9'); // strip depth
12036             if(time >= 0)
12037             while( *++sep >= '0' && *sep <= '9'); // strip time
12038             if(sec >= 0)
12039             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12040             if(deci >= 0)
12041             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12042             while(*sep == ' ') sep++;
12043         }
12044
12045         if( depth <= 0 ) {
12046             return text;
12047         }
12048
12049         if( time < 0 ) {
12050             time = -1;
12051         }
12052
12053         pvInfoList[index-1].depth = depth;
12054         pvInfoList[index-1].score = score;
12055         pvInfoList[index-1].time  = 10*time; // centi-sec
12056     }
12057     return sep;
12058 }
12059
12060 void
12061 SendToProgram(message, cps)
12062      char *message;
12063      ChessProgramState *cps;
12064 {
12065     int count, outCount, error;
12066     char buf[MSG_SIZ];
12067
12068     if (cps->pr == NULL) return;
12069     Attention(cps);
12070     
12071     if (appData.debugMode) {
12072         TimeMark now;
12073         GetTimeMark(&now);
12074         fprintf(debugFP, "%ld >%-6s: %s", 
12075                 SubtractTimeMarks(&now, &programStartTime),
12076                 cps->which, message);
12077     }
12078     
12079     count = strlen(message);
12080     outCount = OutputToProcess(cps->pr, message, count, &error);
12081     if (outCount < count && !exiting 
12082                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12083         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12085             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12086                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12087                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12088             } else {
12089                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12090             }
12091             gameInfo.resultDetails = buf;
12092         }
12093         DisplayFatalError(buf, error, 1);
12094     }
12095 }
12096
12097 void
12098 ReceiveFromProgram(isr, closure, message, count, error)
12099      InputSourceRef isr;
12100      VOIDSTAR closure;
12101      char *message;
12102      int count;
12103      int error;
12104 {
12105     char *end_str;
12106     char buf[MSG_SIZ];
12107     ChessProgramState *cps = (ChessProgramState *)closure;
12108
12109     if (isr != cps->isr) return; /* Killed intentionally */
12110     if (count <= 0) {
12111         if (count == 0) {
12112             sprintf(buf,
12113                     _("Error: %s chess program (%s) exited unexpectedly"),
12114                     cps->which, cps->program);
12115         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12116                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12117                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12118                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12119                 } else {
12120                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12121                 }
12122                 gameInfo.resultDetails = buf;
12123             }
12124             RemoveInputSource(cps->isr);
12125             DisplayFatalError(buf, 0, 1);
12126         } else {
12127             sprintf(buf,
12128                     _("Error reading from %s chess program (%s)"),
12129                     cps->which, cps->program);
12130             RemoveInputSource(cps->isr);
12131
12132             /* [AS] Program is misbehaving badly... kill it */
12133             if( count == -2 ) {
12134                 DestroyChildProcess( cps->pr, 9 );
12135                 cps->pr = NoProc;
12136             }
12137
12138             DisplayFatalError(buf, error, 1);
12139         }
12140         return;
12141     }
12142     
12143     if ((end_str = strchr(message, '\r')) != NULL)
12144       *end_str = NULLCHAR;
12145     if ((end_str = strchr(message, '\n')) != NULL)
12146       *end_str = NULLCHAR;
12147     
12148     if (appData.debugMode) {
12149         TimeMark now; int print = 1;
12150         char *quote = ""; char c; int i;
12151
12152         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12153                 char start = message[0];
12154                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12155                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12156                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12157                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12158                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12159                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12160                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12161                         { quote = "# "; print = (appData.engineComments == 2); }
12162                 message[0] = start; // restore original message
12163         }
12164         if(print) {
12165                 GetTimeMark(&now);
12166                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12167                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12168                         quote,
12169                         message);
12170         }
12171     }
12172
12173     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12174     if (appData.icsEngineAnalyze) {
12175         if (strstr(message, "whisper") != NULL ||
12176              strstr(message, "kibitz") != NULL || 
12177             strstr(message, "tellics") != NULL) return;
12178     }
12179
12180     HandleMachineMove(message, cps);
12181 }
12182
12183
12184 void
12185 SendTimeControl(cps, mps, tc, inc, sd, st)
12186      ChessProgramState *cps;
12187      int mps, inc, sd, st;
12188      long tc;
12189 {
12190     char buf[MSG_SIZ];
12191     int seconds;
12192
12193     if( timeControl_2 > 0 ) {
12194         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12195             tc = timeControl_2;
12196         }
12197     }
12198     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12199     inc /= cps->timeOdds;
12200     st  /= cps->timeOdds;
12201
12202     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12203
12204     if (st > 0) {
12205       /* Set exact time per move, normally using st command */
12206       if (cps->stKludge) {
12207         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12208         seconds = st % 60;
12209         if (seconds == 0) {
12210           sprintf(buf, "level 1 %d\n", st/60);
12211         } else {
12212           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12213         }
12214       } else {
12215         sprintf(buf, "st %d\n", st);
12216       }
12217     } else {
12218       /* Set conventional or incremental time control, using level command */
12219       if (seconds == 0) {
12220         /* Note old gnuchess bug -- minutes:seconds used to not work.
12221            Fixed in later versions, but still avoid :seconds
12222            when seconds is 0. */
12223         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12224       } else {
12225         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12226                 seconds, inc/1000);
12227       }
12228     }
12229     SendToProgram(buf, cps);
12230
12231     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12232     /* Orthogonally, limit search to given depth */
12233     if (sd > 0) {
12234       if (cps->sdKludge) {
12235         sprintf(buf, "depth\n%d\n", sd);
12236       } else {
12237         sprintf(buf, "sd %d\n", sd);
12238       }
12239       SendToProgram(buf, cps);
12240     }
12241
12242     if(cps->nps > 0) { /* [HGM] nps */
12243         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12244         else {
12245                 sprintf(buf, "nps %d\n", cps->nps);
12246               SendToProgram(buf, cps);
12247         }
12248     }
12249 }
12250
12251 ChessProgramState *WhitePlayer()
12252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12253 {
12254     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12255        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12256         return &second;
12257     return &first;
12258 }
12259
12260 void
12261 SendTimeRemaining(cps, machineWhite)
12262      ChessProgramState *cps;
12263      int /*boolean*/ machineWhite;
12264 {
12265     char message[MSG_SIZ];
12266     long time, otime;
12267
12268     /* Note: this routine must be called when the clocks are stopped
12269        or when they have *just* been set or switched; otherwise
12270        it will be off by the time since the current tick started.
12271     */
12272     if (machineWhite) {
12273         time = whiteTimeRemaining / 10;
12274         otime = blackTimeRemaining / 10;
12275     } else {
12276         time = blackTimeRemaining / 10;
12277         otime = whiteTimeRemaining / 10;
12278     }
12279     /* [HGM] translate opponent's time by time-odds factor */
12280     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12281     if (appData.debugMode) {
12282         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12283     }
12284
12285     if (time <= 0) time = 1;
12286     if (otime <= 0) otime = 1;
12287     
12288     sprintf(message, "time %ld\n", time);
12289     SendToProgram(message, cps);
12290
12291     sprintf(message, "otim %ld\n", otime);
12292     SendToProgram(message, cps);
12293 }
12294
12295 int
12296 BoolFeature(p, name, loc, cps)
12297      char **p;
12298      char *name;
12299      int *loc;
12300      ChessProgramState *cps;
12301 {
12302   char buf[MSG_SIZ];
12303   int len = strlen(name);
12304   int val;
12305   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12306     (*p) += len + 1;
12307     sscanf(*p, "%d", &val);
12308     *loc = (val != 0);
12309     while (**p && **p != ' ') (*p)++;
12310     sprintf(buf, "accepted %s\n", name);
12311     SendToProgram(buf, cps);
12312     return TRUE;
12313   }
12314   return FALSE;
12315 }
12316
12317 int
12318 IntFeature(p, name, loc, cps)
12319      char **p;
12320      char *name;
12321      int *loc;
12322      ChessProgramState *cps;
12323 {
12324   char buf[MSG_SIZ];
12325   int len = strlen(name);
12326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12327     (*p) += len + 1;
12328     sscanf(*p, "%d", loc);
12329     while (**p && **p != ' ') (*p)++;
12330     sprintf(buf, "accepted %s\n", name);
12331     SendToProgram(buf, cps);
12332     return TRUE;
12333   }
12334   return FALSE;
12335 }
12336
12337 int
12338 StringFeature(p, name, loc, cps)
12339      char **p;
12340      char *name;
12341      char loc[];
12342      ChessProgramState *cps;
12343 {
12344   char buf[MSG_SIZ];
12345   int len = strlen(name);
12346   if (strncmp((*p), name, len) == 0
12347       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12348     (*p) += len + 2;
12349     sscanf(*p, "%[^\"]", loc);
12350     while (**p && **p != '\"') (*p)++;
12351     if (**p == '\"') (*p)++;
12352     sprintf(buf, "accepted %s\n", name);
12353     SendToProgram(buf, cps);
12354     return TRUE;
12355   }
12356   return FALSE;
12357 }
12358
12359 int 
12360 ParseOption(Option *opt, ChessProgramState *cps)
12361 // [HGM] options: process the string that defines an engine option, and determine
12362 // name, type, default value, and allowed value range
12363 {
12364         char *p, *q, buf[MSG_SIZ];
12365         int n, min = (-1)<<31, max = 1<<31, def;
12366
12367         if(p = strstr(opt->name, " -spin ")) {
12368             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12369             if(max < min) max = min; // enforce consistency
12370             if(def < min) def = min;
12371             if(def > max) def = max;
12372             opt->value = def;
12373             opt->min = min;
12374             opt->max = max;
12375             opt->type = Spin;
12376         } else if(p = strstr(opt->name, " -string ")) {
12377             opt->textValue = p+9;
12378             opt->type = TextBox;
12379         } else if(p = strstr(opt->name, " -check ")) {
12380             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12381             opt->value = (def != 0);
12382             opt->type = CheckBox;
12383         } else if(p = strstr(opt->name, " -combo ")) {
12384             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12385             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12386             opt->value = n = 0;
12387             while(q = StrStr(q, " /// ")) {
12388                 n++; *q = 0;    // count choices, and null-terminate each of them
12389                 q += 5;
12390                 if(*q == '*') { // remember default, which is marked with * prefix
12391                     q++;
12392                     opt->value = n;
12393                 }
12394                 cps->comboList[cps->comboCnt++] = q;
12395             }
12396             cps->comboList[cps->comboCnt++] = NULL;
12397             opt->max = n + 1;
12398             opt->type = ComboBox;
12399         } else if(p = strstr(opt->name, " -button")) {
12400             opt->type = Button;
12401         } else if(p = strstr(opt->name, " -save")) {
12402             opt->type = SaveButton;
12403         } else return FALSE;
12404         *p = 0; // terminate option name
12405         // now look if the command-line options define a setting for this engine option.
12406         if(cps->optionSettings && cps->optionSettings[0])
12407             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12408         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12409                 sprintf(buf, "option %s", p);
12410                 if(p = strstr(buf, ",")) *p = 0;
12411                 strcat(buf, "\n");
12412                 SendToProgram(buf, cps);
12413         }
12414         return TRUE;
12415 }
12416
12417 void
12418 FeatureDone(cps, val)
12419      ChessProgramState* cps;
12420      int val;
12421 {
12422   DelayedEventCallback cb = GetDelayedEvent();
12423   if ((cb == InitBackEnd3 && cps == &first) ||
12424       (cb == TwoMachinesEventIfReady && cps == &second)) {
12425     CancelDelayedEvent();
12426     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12427   }
12428   cps->initDone = val;
12429 }
12430
12431 /* Parse feature command from engine */
12432 void
12433 ParseFeatures(args, cps)
12434      char* args;
12435      ChessProgramState *cps;  
12436 {
12437   char *p = args;
12438   char *q;
12439   int val;
12440   char buf[MSG_SIZ];
12441
12442   for (;;) {
12443     while (*p == ' ') p++;
12444     if (*p == NULLCHAR) return;
12445
12446     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12447     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12448     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12449     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12450     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12451     if (BoolFeature(&p, "reuse", &val, cps)) {
12452       /* Engine can disable reuse, but can't enable it if user said no */
12453       if (!val) cps->reuse = FALSE;
12454       continue;
12455     }
12456     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12457     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12458       if (gameMode == TwoMachinesPlay) {
12459         DisplayTwoMachinesTitle();
12460       } else {
12461         DisplayTitle("");
12462       }
12463       continue;
12464     }
12465     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12466     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12467     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12468     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12469     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12470     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12471     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12472     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12473     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12474     if (IntFeature(&p, "done", &val, cps)) {
12475       FeatureDone(cps, val);
12476       continue;
12477     }
12478     /* Added by Tord: */
12479     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12480     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12481     /* End of additions by Tord */
12482
12483     /* [HGM] added features: */
12484     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12485     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12486     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12487     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12488     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12489     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12490     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12491         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12492         if(cps->nrOptions >= MAX_OPTIONS) {
12493             cps->nrOptions--;
12494             sprintf(buf, "%s engine has too many options\n", cps->which);
12495             DisplayError(buf, 0);
12496         }
12497         continue;
12498     }
12499     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12500     /* End of additions by HGM */
12501
12502     /* unknown feature: complain and skip */
12503     q = p;
12504     while (*q && *q != '=') q++;
12505     sprintf(buf, "rejected %.*s\n", q-p, p);
12506     SendToProgram(buf, cps);
12507     p = q;
12508     if (*p == '=') {
12509       p++;
12510       if (*p == '\"') {
12511         p++;
12512         while (*p && *p != '\"') p++;
12513         if (*p == '\"') p++;
12514       } else {
12515         while (*p && *p != ' ') p++;
12516       }
12517     }
12518   }
12519
12520 }
12521
12522 void
12523 PeriodicUpdatesEvent(newState)
12524      int newState;
12525 {
12526     if (newState == appData.periodicUpdates)
12527       return;
12528
12529     appData.periodicUpdates=newState;
12530
12531     /* Display type changes, so update it now */
12532     DisplayAnalysis();
12533
12534     /* Get the ball rolling again... */
12535     if (newState) {
12536         AnalysisPeriodicEvent(1);
12537         StartAnalysisClock();
12538     }
12539 }
12540
12541 void
12542 PonderNextMoveEvent(newState)
12543      int newState;
12544 {
12545     if (newState == appData.ponderNextMove) return;
12546     if (gameMode == EditPosition) EditPositionDone();
12547     if (newState) {
12548         SendToProgram("hard\n", &first);
12549         if (gameMode == TwoMachinesPlay) {
12550             SendToProgram("hard\n", &second);
12551         }
12552     } else {
12553         SendToProgram("easy\n", &first);
12554         thinkOutput[0] = NULLCHAR;
12555         if (gameMode == TwoMachinesPlay) {
12556             SendToProgram("easy\n", &second);
12557         }
12558     }
12559     appData.ponderNextMove = newState;
12560 }
12561
12562 void
12563 NewSettingEvent(option, command, value)
12564      char *command;
12565      int option, value;
12566 {
12567     char buf[MSG_SIZ];
12568
12569     if (gameMode == EditPosition) EditPositionDone();
12570     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12571     SendToProgram(buf, &first);
12572     if (gameMode == TwoMachinesPlay) {
12573         SendToProgram(buf, &second);
12574     }
12575 }
12576
12577 void
12578 ShowThinkingEvent()
12579 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12580 {
12581     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12582     int newState = appData.showThinking
12583         // [HGM] thinking: other features now need thinking output as well
12584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12585     
12586     if (oldState == newState) return;
12587     oldState = newState;
12588     if (gameMode == EditPosition) EditPositionDone();
12589     if (oldState) {
12590         SendToProgram("post\n", &first);
12591         if (gameMode == TwoMachinesPlay) {
12592             SendToProgram("post\n", &second);
12593         }
12594     } else {
12595         SendToProgram("nopost\n", &first);
12596         thinkOutput[0] = NULLCHAR;
12597         if (gameMode == TwoMachinesPlay) {
12598             SendToProgram("nopost\n", &second);
12599         }
12600     }
12601 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12602 }
12603
12604 void
12605 AskQuestionEvent(title, question, replyPrefix, which)
12606      char *title; char *question; char *replyPrefix; char *which;
12607 {
12608   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12609   if (pr == NoProc) return;
12610   AskQuestion(title, question, replyPrefix, pr);
12611 }
12612
12613 void
12614 DisplayMove(moveNumber)
12615      int moveNumber;
12616 {
12617     char message[MSG_SIZ];
12618     char res[MSG_SIZ];
12619     char cpThinkOutput[MSG_SIZ];
12620
12621     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12622     
12623     if (moveNumber == forwardMostMove - 1 || 
12624         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12625
12626         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12627
12628         if (strchr(cpThinkOutput, '\n')) {
12629             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12630         }
12631     } else {
12632         *cpThinkOutput = NULLCHAR;
12633     }
12634
12635     /* [AS] Hide thinking from human user */
12636     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12637         *cpThinkOutput = NULLCHAR;
12638         if( thinkOutput[0] != NULLCHAR ) {
12639             int i;
12640
12641             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12642                 cpThinkOutput[i] = '.';
12643             }
12644             cpThinkOutput[i] = NULLCHAR;
12645             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12646         }
12647     }
12648
12649     if (moveNumber == forwardMostMove - 1 &&
12650         gameInfo.resultDetails != NULL) {
12651         if (gameInfo.resultDetails[0] == NULLCHAR) {
12652             sprintf(res, " %s", PGNResult(gameInfo.result));
12653         } else {
12654             sprintf(res, " {%s} %s",
12655                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12656         }
12657     } else {
12658         res[0] = NULLCHAR;
12659     }
12660
12661     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12662         DisplayMessage(res, cpThinkOutput);
12663     } else {
12664         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12665                 WhiteOnMove(moveNumber) ? " " : ".. ",
12666                 parseList[moveNumber], res);
12667         DisplayMessage(message, cpThinkOutput);
12668     }
12669 }
12670
12671 void
12672 DisplayAnalysisText(text)
12673      char *text;
12674 {
12675     char buf[MSG_SIZ];
12676
12677     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12678                || appData.icsEngineAnalyze) {
12679         sprintf(buf, "Analysis (%s)", first.tidy);
12680         AnalysisPopUp(buf, text);
12681     }
12682 }
12683
12684 static int
12685 only_one_move(str)
12686      char *str;
12687 {
12688     while (*str && isspace(*str)) ++str;
12689     while (*str && !isspace(*str)) ++str;
12690     if (!*str) return 1;
12691     while (*str && isspace(*str)) ++str;
12692     if (!*str) return 1;
12693     return 0;
12694 }
12695
12696 void
12697 DisplayAnalysis()
12698 {
12699     char buf[MSG_SIZ];
12700     char lst[MSG_SIZ / 2];
12701     double nps;
12702     static char *xtra[] = { "", " (--)", " (++)" };
12703     int h, m, s, cs;
12704   
12705     if (programStats.time == 0) {
12706         programStats.time = 1;
12707     }
12708   
12709     if (programStats.got_only_move) {
12710         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12711     } else {
12712         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12713
12714         nps = (u64ToDouble(programStats.nodes) /
12715              ((double)programStats.time /100.0));
12716
12717         cs = programStats.time % 100;
12718         s = programStats.time / 100;
12719         h = (s / (60*60));
12720         s = s - h*60*60;
12721         m = (s/60);
12722         s = s - m*60;
12723
12724         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12725           if (programStats.move_name[0] != NULLCHAR) {
12726             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12727                     programStats.depth,
12728                     programStats.nr_moves-programStats.moves_left,
12729                     programStats.nr_moves, programStats.move_name,
12730                     ((float)programStats.score)/100.0, lst,
12731                     only_one_move(lst)?
12732                     xtra[programStats.got_fail] : "",
12733                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12734           } else {
12735             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12736                     programStats.depth,
12737                     programStats.nr_moves-programStats.moves_left,
12738                     programStats.nr_moves, ((float)programStats.score)/100.0,
12739                     lst,
12740                     only_one_move(lst)?
12741                     xtra[programStats.got_fail] : "",
12742                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12743           }
12744         } else {
12745             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12746                     programStats.depth,
12747                     ((float)programStats.score)/100.0,
12748                     lst,
12749                     only_one_move(lst)?
12750                     xtra[programStats.got_fail] : "",
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12752         }
12753     }
12754     DisplayAnalysisText(buf);
12755 }
12756
12757 void
12758 DisplayComment(moveNumber, text)
12759      int moveNumber;
12760      char *text;
12761 {
12762     char title[MSG_SIZ];
12763     char buf[8000]; // comment can be long!
12764     int score, depth;
12765
12766     if( appData.autoDisplayComment ) {
12767         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12768             strcpy(title, "Comment");
12769         } else {
12770             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12771                     WhiteOnMove(moveNumber) ? " " : ".. ",
12772                     parseList[moveNumber]);
12773         }
12774         // [HGM] PV info: display PV info together with (or as) comment
12775         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12776             if(text == NULL) text = "";                                           
12777             score = pvInfoList[moveNumber].score;
12778             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12779                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12780             text = buf;
12781         }
12782     } else title[0] = 0;
12783
12784     if (text != NULL)
12785         CommentPopUp(title, text);
12786 }
12787
12788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12789  * might be busy thinking or pondering.  It can be omitted if your
12790  * gnuchess is configured to stop thinking immediately on any user
12791  * input.  However, that gnuchess feature depends on the FIONREAD
12792  * ioctl, which does not work properly on some flavors of Unix.
12793  */
12794 void
12795 Attention(cps)
12796      ChessProgramState *cps;
12797 {
12798 #if ATTENTION
12799     if (!cps->useSigint) return;
12800     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12801     switch (gameMode) {
12802       case MachinePlaysWhite:
12803       case MachinePlaysBlack:
12804       case TwoMachinesPlay:
12805       case IcsPlayingWhite:
12806       case IcsPlayingBlack:
12807       case AnalyzeMode:
12808       case AnalyzeFile:
12809         /* Skip if we know it isn't thinking */
12810         if (!cps->maybeThinking) return;
12811         if (appData.debugMode)
12812           fprintf(debugFP, "Interrupting %s\n", cps->which);
12813         InterruptChildProcess(cps->pr);
12814         cps->maybeThinking = FALSE;
12815         break;
12816       default:
12817         break;
12818     }
12819 #endif /*ATTENTION*/
12820 }
12821
12822 int
12823 CheckFlags()
12824 {
12825     if (whiteTimeRemaining <= 0) {
12826         if (!whiteFlag) {
12827             whiteFlag = TRUE;
12828             if (appData.icsActive) {
12829                 if (appData.autoCallFlag &&
12830                     gameMode == IcsPlayingBlack && !blackFlag) {
12831                   SendToICS(ics_prefix);
12832                   SendToICS("flag\n");
12833                 }
12834             } else {
12835                 if (blackFlag) {
12836                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12837                 } else {
12838                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12839                     if (appData.autoCallFlag) {
12840                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12841                         return TRUE;
12842                     }
12843                 }
12844             }
12845         }
12846     }
12847     if (blackTimeRemaining <= 0) {
12848         if (!blackFlag) {
12849             blackFlag = TRUE;
12850             if (appData.icsActive) {
12851                 if (appData.autoCallFlag &&
12852                     gameMode == IcsPlayingWhite && !whiteFlag) {
12853                   SendToICS(ics_prefix);
12854                   SendToICS("flag\n");
12855                 }
12856             } else {
12857                 if (whiteFlag) {
12858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12859                 } else {
12860                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12861                     if (appData.autoCallFlag) {
12862                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12863                         return TRUE;
12864                     }
12865                 }
12866             }
12867         }
12868     }
12869     return FALSE;
12870 }
12871
12872 void
12873 CheckTimeControl()
12874 {
12875     if (!appData.clockMode || appData.icsActive ||
12876         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12877
12878     /*
12879      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12880      */
12881     if ( !WhiteOnMove(forwardMostMove) )
12882         /* White made time control */
12883         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12884         /* [HGM] time odds: correct new time quota for time odds! */
12885                                             / WhitePlayer()->timeOdds;
12886       else
12887         /* Black made time control */
12888         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12889                                             / WhitePlayer()->other->timeOdds;
12890 }
12891
12892 void
12893 DisplayBothClocks()
12894 {
12895     int wom = gameMode == EditPosition ?
12896       !blackPlaysFirst : WhiteOnMove(currentMove);
12897     DisplayWhiteClock(whiteTimeRemaining, wom);
12898     DisplayBlackClock(blackTimeRemaining, !wom);
12899 }
12900
12901
12902 /* Timekeeping seems to be a portability nightmare.  I think everyone
12903    has ftime(), but I'm really not sure, so I'm including some ifdefs
12904    to use other calls if you don't.  Clocks will be less accurate if
12905    you have neither ftime nor gettimeofday.
12906 */
12907
12908 /* VS 2008 requires the #include outside of the function */
12909 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12910 #include <sys/timeb.h>
12911 #endif
12912
12913 /* Get the current time as a TimeMark */
12914 void
12915 GetTimeMark(tm)
12916      TimeMark *tm;
12917 {
12918 #if HAVE_GETTIMEOFDAY
12919
12920     struct timeval timeVal;
12921     struct timezone timeZone;
12922
12923     gettimeofday(&timeVal, &timeZone);
12924     tm->sec = (long) timeVal.tv_sec; 
12925     tm->ms = (int) (timeVal.tv_usec / 1000L);
12926
12927 #else /*!HAVE_GETTIMEOFDAY*/
12928 #if HAVE_FTIME
12929
12930 // include <sys/timeb.h> / moved to just above start of function
12931     struct timeb timeB;
12932
12933     ftime(&timeB);
12934     tm->sec = (long) timeB.time;
12935     tm->ms = (int) timeB.millitm;
12936
12937 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12938     tm->sec = (long) time(NULL);
12939     tm->ms = 0;
12940 #endif
12941 #endif
12942 }
12943
12944 /* Return the difference in milliseconds between two
12945    time marks.  We assume the difference will fit in a long!
12946 */
12947 long
12948 SubtractTimeMarks(tm2, tm1)
12949      TimeMark *tm2, *tm1;
12950 {
12951     return 1000L*(tm2->sec - tm1->sec) +
12952            (long) (tm2->ms - tm1->ms);
12953 }
12954
12955
12956 /*
12957  * Code to manage the game clocks.
12958  *
12959  * In tournament play, black starts the clock and then white makes a move.
12960  * We give the human user a slight advantage if he is playing white---the
12961  * clocks don't run until he makes his first move, so it takes zero time.
12962  * Also, we don't account for network lag, so we could get out of sync
12963  * with GNU Chess's clock -- but then, referees are always right.  
12964  */
12965
12966 static TimeMark tickStartTM;
12967 static long intendedTickLength;
12968
12969 long
12970 NextTickLength(timeRemaining)
12971      long timeRemaining;
12972 {
12973     long nominalTickLength, nextTickLength;
12974
12975     if (timeRemaining > 0L && timeRemaining <= 10000L)
12976       nominalTickLength = 100L;
12977     else
12978       nominalTickLength = 1000L;
12979     nextTickLength = timeRemaining % nominalTickLength;
12980     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12981
12982     return nextTickLength;
12983 }
12984
12985 /* Adjust clock one minute up or down */
12986 void
12987 AdjustClock(Boolean which, int dir)
12988 {
12989     if(which) blackTimeRemaining += 60000*dir;
12990     else      whiteTimeRemaining += 60000*dir;
12991     DisplayBothClocks();
12992 }
12993
12994 /* Stop clocks and reset to a fresh time control */
12995 void
12996 ResetClocks() 
12997 {
12998     (void) StopClockTimer();
12999     if (appData.icsActive) {
13000         whiteTimeRemaining = blackTimeRemaining = 0;
13001     } else { /* [HGM] correct new time quote for time odds */
13002         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13003         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13004     }
13005     if (whiteFlag || blackFlag) {
13006         DisplayTitle("");
13007         whiteFlag = blackFlag = FALSE;
13008     }
13009     DisplayBothClocks();
13010 }
13011
13012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13013
13014 /* Decrement running clock by amount of time that has passed */
13015 void
13016 DecrementClocks()
13017 {
13018     long timeRemaining;
13019     long lastTickLength, fudge;
13020     TimeMark now;
13021
13022     if (!appData.clockMode) return;
13023     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13024         
13025     GetTimeMark(&now);
13026
13027     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13028
13029     /* Fudge if we woke up a little too soon */
13030     fudge = intendedTickLength - lastTickLength;
13031     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13032
13033     if (WhiteOnMove(forwardMostMove)) {
13034         if(whiteNPS >= 0) lastTickLength = 0;
13035         timeRemaining = whiteTimeRemaining -= lastTickLength;
13036         DisplayWhiteClock(whiteTimeRemaining - fudge,
13037                           WhiteOnMove(currentMove));
13038     } else {
13039         if(blackNPS >= 0) lastTickLength = 0;
13040         timeRemaining = blackTimeRemaining -= lastTickLength;
13041         DisplayBlackClock(blackTimeRemaining - fudge,
13042                           !WhiteOnMove(currentMove));
13043     }
13044
13045     if (CheckFlags()) return;
13046         
13047     tickStartTM = now;
13048     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13049     StartClockTimer(intendedTickLength);
13050
13051     /* if the time remaining has fallen below the alarm threshold, sound the
13052      * alarm. if the alarm has sounded and (due to a takeback or time control
13053      * with increment) the time remaining has increased to a level above the
13054      * threshold, reset the alarm so it can sound again. 
13055      */
13056     
13057     if (appData.icsActive && appData.icsAlarm) {
13058
13059         /* make sure we are dealing with the user's clock */
13060         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13061                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13062            )) return;
13063
13064         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13065             alarmSounded = FALSE;
13066         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13067             PlayAlarmSound();
13068             alarmSounded = TRUE;
13069         }
13070     }
13071 }
13072
13073
13074 /* A player has just moved, so stop the previously running
13075    clock and (if in clock mode) start the other one.
13076    We redisplay both clocks in case we're in ICS mode, because
13077    ICS gives us an update to both clocks after every move.
13078    Note that this routine is called *after* forwardMostMove
13079    is updated, so the last fractional tick must be subtracted
13080    from the color that is *not* on move now.
13081 */
13082 void
13083 SwitchClocks()
13084 {
13085     long lastTickLength;
13086     TimeMark now;
13087     int flagged = FALSE;
13088
13089     GetTimeMark(&now);
13090
13091     if (StopClockTimer() && appData.clockMode) {
13092         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13093         if (WhiteOnMove(forwardMostMove)) {
13094             if(blackNPS >= 0) lastTickLength = 0;
13095             blackTimeRemaining -= lastTickLength;
13096            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13097 //         if(pvInfoList[forwardMostMove-1].time == -1)
13098                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13099                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13100         } else {
13101            if(whiteNPS >= 0) lastTickLength = 0;
13102            whiteTimeRemaining -= lastTickLength;
13103            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13104 //         if(pvInfoList[forwardMostMove-1].time == -1)
13105                  pvInfoList[forwardMostMove-1].time = 
13106                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13107         }
13108         flagged = CheckFlags();
13109     }
13110     CheckTimeControl();
13111
13112     if (flagged || !appData.clockMode) return;
13113
13114     switch (gameMode) {
13115       case MachinePlaysBlack:
13116       case MachinePlaysWhite:
13117       case BeginningOfGame:
13118         if (pausing) return;
13119         break;
13120
13121       case EditGame:
13122       case PlayFromGameFile:
13123       case IcsExamining:
13124         return;
13125
13126       default:
13127         break;
13128     }
13129
13130     tickStartTM = now;
13131     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13132       whiteTimeRemaining : blackTimeRemaining);
13133     StartClockTimer(intendedTickLength);
13134 }
13135         
13136
13137 /* Stop both clocks */
13138 void
13139 StopClocks()
13140 {       
13141     long lastTickLength;
13142     TimeMark now;
13143
13144     if (!StopClockTimer()) return;
13145     if (!appData.clockMode) return;
13146
13147     GetTimeMark(&now);
13148
13149     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13150     if (WhiteOnMove(forwardMostMove)) {
13151         if(whiteNPS >= 0) lastTickLength = 0;
13152         whiteTimeRemaining -= lastTickLength;
13153         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13154     } else {
13155         if(blackNPS >= 0) lastTickLength = 0;
13156         blackTimeRemaining -= lastTickLength;
13157         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13158     }
13159     CheckFlags();
13160 }
13161         
13162 /* Start clock of player on move.  Time may have been reset, so
13163    if clock is already running, stop and restart it. */
13164 void
13165 StartClocks()
13166 {
13167     (void) StopClockTimer(); /* in case it was running already */
13168     DisplayBothClocks();
13169     if (CheckFlags()) return;
13170
13171     if (!appData.clockMode) return;
13172     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13173
13174     GetTimeMark(&tickStartTM);
13175     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13176       whiteTimeRemaining : blackTimeRemaining);
13177
13178    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13179     whiteNPS = blackNPS = -1; 
13180     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13181        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13182         whiteNPS = first.nps;
13183     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13184        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13185         blackNPS = first.nps;
13186     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13187         whiteNPS = second.nps;
13188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13189         blackNPS = second.nps;
13190     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13191
13192     StartClockTimer(intendedTickLength);
13193 }
13194
13195 char *
13196 TimeString(ms)
13197      long ms;
13198 {
13199     long second, minute, hour, day;
13200     char *sign = "";
13201     static char buf[32];
13202     
13203     if (ms > 0 && ms <= 9900) {
13204       /* convert milliseconds to tenths, rounding up */
13205       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13206
13207       sprintf(buf, " %03.1f ", tenths/10.0);
13208       return buf;
13209     }
13210
13211     /* convert milliseconds to seconds, rounding up */
13212     /* use floating point to avoid strangeness of integer division
13213        with negative dividends on many machines */
13214     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13215
13216     if (second < 0) {
13217         sign = "-";
13218         second = -second;
13219     }
13220     
13221     day = second / (60 * 60 * 24);
13222     second = second % (60 * 60 * 24);
13223     hour = second / (60 * 60);
13224     second = second % (60 * 60);
13225     minute = second / 60;
13226     second = second % 60;
13227     
13228     if (day > 0)
13229       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13230               sign, day, hour, minute, second);
13231     else if (hour > 0)
13232       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13233     else
13234       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13235     
13236     return buf;
13237 }
13238
13239
13240 /*
13241  * This is necessary because some C libraries aren't ANSI C compliant yet.
13242  */
13243 char *
13244 StrStr(string, match)
13245      char *string, *match;
13246 {
13247     int i, length;
13248     
13249     length = strlen(match);
13250     
13251     for (i = strlen(string) - length; i >= 0; i--, string++)
13252       if (!strncmp(match, string, length))
13253         return string;
13254     
13255     return NULL;
13256 }
13257
13258 char *
13259 StrCaseStr(string, match)
13260      char *string, *match;
13261 {
13262     int i, j, length;
13263     
13264     length = strlen(match);
13265     
13266     for (i = strlen(string) - length; i >= 0; i--, string++) {
13267         for (j = 0; j < length; j++) {
13268             if (ToLower(match[j]) != ToLower(string[j]))
13269               break;
13270         }
13271         if (j == length) return string;
13272     }
13273
13274     return NULL;
13275 }
13276
13277 #ifndef _amigados
13278 int
13279 StrCaseCmp(s1, s2)
13280      char *s1, *s2;
13281 {
13282     char c1, c2;
13283     
13284     for (;;) {
13285         c1 = ToLower(*s1++);
13286         c2 = ToLower(*s2++);
13287         if (c1 > c2) return 1;
13288         if (c1 < c2) return -1;
13289         if (c1 == NULLCHAR) return 0;
13290     }
13291 }
13292
13293
13294 int
13295 ToLower(c)
13296      int c;
13297 {
13298     return isupper(c) ? tolower(c) : c;
13299 }
13300
13301
13302 int
13303 ToUpper(c)
13304      int c;
13305 {
13306     return islower(c) ? toupper(c) : c;
13307 }
13308 #endif /* !_amigados    */
13309
13310 char *
13311 StrSave(s)
13312      char *s;
13313 {
13314     char *ret;
13315
13316     if ((ret = (char *) malloc(strlen(s) + 1))) {
13317         strcpy(ret, s);
13318     }
13319     return ret;
13320 }
13321
13322 char *
13323 StrSavePtr(s, savePtr)
13324      char *s, **savePtr;
13325 {
13326     if (*savePtr) {
13327         free(*savePtr);
13328     }
13329     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13330         strcpy(*savePtr, s);
13331     }
13332     return(*savePtr);
13333 }
13334
13335 char *
13336 PGNDate()
13337 {
13338     time_t clock;
13339     struct tm *tm;
13340     char buf[MSG_SIZ];
13341
13342     clock = time((time_t *)NULL);
13343     tm = localtime(&clock);
13344     sprintf(buf, "%04d.%02d.%02d",
13345             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13346     return StrSave(buf);
13347 }
13348
13349
13350 char *
13351 PositionToFEN(move, overrideCastling)
13352      int move;
13353      char *overrideCastling;
13354 {
13355     int i, j, fromX, fromY, toX, toY;
13356     int whiteToPlay;
13357     char buf[128];
13358     char *p, *q;
13359     int emptycount;
13360     ChessSquare piece;
13361
13362     whiteToPlay = (gameMode == EditPosition) ?
13363       !blackPlaysFirst : (move % 2 == 0);
13364     p = buf;
13365
13366     /* Piece placement data */
13367     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13368         emptycount = 0;
13369         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13370             if (boards[move][i][j] == EmptySquare) {
13371                 emptycount++;
13372             } else { ChessSquare piece = boards[move][i][j];
13373                 if (emptycount > 0) {
13374                     if(emptycount<10) /* [HGM] can be >= 10 */
13375                         *p++ = '0' + emptycount;
13376                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13377                     emptycount = 0;
13378                 }
13379                 if(PieceToChar(piece) == '+') {
13380                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13381                     *p++ = '+';
13382                     piece = (ChessSquare)(DEMOTED piece);
13383                 } 
13384                 *p++ = PieceToChar(piece);
13385                 if(p[-1] == '~') {
13386                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13387                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13388                     *p++ = '~';
13389                 }
13390             }
13391         }
13392         if (emptycount > 0) {
13393             if(emptycount<10) /* [HGM] can be >= 10 */
13394                 *p++ = '0' + emptycount;
13395             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13396             emptycount = 0;
13397         }
13398         *p++ = '/';
13399     }
13400     *(p - 1) = ' ';
13401
13402     /* [HGM] print Crazyhouse or Shogi holdings */
13403     if( gameInfo.holdingsWidth ) {
13404         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13405         q = p;
13406         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13407             piece = boards[move][i][BOARD_WIDTH-1];
13408             if( piece != EmptySquare )
13409               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13410                   *p++ = PieceToChar(piece);
13411         }
13412         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13413             piece = boards[move][BOARD_HEIGHT-i-1][0];
13414             if( piece != EmptySquare )
13415               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13416                   *p++ = PieceToChar(piece);
13417         }
13418
13419         if( q == p ) *p++ = '-';
13420         *p++ = ']';
13421         *p++ = ' ';
13422     }
13423
13424     /* Active color */
13425     *p++ = whiteToPlay ? 'w' : 'b';
13426     *p++ = ' ';
13427
13428   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13429     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13430   } else {
13431   if(nrCastlingRights) {
13432      q = p;
13433      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13434        /* [HGM] write directly from rights */
13435            if(castlingRights[move][2] >= 0 &&
13436               castlingRights[move][0] >= 0   )
13437                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13438            if(castlingRights[move][2] >= 0 &&
13439               castlingRights[move][1] >= 0   )
13440                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13441            if(castlingRights[move][5] >= 0 &&
13442               castlingRights[move][3] >= 0   )
13443                 *p++ = castlingRights[move][3] + AAA;
13444            if(castlingRights[move][5] >= 0 &&
13445               castlingRights[move][4] >= 0   )
13446                 *p++ = castlingRights[move][4] + AAA;
13447      } else {
13448
13449         /* [HGM] write true castling rights */
13450         if( nrCastlingRights == 6 ) {
13451             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13452                castlingRights[move][2] >= 0  ) *p++ = 'K';
13453             if(castlingRights[move][1] == BOARD_LEFT &&
13454                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13455             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13456                castlingRights[move][5] >= 0  ) *p++ = 'k';
13457             if(castlingRights[move][4] == BOARD_LEFT &&
13458                castlingRights[move][5] >= 0  ) *p++ = 'q';
13459         }
13460      }
13461      if (q == p) *p++ = '-'; /* No castling rights */
13462      *p++ = ' ';
13463   }
13464
13465   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13466      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13467     /* En passant target square */
13468     if (move > backwardMostMove) {
13469         fromX = moveList[move - 1][0] - AAA;
13470         fromY = moveList[move - 1][1] - ONE;
13471         toX = moveList[move - 1][2] - AAA;
13472         toY = moveList[move - 1][3] - ONE;
13473         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13474             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13475             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13476             fromX == toX) {
13477             /* 2-square pawn move just happened */
13478             *p++ = toX + AAA;
13479             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13480         } else {
13481             *p++ = '-';
13482         }
13483     } else {
13484         *p++ = '-';
13485     }
13486     *p++ = ' ';
13487   }
13488   }
13489
13490     /* [HGM] find reversible plies */
13491     {   int i = 0, j=move;
13492
13493         if (appData.debugMode) { int k;
13494             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13495             for(k=backwardMostMove; k<=forwardMostMove; k++)
13496                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13497
13498         }
13499
13500         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13501         if( j == backwardMostMove ) i += initialRulePlies;
13502         sprintf(p, "%d ", i);
13503         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13504     }
13505     /* Fullmove number */
13506     sprintf(p, "%d", (move / 2) + 1);
13507     
13508     return StrSave(buf);
13509 }
13510
13511 Boolean
13512 ParseFEN(board, blackPlaysFirst, fen)
13513     Board board;
13514      int *blackPlaysFirst;
13515      char *fen;
13516 {
13517     int i, j;
13518     char *p;
13519     int emptycount;
13520     ChessSquare piece;
13521
13522     p = fen;
13523
13524     /* [HGM] by default clear Crazyhouse holdings, if present */
13525     if(gameInfo.holdingsWidth) {
13526        for(i=0; i<BOARD_HEIGHT; i++) {
13527            board[i][0]             = EmptySquare; /* black holdings */
13528            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13529            board[i][1]             = (ChessSquare) 0; /* black counts */
13530            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13531        }
13532     }
13533
13534     /* Piece placement data */
13535     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13536         j = 0;
13537         for (;;) {
13538             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13539                 if (*p == '/') p++;
13540                 emptycount = gameInfo.boardWidth - j;
13541                 while (emptycount--)
13542                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13543                 break;
13544 #if(BOARD_SIZE >= 10)
13545             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13546                 p++; emptycount=10;
13547                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13548                 while (emptycount--)
13549                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13550 #endif
13551             } else if (isdigit(*p)) {
13552                 emptycount = *p++ - '0';
13553                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13554                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13555                 while (emptycount--)
13556                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13557             } else if (*p == '+' || isalpha(*p)) {
13558                 if (j >= gameInfo.boardWidth) return FALSE;
13559                 if(*p=='+') {
13560                     piece = CharToPiece(*++p);
13561                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13562                     piece = (ChessSquare) (PROMOTED piece ); p++;
13563                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13564                 } else piece = CharToPiece(*p++);
13565
13566                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13567                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13568                     piece = (ChessSquare) (PROMOTED piece);
13569                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13570                     p++;
13571                 }
13572                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13573             } else {
13574                 return FALSE;
13575             }
13576         }
13577     }
13578     while (*p == '/' || *p == ' ') p++;
13579
13580     /* [HGM] look for Crazyhouse holdings here */
13581     while(*p==' ') p++;
13582     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13583         if(*p == '[') p++;
13584         if(*p == '-' ) *p++; /* empty holdings */ else {
13585             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13586             /* if we would allow FEN reading to set board size, we would   */
13587             /* have to add holdings and shift the board read so far here   */
13588             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13589                 *p++;
13590                 if((int) piece >= (int) BlackPawn ) {
13591                     i = (int)piece - (int)BlackPawn;
13592                     i = PieceToNumber((ChessSquare)i);
13593                     if( i >= gameInfo.holdingsSize ) return FALSE;
13594                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13595                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13596                 } else {
13597                     i = (int)piece - (int)WhitePawn;
13598                     i = PieceToNumber((ChessSquare)i);
13599                     if( i >= gameInfo.holdingsSize ) return FALSE;
13600                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13601                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13602                 }
13603             }
13604         }
13605         if(*p == ']') *p++;
13606     }
13607
13608     while(*p == ' ') p++;
13609
13610     /* Active color */
13611     switch (*p++) {
13612       case 'w':
13613         *blackPlaysFirst = FALSE;
13614         break;
13615       case 'b': 
13616         *blackPlaysFirst = TRUE;
13617         break;
13618       default:
13619         return FALSE;
13620     }
13621
13622     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13623     /* return the extra info in global variiables             */
13624
13625     /* set defaults in case FEN is incomplete */
13626     FENepStatus = EP_UNKNOWN;
13627     for(i=0; i<nrCastlingRights; i++ ) {
13628         FENcastlingRights[i] =
13629             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13630     }   /* assume possible unless obviously impossible */
13631     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13632     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13633     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13634     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13635     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13636     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13637     FENrulePlies = 0;
13638
13639     while(*p==' ') p++;
13640     if(nrCastlingRights) {
13641       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13642           /* castling indicator present, so default becomes no castlings */
13643           for(i=0; i<nrCastlingRights; i++ ) {
13644                  FENcastlingRights[i] = -1;
13645           }
13646       }
13647       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13648              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13649              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13650              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13651         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13652
13653         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13654             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13655             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13656         }
13657         switch(c) {
13658           case'K':
13659               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13660               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13661               FENcastlingRights[2] = whiteKingFile;
13662               break;
13663           case'Q':
13664               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13665               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13666               FENcastlingRights[2] = whiteKingFile;
13667               break;
13668           case'k':
13669               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13670               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13671               FENcastlingRights[5] = blackKingFile;
13672               break;
13673           case'q':
13674               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13675               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13676               FENcastlingRights[5] = blackKingFile;
13677           case '-':
13678               break;
13679           default: /* FRC castlings */
13680               if(c >= 'a') { /* black rights */
13681                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13682                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13683                   if(i == BOARD_RGHT) break;
13684                   FENcastlingRights[5] = i;
13685                   c -= AAA;
13686                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13687                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13688                   if(c > i)
13689                       FENcastlingRights[3] = c;
13690                   else
13691                       FENcastlingRights[4] = c;
13692               } else { /* white rights */
13693                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13694                     if(board[0][i] == WhiteKing) break;
13695                   if(i == BOARD_RGHT) break;
13696                   FENcastlingRights[2] = i;
13697                   c -= AAA - 'a' + 'A';
13698                   if(board[0][c] >= WhiteKing) break;
13699                   if(c > i)
13700                       FENcastlingRights[0] = c;
13701                   else
13702                       FENcastlingRights[1] = c;
13703               }
13704         }
13705       }
13706     if (appData.debugMode) {
13707         fprintf(debugFP, "FEN castling rights:");
13708         for(i=0; i<nrCastlingRights; i++)
13709         fprintf(debugFP, " %d", FENcastlingRights[i]);
13710         fprintf(debugFP, "\n");
13711     }
13712
13713       while(*p==' ') p++;
13714     }
13715
13716     /* read e.p. field in games that know e.p. capture */
13717     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13718        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13719       if(*p=='-') {
13720         p++; FENepStatus = EP_NONE;
13721       } else {
13722          char c = *p++ - AAA;
13723
13724          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13725          if(*p >= '0' && *p <='9') *p++;
13726          FENepStatus = c;
13727       }
13728     }
13729
13730
13731     if(sscanf(p, "%d", &i) == 1) {
13732         FENrulePlies = i; /* 50-move ply counter */
13733         /* (The move number is still ignored)    */
13734     }
13735
13736     return TRUE;
13737 }
13738       
13739 void
13740 EditPositionPasteFEN(char *fen)
13741 {
13742   if (fen != NULL) {
13743     Board initial_position;
13744
13745     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13746       DisplayError(_("Bad FEN position in clipboard"), 0);
13747       return ;
13748     } else {
13749       int savedBlackPlaysFirst = blackPlaysFirst;
13750       EditPositionEvent();
13751       blackPlaysFirst = savedBlackPlaysFirst;
13752       CopyBoard(boards[0], initial_position);
13753           /* [HGM] copy FEN attributes as well */
13754           {   int i;
13755               initialRulePlies = FENrulePlies;
13756               epStatus[0] = FENepStatus;
13757               for( i=0; i<nrCastlingRights; i++ )
13758                   castlingRights[0][i] = FENcastlingRights[i];
13759           }
13760       EditPositionDone();
13761       DisplayBothClocks();
13762       DrawPosition(FALSE, boards[currentMove]);
13763     }
13764   }
13765 }