some bugfixes: ICS error handling
[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 (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2958                 if (looking_at(buf, &i, "Illegal move") ||
2959                     looking_at(buf, &i, "Not a legal move") ||
2960                     looking_at(buf, &i, "Your king is in check") ||
2961                     looking_at(buf, &i, "It isn't your turn") ||
2962                     looking_at(buf, &i, "It is not your move")) {
2963                     /* Illegal move */
2964                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2965                         currentMove = --forwardMostMove;
2966                         DisplayMove(currentMove - 1); /* before DMError */
2967                         DrawPosition(FALSE, boards[currentMove]);
2968                         SwitchClocks();
2969                         DisplayBothClocks();
2970                     }
2971                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2972                     ics_user_moved = 0;
2973                     continue;
2974                 }
2975             }
2976
2977             if (looking_at(buf, &i, "still have time") ||
2978                 looking_at(buf, &i, "not out of time") ||
2979                 looking_at(buf, &i, "either player is out of time") ||
2980                 looking_at(buf, &i, "has timeseal; checking")) {
2981                 /* We must have called his flag a little too soon */
2982                 whiteFlag = blackFlag = FALSE;
2983                 continue;
2984             }
2985
2986             if (looking_at(buf, &i, "added * seconds to") ||
2987                 looking_at(buf, &i, "seconds were added to")) {
2988                 /* Update the clocks */
2989                 SendToICS(ics_prefix);
2990                 SendToICS("refresh\n");
2991                 continue;
2992             }
2993
2994             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2995                 ics_clock_paused = TRUE;
2996                 StopClocks();
2997                 continue;
2998             }
2999
3000             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3001                 ics_clock_paused = FALSE;
3002                 StartClocks();
3003                 continue;
3004             }
3005
3006             /* Grab player ratings from the Creating: message.
3007                Note we have to check for the special case when
3008                the ICS inserts things like [white] or [black]. */
3009             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3010                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3011                 /* star_matches:
3012                    0    player 1 name (not necessarily white)
3013                    1    player 1 rating
3014                    2    empty, white, or black (IGNORED)
3015                    3    player 2 name (not necessarily black)
3016                    4    player 2 rating
3017                    
3018                    The names/ratings are sorted out when the game
3019                    actually starts (below).
3020                 */
3021                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3022                 player1Rating = string_to_rating(star_match[1]);
3023                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3024                 player2Rating = string_to_rating(star_match[4]);
3025
3026                 if (appData.debugMode)
3027                   fprintf(debugFP, 
3028                           "Ratings from 'Creating:' %s %d, %s %d\n",
3029                           player1Name, player1Rating,
3030                           player2Name, player2Rating);
3031
3032                 continue;
3033             }
3034             
3035             /* Improved generic start/end-of-game messages */
3036             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3037                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3038                 /* If tkind == 0: */
3039                 /* star_match[0] is the game number */
3040                 /*           [1] is the white player's name */
3041                 /*           [2] is the black player's name */
3042                 /* For end-of-game: */
3043                 /*           [3] is the reason for the game end */
3044                 /*           [4] is a PGN end game-token, preceded by " " */
3045                 /* For start-of-game: */
3046                 /*           [3] begins with "Creating" or "Continuing" */
3047                 /*           [4] is " *" or empty (don't care). */
3048                 int gamenum = atoi(star_match[0]);
3049                 char *whitename, *blackname, *why, *endtoken;
3050                 ChessMove endtype = (ChessMove) 0;
3051
3052                 if (tkind == 0) {
3053                   whitename = star_match[1];
3054                   blackname = star_match[2];
3055                   why = star_match[3];
3056                   endtoken = star_match[4];
3057                 } else {
3058                   whitename = star_match[1];
3059                   blackname = star_match[3];
3060                   why = star_match[5];
3061                   endtoken = star_match[6];
3062                 }
3063
3064                 /* Game start messages */
3065                 if (strncmp(why, "Creating ", 9) == 0 ||
3066                     strncmp(why, "Continuing ", 11) == 0) {
3067                     gs_gamenum = gamenum;
3068                     strcpy(gs_kind, strchr(why, ' ') + 1);
3069 #if ZIPPY
3070                     if (appData.zippyPlay) {
3071                         ZippyGameStart(whitename, blackname);
3072                     }
3073 #endif /*ZIPPY*/
3074                     continue;
3075                 }
3076
3077                 /* Game end messages */
3078                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3079                     ics_gamenum != gamenum) {
3080                     continue;
3081                 }
3082                 while (endtoken[0] == ' ') endtoken++;
3083                 switch (endtoken[0]) {
3084                   case '*':
3085                   default:
3086                     endtype = GameUnfinished;
3087                     break;
3088                   case '0':
3089                     endtype = BlackWins;
3090                     break;
3091                   case '1':
3092                     if (endtoken[1] == '/')
3093                       endtype = GameIsDrawn;
3094                     else
3095                       endtype = WhiteWins;
3096                     break;
3097                 }
3098                 GameEnds(endtype, why, GE_ICS);
3099 #if ZIPPY
3100                 if (appData.zippyPlay && first.initDone) {
3101                     ZippyGameEnd(endtype, why);
3102                     if (first.pr == NULL) {
3103                       /* Start the next process early so that we'll
3104                          be ready for the next challenge */
3105                       StartChessProgram(&first);
3106                     }
3107                     /* Send "new" early, in case this command takes
3108                        a long time to finish, so that we'll be ready
3109                        for the next challenge. */
3110                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3111                     Reset(TRUE, TRUE);
3112                 }
3113 #endif /*ZIPPY*/
3114                 continue;
3115             }
3116
3117             if (looking_at(buf, &i, "Removing game * from observation") ||
3118                 looking_at(buf, &i, "no longer observing game *") ||
3119                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3120                 if (gameMode == IcsObserving &&
3121                     atoi(star_match[0]) == ics_gamenum)
3122                   {
3123                       /* icsEngineAnalyze */
3124                       if (appData.icsEngineAnalyze) {
3125                             ExitAnalyzeMode();
3126                             ModeHighlight();
3127                       }
3128                       StopClocks();
3129                       gameMode = IcsIdle;
3130                       ics_gamenum = -1;
3131                       ics_user_moved = FALSE;
3132                   }
3133                 continue;
3134             }
3135
3136             if (looking_at(buf, &i, "no longer examining game *")) {
3137                 if (gameMode == IcsExamining &&
3138                     atoi(star_match[0]) == ics_gamenum)
3139                   {
3140                       gameMode = IcsIdle;
3141                       ics_gamenum = -1;
3142                       ics_user_moved = FALSE;
3143                   }
3144                 continue;
3145             }
3146
3147             /* Advance leftover_start past any newlines we find,
3148                so only partial lines can get reparsed */
3149             if (looking_at(buf, &i, "\n")) {
3150                 prevColor = curColor;
3151                 if (curColor != ColorNormal) {
3152                     if (oldi > next_out) {
3153                         SendToPlayer(&buf[next_out], oldi - next_out);
3154                         next_out = oldi;
3155                     }
3156                     Colorize(ColorNormal, FALSE);
3157                     curColor = ColorNormal;
3158                 }
3159                 if (started == STARTED_BOARD) {
3160                     started = STARTED_NONE;
3161                     parse[parse_pos] = NULLCHAR;
3162                     ParseBoard12(parse);
3163                     ics_user_moved = 0;
3164
3165                     /* Send premove here */
3166                     if (appData.premove) {
3167                       char str[MSG_SIZ];
3168                       if (currentMove == 0 &&
3169                           gameMode == IcsPlayingWhite &&
3170                           appData.premoveWhite) {
3171                         sprintf(str, "%s%s\n", ics_prefix,
3172                                 appData.premoveWhiteText);
3173                         if (appData.debugMode)
3174                           fprintf(debugFP, "Sending premove:\n");
3175                         SendToICS(str);
3176                       } else if (currentMove == 1 &&
3177                                  gameMode == IcsPlayingBlack &&
3178                                  appData.premoveBlack) {
3179                         sprintf(str, "%s%s\n", ics_prefix,
3180                                 appData.premoveBlackText);
3181                         if (appData.debugMode)
3182                           fprintf(debugFP, "Sending premove:\n");
3183                         SendToICS(str);
3184                       } else if (gotPremove) {
3185                         gotPremove = 0;
3186                         ClearPremoveHighlights();
3187                         if (appData.debugMode)
3188                           fprintf(debugFP, "Sending premove:\n");
3189                           UserMoveEvent(premoveFromX, premoveFromY, 
3190                                         premoveToX, premoveToY, 
3191                                         premovePromoChar);
3192                       }
3193                     }
3194
3195                     /* Usually suppress following prompt */
3196                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3197                         if (looking_at(buf, &i, "*% ")) {
3198                             savingComment = FALSE;
3199                         }
3200                     }
3201                     next_out = i;
3202                 } else if (started == STARTED_HOLDINGS) {
3203                     int gamenum;
3204                     char new_piece[MSG_SIZ];
3205                     started = STARTED_NONE;
3206                     parse[parse_pos] = NULLCHAR;
3207                     if (appData.debugMode)
3208                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3209                                                         parse, currentMove);
3210                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3211                         gamenum == ics_gamenum) {
3212                         if (gameInfo.variant == VariantNormal) {
3213                           /* [HGM] We seem to switch variant during a game!
3214                            * Presumably no holdings were displayed, so we have
3215                            * to move the position two files to the right to
3216                            * create room for them!
3217                            */
3218                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3219                           /* Get a move list just to see the header, which
3220                              will tell us whether this is really bug or zh */
3221                           if (ics_getting_history == H_FALSE) {
3222                             ics_getting_history = H_REQUESTED;
3223                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3224                             SendToICS(str);
3225                           }
3226                         }
3227                         new_piece[0] = NULLCHAR;
3228                         sscanf(parse, "game %d white [%s black [%s <- %s",
3229                                &gamenum, white_holding, black_holding,
3230                                new_piece);
3231                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3232                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3233                         /* [HGM] copy holdings to board holdings area */
3234                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3235                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3236 #if ZIPPY
3237                         if (appData.zippyPlay && first.initDone) {
3238                             ZippyHoldings(white_holding, black_holding,
3239                                           new_piece);
3240                         }
3241 #endif /*ZIPPY*/
3242                         if (tinyLayout || smallLayout) {
3243                             char wh[16], bh[16];
3244                             PackHolding(wh, white_holding);
3245                             PackHolding(bh, black_holding);
3246                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3247                                     gameInfo.white, gameInfo.black);
3248                         } else {
3249                             sprintf(str, "%s [%s] vs. %s [%s]",
3250                                     gameInfo.white, white_holding,
3251                                     gameInfo.black, black_holding);
3252                         }
3253
3254                         DrawPosition(FALSE, boards[currentMove]);
3255                         DisplayTitle(str);
3256                     }
3257                     /* Suppress following prompt */
3258                     if (looking_at(buf, &i, "*% ")) {
3259                         savingComment = FALSE;
3260                     }
3261                     next_out = i;
3262                 }
3263                 continue;
3264             }
3265
3266             i++;                /* skip unparsed character and loop back */
3267         }
3268         
3269         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3270             started != STARTED_HOLDINGS && i > next_out) {
3271             SendToPlayer(&buf[next_out], i - next_out);
3272             next_out = i;
3273         }
3274         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3275         
3276         leftover_len = buf_len - leftover_start;
3277         /* if buffer ends with something we couldn't parse,
3278            reparse it after appending the next read */
3279         
3280     } else if (count == 0) {
3281         RemoveInputSource(isr);
3282         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3283     } else {
3284         DisplayFatalError(_("Error reading from ICS"), error, 1);
3285     }
3286 }
3287
3288
3289 /* Board style 12 looks like this:
3290    
3291    <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
3292    
3293  * The "<12> " is stripped before it gets to this routine.  The two
3294  * trailing 0's (flip state and clock ticking) are later addition, and
3295  * some chess servers may not have them, or may have only the first.
3296  * Additional trailing fields may be added in the future.  
3297  */
3298
3299 #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"
3300
3301 #define RELATION_OBSERVING_PLAYED    0
3302 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3303 #define RELATION_PLAYING_MYMOVE      1
3304 #define RELATION_PLAYING_NOTMYMOVE  -1
3305 #define RELATION_EXAMINING           2
3306 #define RELATION_ISOLATED_BOARD     -3
3307 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3308
3309 void
3310 ParseBoard12(string)
3311      char *string;
3312
3313     GameMode newGameMode;
3314     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3315     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3316     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3317     char to_play, board_chars[200];
3318     char move_str[500], str[500], elapsed_time[500];
3319     char black[32], white[32];
3320     Board board;
3321     int prevMove = currentMove;
3322     int ticking = 2;
3323     ChessMove moveType;
3324     int fromX, fromY, toX, toY;
3325     char promoChar;
3326     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3327     char *bookHit = NULL; // [HGM] book
3328
3329     fromX = fromY = toX = toY = -1;
3330     
3331     newGame = FALSE;
3332
3333     if (appData.debugMode)
3334       fprintf(debugFP, _("Parsing board: %s\n"), string);
3335
3336     move_str[0] = NULLCHAR;
3337     elapsed_time[0] = NULLCHAR;
3338     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3339         int  i = 0, j;
3340         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3341             if(string[i] == ' ') { ranks++; files = 0; }
3342             else files++;
3343             i++;
3344         }
3345         for(j = 0; j <i; j++) board_chars[j] = string[j];
3346         board_chars[i] = '\0';
3347         string += i + 1;
3348     }
3349     n = sscanf(string, PATTERN, &to_play, &double_push,
3350                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3351                &gamenum, white, black, &relation, &basetime, &increment,
3352                &white_stren, &black_stren, &white_time, &black_time,
3353                &moveNum, str, elapsed_time, move_str, &ics_flip,
3354                &ticking);
3355
3356     if (n < 21) {
3357         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3358         DisplayError(str, 0);
3359         return;
3360     }
3361
3362     /* Convert the move number to internal form */
3363     moveNum = (moveNum - 1) * 2;
3364     if (to_play == 'B') moveNum++;
3365     if (moveNum >= MAX_MOVES) {
3366       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3367                         0, 1);
3368       return;
3369     }
3370     
3371     switch (relation) {
3372       case RELATION_OBSERVING_PLAYED:
3373       case RELATION_OBSERVING_STATIC:
3374         if (gamenum == -1) {
3375             /* Old ICC buglet */
3376             relation = RELATION_OBSERVING_STATIC;
3377         }
3378         newGameMode = IcsObserving;
3379         break;
3380       case RELATION_PLAYING_MYMOVE:
3381       case RELATION_PLAYING_NOTMYMOVE:
3382         newGameMode =
3383           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3384             IcsPlayingWhite : IcsPlayingBlack;
3385         break;
3386       case RELATION_EXAMINING:
3387         newGameMode = IcsExamining;
3388         break;
3389       case RELATION_ISOLATED_BOARD:
3390       default:
3391         /* Just display this board.  If user was doing something else,
3392            we will forget about it until the next board comes. */ 
3393         newGameMode = IcsIdle;
3394         break;
3395       case RELATION_STARTING_POSITION:
3396         newGameMode = gameMode;
3397         break;
3398     }
3399     
3400     /* Modify behavior for initial board display on move listing
3401        of wild games.
3402        */
3403     switch (ics_getting_history) {
3404       case H_FALSE:
3405       case H_REQUESTED:
3406         break;
3407       case H_GOT_REQ_HEADER:
3408       case H_GOT_UNREQ_HEADER:
3409         /* This is the initial position of the current game */
3410         gamenum = ics_gamenum;
3411         moveNum = 0;            /* old ICS bug workaround */
3412         if (to_play == 'B') {
3413           startedFromSetupPosition = TRUE;
3414           blackPlaysFirst = TRUE;
3415           moveNum = 1;
3416           if (forwardMostMove == 0) forwardMostMove = 1;
3417           if (backwardMostMove == 0) backwardMostMove = 1;
3418           if (currentMove == 0) currentMove = 1;
3419         }
3420         newGameMode = gameMode;
3421         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3422         break;
3423       case H_GOT_UNWANTED_HEADER:
3424         /* This is an initial board that we don't want */
3425         return;
3426       case H_GETTING_MOVES:
3427         /* Should not happen */
3428         DisplayError(_("Error gathering move list: extra board"), 0);
3429         ics_getting_history = H_FALSE;
3430         return;
3431     }
3432     
3433     /* Take action if this is the first board of a new game, or of a
3434        different game than is currently being displayed.  */
3435     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3436         relation == RELATION_ISOLATED_BOARD) {
3437         
3438         /* Forget the old game and get the history (if any) of the new one */
3439         if (gameMode != BeginningOfGame) {
3440           Reset(FALSE, TRUE);
3441         }
3442         newGame = TRUE;
3443         if (appData.autoRaiseBoard) BoardToTop();
3444         prevMove = -3;
3445         if (gamenum == -1) {
3446             newGameMode = IcsIdle;
3447         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3448                    appData.getMoveList) {
3449             /* Need to get game history */
3450             ics_getting_history = H_REQUESTED;
3451             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3452             SendToICS(str);
3453         }
3454         
3455         /* Initially flip the board to have black on the bottom if playing
3456            black or if the ICS flip flag is set, but let the user change
3457            it with the Flip View button. */
3458         flipView = appData.autoFlipView ? 
3459           (newGameMode == IcsPlayingBlack) || ics_flip :
3460           appData.flipView;
3461         
3462         /* Done with values from previous mode; copy in new ones */
3463         gameMode = newGameMode;
3464         ModeHighlight();
3465         ics_gamenum = gamenum;
3466         if (gamenum == gs_gamenum) {
3467             int klen = strlen(gs_kind);
3468             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3469             sprintf(str, "ICS %s", gs_kind);
3470             gameInfo.event = StrSave(str);
3471         } else {
3472             gameInfo.event = StrSave("ICS game");
3473         }
3474         gameInfo.site = StrSave(appData.icsHost);
3475         gameInfo.date = PGNDate();
3476         gameInfo.round = StrSave("-");
3477         gameInfo.white = StrSave(white);
3478         gameInfo.black = StrSave(black);
3479         timeControl = basetime * 60 * 1000;
3480         timeControl_2 = 0;
3481         timeIncrement = increment * 1000;
3482         movesPerSession = 0;
3483         gameInfo.timeControl = TimeControlTagValue();
3484         VariantSwitch(board, StringToVariant(gameInfo.event) );
3485   if (appData.debugMode) {
3486     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3487     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3488     setbuf(debugFP, NULL);
3489   }
3490
3491         gameInfo.outOfBook = NULL;
3492         
3493         /* Do we have the ratings? */
3494         if (strcmp(player1Name, white) == 0 &&
3495             strcmp(player2Name, black) == 0) {
3496             if (appData.debugMode)
3497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3498                       player1Rating, player2Rating);
3499             gameInfo.whiteRating = player1Rating;
3500             gameInfo.blackRating = player2Rating;
3501         } else if (strcmp(player2Name, white) == 0 &&
3502                    strcmp(player1Name, black) == 0) {
3503             if (appData.debugMode)
3504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505                       player2Rating, player1Rating);
3506             gameInfo.whiteRating = player2Rating;
3507             gameInfo.blackRating = player1Rating;
3508         }
3509         player1Name[0] = player2Name[0] = NULLCHAR;
3510
3511         /* Silence shouts if requested */
3512         if (appData.quietPlay &&
3513             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3514             SendToICS(ics_prefix);
3515             SendToICS("set shout 0\n");
3516         }
3517     }
3518     
3519     /* Deal with midgame name changes */
3520     if (!newGame) {
3521         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3522             if (gameInfo.white) free(gameInfo.white);
3523             gameInfo.white = StrSave(white);
3524         }
3525         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3526             if (gameInfo.black) free(gameInfo.black);
3527             gameInfo.black = StrSave(black);
3528         }
3529     }
3530     
3531     /* Throw away game result if anything actually changes in examine mode */
3532     if (gameMode == IcsExamining && !newGame) {
3533         gameInfo.result = GameUnfinished;
3534         if (gameInfo.resultDetails != NULL) {
3535             free(gameInfo.resultDetails);
3536             gameInfo.resultDetails = NULL;
3537         }
3538     }
3539     
3540     /* In pausing && IcsExamining mode, we ignore boards coming
3541        in if they are in a different variation than we are. */
3542     if (pauseExamInvalid) return;
3543     if (pausing && gameMode == IcsExamining) {
3544         if (moveNum <= pauseExamForwardMostMove) {
3545             pauseExamInvalid = TRUE;
3546             forwardMostMove = pauseExamForwardMostMove;
3547             return;
3548         }
3549     }
3550     
3551   if (appData.debugMode) {
3552     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3553   }
3554     /* Parse the board */
3555     for (k = 0; k < ranks; k++) {
3556       for (j = 0; j < files; j++)
3557         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3558       if(gameInfo.holdingsWidth > 1) {
3559            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3560            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3561       }
3562     }
3563     CopyBoard(boards[moveNum], board);
3564     if (moveNum == 0) {
3565         startedFromSetupPosition =
3566           !CompareBoards(board, initialPosition);
3567         if(startedFromSetupPosition)
3568             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3569     }
3570
3571     /* [HGM] Set castling rights. Take the outermost Rooks,
3572        to make it also work for FRC opening positions. Note that board12
3573        is really defective for later FRC positions, as it has no way to
3574        indicate which Rook can castle if they are on the same side of King.
3575        For the initial position we grant rights to the outermost Rooks,
3576        and remember thos rights, and we then copy them on positions
3577        later in an FRC game. This means WB might not recognize castlings with
3578        Rooks that have moved back to their original position as illegal,
3579        but in ICS mode that is not its job anyway.
3580     */
3581     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3582     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3583
3584         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3585             if(board[0][i] == WhiteRook) j = i;
3586         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3588             if(board[0][i] == WhiteRook) j = i;
3589         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3590         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3591             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3592         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3593         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3594             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3595         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3596
3597         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3598         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3599             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3600         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601             if(board[BOARD_HEIGHT-1][k] == bKing)
3602                 initialRights[5] = castlingRights[moveNum][5] = k;
3603     } else { int r;
3604         r = castlingRights[moveNum][0] = initialRights[0];
3605         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3606         r = castlingRights[moveNum][1] = initialRights[1];
3607         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3608         r = castlingRights[moveNum][3] = initialRights[3];
3609         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3610         r = castlingRights[moveNum][4] = initialRights[4];
3611         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3612         /* wildcastle kludge: always assume King has rights */
3613         r = castlingRights[moveNum][2] = initialRights[2];
3614         r = castlingRights[moveNum][5] = initialRights[5];
3615     }
3616     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3617     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3618
3619     
3620     if (ics_getting_history == H_GOT_REQ_HEADER ||
3621         ics_getting_history == H_GOT_UNREQ_HEADER) {
3622         /* This was an initial position from a move list, not
3623            the current position */
3624         return;
3625     }
3626     
3627     /* Update currentMove and known move number limits */
3628     newMove = newGame || moveNum > forwardMostMove;
3629
3630     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3631     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3632         takeback = forwardMostMove - moveNum;
3633         for (i = 0; i < takeback; i++) {
3634              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3635              SendToProgram("undo\n", &first);
3636         }
3637     }
3638
3639     if (newGame) {
3640         forwardMostMove = backwardMostMove = currentMove = moveNum;
3641         if (gameMode == IcsExamining && moveNum == 0) {
3642           /* Workaround for ICS limitation: we are not told the wild
3643              type when starting to examine a game.  But if we ask for
3644              the move list, the move list header will tell us */
3645             ics_getting_history = H_REQUESTED;
3646             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3647             SendToICS(str);
3648         }
3649     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3650                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3651         forwardMostMove = moveNum;
3652         if (!pausing || currentMove > forwardMostMove)
3653           currentMove = forwardMostMove;
3654     } else {
3655         /* New part of history that is not contiguous with old part */ 
3656         if (pausing && gameMode == IcsExamining) {
3657             pauseExamInvalid = TRUE;
3658             forwardMostMove = pauseExamForwardMostMove;
3659             return;
3660         }
3661         forwardMostMove = backwardMostMove = currentMove = moveNum;
3662         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3663             ics_getting_history = H_REQUESTED;
3664             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3665             SendToICS(str);
3666         }
3667     }
3668     
3669     /* Update the clocks */
3670     if (strchr(elapsed_time, '.')) {
3671       /* Time is in ms */
3672       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3673       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3674     } else {
3675       /* Time is in seconds */
3676       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3677       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3678     }
3679       
3680
3681 #if ZIPPY
3682     if (appData.zippyPlay && newGame &&
3683         gameMode != IcsObserving && gameMode != IcsIdle &&
3684         gameMode != IcsExamining)
3685       ZippyFirstBoard(moveNum, basetime, increment);
3686 #endif
3687     
3688     /* Put the move on the move list, first converting
3689        to canonical algebraic form. */
3690     if (moveNum > 0) {
3691   if (appData.debugMode) {
3692     if (appData.debugMode) { int f = forwardMostMove;
3693         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3694                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3695     }
3696     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3697     fprintf(debugFP, "moveNum = %d\n", moveNum);
3698     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3699     setbuf(debugFP, NULL);
3700   }
3701         if (moveNum <= backwardMostMove) {
3702             /* We don't know what the board looked like before
3703                this move.  Punt. */
3704             strcpy(parseList[moveNum - 1], move_str);
3705             strcat(parseList[moveNum - 1], " ");
3706             strcat(parseList[moveNum - 1], elapsed_time);
3707             moveList[moveNum - 1][0] = NULLCHAR;
3708         } else if (strcmp(move_str, "none") == 0) {
3709             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3710             /* Again, we don't know what the board looked like;
3711                this is really the start of the game. */
3712             parseList[moveNum - 1][0] = NULLCHAR;
3713             moveList[moveNum - 1][0] = NULLCHAR;
3714             backwardMostMove = moveNum;
3715             startedFromSetupPosition = TRUE;
3716             fromX = fromY = toX = toY = -1;
3717         } else {
3718           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3719           //                 So we parse the long-algebraic move string in stead of the SAN move
3720           int valid; char buf[MSG_SIZ], *prom;
3721
3722           // str looks something like "Q/a1-a2"; kill the slash
3723           if(str[1] == '/') 
3724                 sprintf(buf, "%c%s", str[0], str+2);
3725           else  strcpy(buf, str); // might be castling
3726           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3727                 strcat(buf, prom); // long move lacks promo specification!
3728           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3729                 if(appData.debugMode) 
3730                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3731                 strcpy(move_str, buf);
3732           }
3733           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3734                                 &fromX, &fromY, &toX, &toY, &promoChar)
3735                || ParseOneMove(buf, moveNum - 1, &moveType,
3736                                 &fromX, &fromY, &toX, &toY, &promoChar);
3737           // end of long SAN patch
3738           if (valid) {
3739             (void) CoordsToAlgebraic(boards[moveNum - 1],
3740                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3741                                      fromY, fromX, toY, toX, promoChar,
3742                                      parseList[moveNum-1]);
3743             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3744                              castlingRights[moveNum]) ) {
3745               case MT_NONE:
3746               case MT_STALEMATE:
3747               default:
3748                 break;
3749               case MT_CHECK:
3750                 if(gameInfo.variant != VariantShogi)
3751                     strcat(parseList[moveNum - 1], "+");
3752                 break;
3753               case MT_CHECKMATE:
3754               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3755                 strcat(parseList[moveNum - 1], "#");
3756                 break;
3757             }
3758             strcat(parseList[moveNum - 1], " ");
3759             strcat(parseList[moveNum - 1], elapsed_time);
3760             /* currentMoveString is set as a side-effect of ParseOneMove */
3761             strcpy(moveList[moveNum - 1], currentMoveString);
3762             strcat(moveList[moveNum - 1], "\n");
3763           } else {
3764             /* Move from ICS was illegal!?  Punt. */
3765   if (appData.debugMode) {
3766     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3767     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3768   }
3769 #if 0
3770             if (appData.testLegality && appData.debugMode) {
3771                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3772                 DisplayError(str, 0);
3773             }
3774 #endif
3775             strcpy(parseList[moveNum - 1], move_str);
3776             strcat(parseList[moveNum - 1], " ");
3777             strcat(parseList[moveNum - 1], elapsed_time);
3778             moveList[moveNum - 1][0] = NULLCHAR;
3779             fromX = fromY = toX = toY = -1;
3780           }
3781         }
3782   if (appData.debugMode) {
3783     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3784     setbuf(debugFP, NULL);
3785   }
3786
3787 #if ZIPPY
3788         /* Send move to chess program (BEFORE animating it). */
3789         if (appData.zippyPlay && !newGame && newMove && 
3790            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791
3792             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3793                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3794                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3795                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796                             move_str);
3797                     DisplayError(str, 0);
3798                 } else {
3799                     if (first.sendTime) {
3800                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801                     }
3802                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3803                     if (firstMove && !bookHit) {
3804                         firstMove = FALSE;
3805                         if (first.useColors) {
3806                           SendToProgram(gameMode == IcsPlayingWhite ?
3807                                         "white\ngo\n" :
3808                                         "black\ngo\n", &first);
3809                         } else {
3810                           SendToProgram("go\n", &first);
3811                         }
3812                         first.maybeThinking = TRUE;
3813                     }
3814                 }
3815             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3816               if (moveList[moveNum - 1][0] == NULLCHAR) {
3817                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3818                 DisplayError(str, 0);
3819               } else {
3820                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3821                 SendMoveToProgram(moveNum - 1, &first);
3822               }
3823             }
3824         }
3825 #endif
3826     }
3827
3828     if (moveNum > 0 && !gotPremove) {
3829         /* If move comes from a remote source, animate it.  If it
3830            isn't remote, it will have already been animated. */
3831         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3832             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3833         }
3834         if (!pausing && appData.highlightLastMove) {
3835             SetHighlights(fromX, fromY, toX, toY);
3836         }
3837     }
3838     
3839     /* Start the clocks */
3840     whiteFlag = blackFlag = FALSE;
3841     appData.clockMode = !(basetime == 0 && increment == 0);
3842     if (ticking == 0) {
3843       ics_clock_paused = TRUE;
3844       StopClocks();
3845     } else if (ticking == 1) {
3846       ics_clock_paused = FALSE;
3847     }
3848     if (gameMode == IcsIdle ||
3849         relation == RELATION_OBSERVING_STATIC ||
3850         relation == RELATION_EXAMINING ||
3851         ics_clock_paused)
3852       DisplayBothClocks();
3853     else
3854       StartClocks();
3855     
3856     /* Display opponents and material strengths */
3857     if (gameInfo.variant != VariantBughouse &&
3858         gameInfo.variant != VariantCrazyhouse) {
3859         if (tinyLayout || smallLayout) {
3860             if(gameInfo.variant == VariantNormal)
3861                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3862                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3863                     basetime, increment);
3864             else
3865                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3866                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3867                     basetime, increment, (int) gameInfo.variant);
3868         } else {
3869             if(gameInfo.variant == VariantNormal)
3870                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3872                     basetime, increment);
3873             else
3874                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3876                     basetime, increment, VariantName(gameInfo.variant));
3877         }
3878         DisplayTitle(str);
3879   if (appData.debugMode) {
3880     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3881   }
3882     }
3883
3884    
3885     /* Display the board */
3886     if (!pausing) {
3887       
3888       if (appData.premove)
3889           if (!gotPremove || 
3890              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3891              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3892               ClearPremoveHighlights();
3893
3894       DrawPosition(FALSE, boards[currentMove]);
3895       DisplayMove(moveNum - 1);
3896       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3897             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3898               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3899         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3900       }
3901     }
3902
3903     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3904 #if ZIPPY
3905     if(bookHit) { // [HGM] book: simulate book reply
3906         static char bookMove[MSG_SIZ]; // a bit generous?
3907
3908         programStats.nodes = programStats.depth = programStats.time = 
3909         programStats.score = programStats.got_only_move = 0;
3910         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3911
3912         strcpy(bookMove, "move ");
3913         strcat(bookMove, bookHit);
3914         HandleMachineMove(bookMove, &first);
3915     }
3916 #endif
3917 }
3918
3919 void
3920 GetMoveListEvent()
3921 {
3922     char buf[MSG_SIZ];
3923     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3924         ics_getting_history = H_REQUESTED;
3925         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3926         SendToICS(buf);
3927     }
3928 }
3929
3930 void
3931 AnalysisPeriodicEvent(force)
3932      int force;
3933 {
3934     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3935          && !force) || !appData.periodicUpdates)
3936       return;
3937
3938     /* Send . command to Crafty to collect stats */
3939     SendToProgram(".\n", &first);
3940
3941     /* Don't send another until we get a response (this makes
3942        us stop sending to old Crafty's which don't understand
3943        the "." command (sending illegal cmds resets node count & time,
3944        which looks bad)) */
3945     programStats.ok_to_send = 0;
3946 }
3947
3948 void
3949 SendMoveToProgram(moveNum, cps)
3950      int moveNum;
3951      ChessProgramState *cps;
3952 {
3953     char buf[MSG_SIZ];
3954
3955     if (cps->useUsermove) {
3956       SendToProgram("usermove ", cps);
3957     }
3958     if (cps->useSAN) {
3959       char *space;
3960       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3961         int len = space - parseList[moveNum];
3962         memcpy(buf, parseList[moveNum], len);
3963         buf[len++] = '\n';
3964         buf[len] = NULLCHAR;
3965       } else {
3966         sprintf(buf, "%s\n", parseList[moveNum]);
3967       }
3968       SendToProgram(buf, cps);
3969     } else {
3970       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3971         AlphaRank(moveList[moveNum], 4);
3972         SendToProgram(moveList[moveNum], cps);
3973         AlphaRank(moveList[moveNum], 4); // and back
3974       } else
3975       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3976        * the engine. It would be nice to have a better way to identify castle 
3977        * moves here. */
3978       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3979                                                                          && cps->useOOCastle) {
3980         int fromX = moveList[moveNum][0] - AAA; 
3981         int fromY = moveList[moveNum][1] - ONE;
3982         int toX = moveList[moveNum][2] - AAA; 
3983         int toY = moveList[moveNum][3] - ONE;
3984         if((boards[moveNum][fromY][fromX] == WhiteKing 
3985             && boards[moveNum][toY][toX] == WhiteRook)
3986            || (boards[moveNum][fromY][fromX] == BlackKing 
3987                && boards[moveNum][toY][toX] == BlackRook)) {
3988           if(toX > fromX) SendToProgram("O-O\n", cps);
3989           else SendToProgram("O-O-O\n", cps);
3990         }
3991         else SendToProgram(moveList[moveNum], cps);
3992       }
3993       else SendToProgram(moveList[moveNum], cps);
3994       /* End of additions by Tord */
3995     }
3996
3997     /* [HGM] setting up the opening has brought engine in force mode! */
3998     /*       Send 'go' if we are in a mode where machine should play. */
3999     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4000         (gameMode == TwoMachinesPlay   ||
4001 #ifdef ZIPPY
4002          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4003 #endif
4004          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4005         SendToProgram("go\n", cps);
4006   if (appData.debugMode) {
4007     fprintf(debugFP, "(extra)\n");
4008   }
4009     }
4010     setboardSpoiledMachineBlack = 0;
4011 }
4012
4013 void
4014 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4015      ChessMove moveType;
4016      int fromX, fromY, toX, toY;
4017 {
4018     char user_move[MSG_SIZ];
4019
4020     switch (moveType) {
4021       default:
4022         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4023                 (int)moveType, fromX, fromY, toX, toY);
4024         DisplayError(user_move + strlen("say "), 0);
4025         break;
4026       case WhiteKingSideCastle:
4027       case BlackKingSideCastle:
4028       case WhiteQueenSideCastleWild:
4029       case BlackQueenSideCastleWild:
4030       /* PUSH Fabien */
4031       case WhiteHSideCastleFR:
4032       case BlackHSideCastleFR:
4033       /* POP Fabien */
4034         sprintf(user_move, "o-o\n");
4035         break;
4036       case WhiteQueenSideCastle:
4037       case BlackQueenSideCastle:
4038       case WhiteKingSideCastleWild:
4039       case BlackKingSideCastleWild:
4040       /* PUSH Fabien */
4041       case WhiteASideCastleFR:
4042       case BlackASideCastleFR:
4043       /* POP Fabien */
4044         sprintf(user_move, "o-o-o\n");
4045         break;
4046       case WhitePromotionQueen:
4047       case BlackPromotionQueen:
4048       case WhitePromotionRook:
4049       case BlackPromotionRook:
4050       case WhitePromotionBishop:
4051       case BlackPromotionBishop:
4052       case WhitePromotionKnight:
4053       case BlackPromotionKnight:
4054       case WhitePromotionKing:
4055       case BlackPromotionKing:
4056       case WhitePromotionChancellor:
4057       case BlackPromotionChancellor:
4058       case WhitePromotionArchbishop:
4059       case BlackPromotionArchbishop:
4060         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4061             sprintf(user_move, "%c%c%c%c=%c\n",
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063                 PieceToChar(WhiteFerz));
4064         else if(gameInfo.variant == VariantGreat)
4065             sprintf(user_move, "%c%c%c%c=%c\n",
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067                 PieceToChar(WhiteMan));
4068         else
4069             sprintf(user_move, "%c%c%c%c=%c\n",
4070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071                 PieceToChar(PromoPiece(moveType)));
4072         break;
4073       case WhiteDrop:
4074       case BlackDrop:
4075         sprintf(user_move, "%c@%c%c\n",
4076                 ToUpper(PieceToChar((ChessSquare) fromX)),
4077                 AAA + toX, ONE + toY);
4078         break;
4079       case NormalMove:
4080       case WhiteCapturesEnPassant:
4081       case BlackCapturesEnPassant:
4082       case IllegalMove:  /* could be a variant we don't quite understand */
4083         sprintf(user_move, "%c%c%c%c\n",
4084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4085         break;
4086     }
4087     SendToICS(user_move);
4088 }
4089
4090 void
4091 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4092      int rf, ff, rt, ft;
4093      char promoChar;
4094      char move[7];
4095 {
4096     if (rf == DROP_RANK) {
4097         sprintf(move, "%c@%c%c\n",
4098                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4099     } else {
4100         if (promoChar == 'x' || promoChar == NULLCHAR) {
4101             sprintf(move, "%c%c%c%c\n",
4102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4103         } else {
4104             sprintf(move, "%c%c%c%c%c\n",
4105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4106         }
4107     }
4108 }
4109
4110 void
4111 ProcessICSInitScript(f)
4112      FILE *f;
4113 {
4114     char buf[MSG_SIZ];
4115
4116     while (fgets(buf, MSG_SIZ, f)) {
4117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4118     }
4119
4120     fclose(f);
4121 }
4122
4123
4124 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4125 void
4126 AlphaRank(char *move, int n)
4127 {
4128 //    char *p = move, c; int x, y;
4129
4130     if (appData.debugMode) {
4131         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4132     }
4133
4134     if(move[1]=='*' && 
4135        move[2]>='0' && move[2]<='9' &&
4136        move[3]>='a' && move[3]<='x'    ) {
4137         move[1] = '@';
4138         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4139         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4140     } else
4141     if(move[0]>='0' && move[0]<='9' &&
4142        move[1]>='a' && move[1]<='x' &&
4143        move[2]>='0' && move[2]<='9' &&
4144        move[3]>='a' && move[3]<='x'    ) {
4145         /* input move, Shogi -> normal */
4146         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4147         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4148         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4149         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4150     } else
4151     if(move[1]=='@' &&
4152        move[3]>='0' && move[3]<='9' &&
4153        move[2]>='a' && move[2]<='x'    ) {
4154         move[1] = '*';
4155         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4156         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4157     } else
4158     if(
4159        move[0]>='a' && move[0]<='x' &&
4160        move[3]>='0' && move[3]<='9' &&
4161        move[2]>='a' && move[2]<='x'    ) {
4162          /* output move, normal -> Shogi */
4163         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4164         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4165         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4166         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4167         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4168     }
4169     if (appData.debugMode) {
4170         fprintf(debugFP, "   out = '%s'\n", move);
4171     }
4172 }
4173
4174 /* Parser for moves from gnuchess, ICS, or user typein box */
4175 Boolean
4176 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4177      char *move;
4178      int moveNum;
4179      ChessMove *moveType;
4180      int *fromX, *fromY, *toX, *toY;
4181      char *promoChar;
4182 {       
4183     if (appData.debugMode) {
4184         fprintf(debugFP, "move to parse: %s\n", move);
4185     }
4186     *moveType = yylexstr(moveNum, move);
4187
4188     switch (*moveType) {
4189       case WhitePromotionChancellor:
4190       case BlackPromotionChancellor:
4191       case WhitePromotionArchbishop:
4192       case BlackPromotionArchbishop:
4193       case WhitePromotionQueen:
4194       case BlackPromotionQueen:
4195       case WhitePromotionRook:
4196       case BlackPromotionRook:
4197       case WhitePromotionBishop:
4198       case BlackPromotionBishop:
4199       case WhitePromotionKnight:
4200       case BlackPromotionKnight:
4201       case WhitePromotionKing:
4202       case BlackPromotionKing:
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case WhiteKingSideCastle:
4207       case WhiteQueenSideCastle:
4208       case BlackKingSideCastle:
4209       case BlackQueenSideCastle:
4210       case WhiteKingSideCastleWild:
4211       case WhiteQueenSideCastleWild:
4212       case BlackKingSideCastleWild:
4213       case BlackQueenSideCastleWild:
4214       /* Code added by Tord: */
4215       case WhiteHSideCastleFR:
4216       case WhiteASideCastleFR:
4217       case BlackHSideCastleFR:
4218       case BlackASideCastleFR:
4219       /* End of code added by Tord */
4220       case IllegalMove:         /* bug or odd chess variant */
4221         *fromX = currentMoveString[0] - AAA;
4222         *fromY = currentMoveString[1] - ONE;
4223         *toX = currentMoveString[2] - AAA;
4224         *toY = currentMoveString[3] - ONE;
4225         *promoChar = currentMoveString[4];
4226         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4227             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4228     if (appData.debugMode) {
4229         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4230     }
4231             *fromX = *fromY = *toX = *toY = 0;
4232             return FALSE;
4233         }
4234         if (appData.testLegality) {
4235           return (*moveType != IllegalMove);
4236         } else {
4237           return !(fromX == fromY && toX == toY);
4238         }
4239
4240       case WhiteDrop:
4241       case BlackDrop:
4242         *fromX = *moveType == WhiteDrop ?
4243           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4244           (int) CharToPiece(ToLower(currentMoveString[0]));
4245         *fromY = DROP_RANK;
4246         *toX = currentMoveString[2] - AAA;
4247         *toY = currentMoveString[3] - ONE;
4248         *promoChar = NULLCHAR;
4249         return TRUE;
4250
4251       case AmbiguousMove:
4252       case ImpossibleMove:
4253       case (ChessMove) 0:       /* end of file */
4254       case ElapsedTime:
4255       case Comment:
4256       case PGNTag:
4257       case NAG:
4258       case WhiteWins:
4259       case BlackWins:
4260       case GameIsDrawn:
4261       default:
4262     if (appData.debugMode) {
4263         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4264     }
4265         /* bug? */
4266         *fromX = *fromY = *toX = *toY = 0;
4267         *promoChar = NULLCHAR;
4268         return FALSE;
4269     }
4270 }
4271
4272 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4273 // All positions will have equal probability, but the current method will not provide a unique
4274 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4275 #define DARK 1
4276 #define LITE 2
4277 #define ANY 3
4278
4279 int squaresLeft[4];
4280 int piecesLeft[(int)BlackPawn];
4281 int seed, nrOfShuffles;
4282
4283 void GetPositionNumber()
4284 {       // sets global variable seed
4285         int i;
4286
4287         seed = appData.defaultFrcPosition;
4288         if(seed < 0) { // randomize based on time for negative FRC position numbers
4289                 for(i=0; i<50; i++) seed += random();
4290                 seed = random() ^ random() >> 8 ^ random() << 8;
4291                 if(seed<0) seed = -seed;
4292         }
4293 }
4294
4295 int put(Board board, int pieceType, int rank, int n, int shade)
4296 // put the piece on the (n-1)-th empty squares of the given shade
4297 {
4298         int i;
4299
4300         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4301                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4302                         board[rank][i] = (ChessSquare) pieceType;
4303                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4304                         squaresLeft[ANY]--;
4305                         piecesLeft[pieceType]--; 
4306                         return i;
4307                 }
4308         }
4309         return -1;
4310 }
4311
4312
4313 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4314 // calculate where the next piece goes, (any empty square), and put it there
4315 {
4316         int i;
4317
4318         i = seed % squaresLeft[shade];
4319         nrOfShuffles *= squaresLeft[shade];
4320         seed /= squaresLeft[shade];
4321         put(board, pieceType, rank, i, shade);
4322 }
4323
4324 void AddTwoPieces(Board board, int pieceType, int rank)
4325 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4326 {
4327         int i, n=squaresLeft[ANY], j=n-1, k;
4328
4329         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4330         i = seed % k;  // pick one
4331         nrOfShuffles *= k;
4332         seed /= k;
4333         while(i >= j) i -= j--;
4334         j = n - 1 - j; i += j;
4335         put(board, pieceType, rank, j, ANY);
4336         put(board, pieceType, rank, i, ANY);
4337 }
4338
4339 void SetUpShuffle(Board board, int number)
4340 {
4341         int i, p, first=1;
4342
4343         GetPositionNumber(); nrOfShuffles = 1;
4344
4345         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4346         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4347         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4348
4349         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4350
4351         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4352             p = (int) board[0][i];
4353             if(p < (int) BlackPawn) piecesLeft[p] ++;
4354             board[0][i] = EmptySquare;
4355         }
4356
4357         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4358             // shuffles restricted to allow normal castling put KRR first
4359             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4360                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4361             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4362                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4363             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4364                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4365             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4366                 put(board, WhiteRook, 0, 0, ANY);
4367             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4368         }
4369
4370         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4371             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4372             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4373                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4374                 while(piecesLeft[p] >= 2) {
4375                     AddOnePiece(board, p, 0, LITE);
4376                     AddOnePiece(board, p, 0, DARK);
4377                 }
4378                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4379             }
4380
4381         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4382             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4383             // but we leave King and Rooks for last, to possibly obey FRC restriction
4384             if(p == (int)WhiteRook) continue;
4385             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4386             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4387         }
4388
4389         // now everything is placed, except perhaps King (Unicorn) and Rooks
4390
4391         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4392             // Last King gets castling rights
4393             while(piecesLeft[(int)WhiteUnicorn]) {
4394                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4395                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4396             }
4397
4398             while(piecesLeft[(int)WhiteKing]) {
4399                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4400                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4401             }
4402
4403
4404         } else {
4405             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4406             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4407         }
4408
4409         // Only Rooks can be left; simply place them all
4410         while(piecesLeft[(int)WhiteRook]) {
4411                 i = put(board, WhiteRook, 0, 0, ANY);
4412                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4413                         if(first) {
4414                                 first=0;
4415                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4416                         }
4417                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4418                 }
4419         }
4420         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4421             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4422         }
4423
4424         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4425 }
4426
4427 int SetCharTable( char *table, const char * map )
4428 /* [HGM] moved here from winboard.c because of its general usefulness */
4429 /*       Basically a safe strcpy that uses the last character as King */
4430 {
4431     int result = FALSE; int NrPieces;
4432
4433     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4434                     && NrPieces >= 12 && !(NrPieces&1)) {
4435         int i; /* [HGM] Accept even length from 12 to 34 */
4436
4437         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4438         for( i=0; i<NrPieces/2-1; i++ ) {
4439             table[i] = map[i];
4440             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4441         }
4442         table[(int) WhiteKing]  = map[NrPieces/2-1];
4443         table[(int) BlackKing]  = map[NrPieces-1];
4444
4445         result = TRUE;
4446     }
4447
4448     return result;
4449 }
4450
4451 void Prelude(Board board)
4452 {       // [HGM] superchess: random selection of exo-pieces
4453         int i, j, k; ChessSquare p; 
4454         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4455
4456         GetPositionNumber(); // use FRC position number
4457
4458         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4459             SetCharTable(pieceToChar, appData.pieceToCharTable);
4460             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4461                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4462         }
4463
4464         j = seed%4;                 seed /= 4; 
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 >= j); seed /= 3; 
4469         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3; 
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%2 + (seed%2 >= j); seed /= 2; 
4477         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4478         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4479         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4480         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4481         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4482         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4483         put(board, exoPieces[0],    0, 0, ANY);
4484         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4485 }
4486
4487 void
4488 InitPosition(redraw)
4489      int redraw;
4490 {
4491     ChessSquare (* pieces)[BOARD_SIZE];
4492     int i, j, pawnRow, overrule,
4493     oldx = gameInfo.boardWidth,
4494     oldy = gameInfo.boardHeight,
4495     oldh = gameInfo.holdingsWidth,
4496     oldv = gameInfo.variant;
4497
4498     currentMove = forwardMostMove = backwardMostMove = 0;
4499     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4500
4501     /* [AS] Initialize pv info list [HGM] and game status */
4502     {
4503         for( i=0; i<MAX_MOVES; i++ ) {
4504             pvInfoList[i].depth = 0;
4505             epStatus[i]=EP_NONE;
4506             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4507         }
4508
4509         initialRulePlies = 0; /* 50-move counter start */
4510
4511         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4512         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4513     }
4514
4515     
4516     /* [HGM] logic here is completely changed. In stead of full positions */
4517     /* the initialized data only consist of the two backranks. The switch */
4518     /* selects which one we will use, which is than copied to the Board   */
4519     /* initialPosition, which for the rest is initialized by Pawns and    */
4520     /* empty squares. This initial position is then copied to boards[0],  */
4521     /* possibly after shuffling, so that it remains available.            */
4522
4523     gameInfo.holdingsWidth = 0; /* default board sizes */
4524     gameInfo.boardWidth    = 8;
4525     gameInfo.boardHeight   = 8;
4526     gameInfo.holdingsSize  = 0;
4527     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4528     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4529     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4530
4531     switch (gameInfo.variant) {
4532     case VariantFischeRandom:
4533       shuffleOpenings = TRUE;
4534     default:
4535       pieces = FIDEArray;
4536       break;
4537     case VariantShatranj:
4538       pieces = ShatranjArray;
4539       nrCastlingRights = 0;
4540       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4541       break;
4542     case VariantTwoKings:
4543       pieces = twoKingsArray;
4544       break;
4545     case VariantCapaRandom:
4546       shuffleOpenings = TRUE;
4547     case VariantCapablanca:
4548       pieces = CapablancaArray;
4549       gameInfo.boardWidth = 10;
4550       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4551       break;
4552     case VariantGothic:
4553       pieces = GothicArray;
4554       gameInfo.boardWidth = 10;
4555       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4556       break;
4557     case VariantJanus:
4558       pieces = JanusArray;
4559       gameInfo.boardWidth = 10;
4560       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4561       nrCastlingRights = 6;
4562         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4563         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4564         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4565         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4566         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4567         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4568       break;
4569     case VariantFalcon:
4570       pieces = FalconArray;
4571       gameInfo.boardWidth = 10;
4572       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4573       break;
4574     case VariantXiangqi:
4575       pieces = XiangqiArray;
4576       gameInfo.boardWidth  = 9;
4577       gameInfo.boardHeight = 10;
4578       nrCastlingRights = 0;
4579       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4580       break;
4581     case VariantShogi:
4582       pieces = ShogiArray;
4583       gameInfo.boardWidth  = 9;
4584       gameInfo.boardHeight = 9;
4585       gameInfo.holdingsSize = 7;
4586       nrCastlingRights = 0;
4587       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4588       break;
4589     case VariantCourier:
4590       pieces = CourierArray;
4591       gameInfo.boardWidth  = 12;
4592       nrCastlingRights = 0;
4593       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4594       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4595       break;
4596     case VariantKnightmate:
4597       pieces = KnightmateArray;
4598       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4599       break;
4600     case VariantFairy:
4601       pieces = fairyArray;
4602       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4603       break;
4604     case VariantGreat:
4605       pieces = GreatArray;
4606       gameInfo.boardWidth = 10;
4607       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4608       gameInfo.holdingsSize = 8;
4609       break;
4610     case VariantSuper:
4611       pieces = FIDEArray;
4612       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4613       gameInfo.holdingsSize = 8;
4614       startedFromSetupPosition = TRUE;
4615       break;
4616     case VariantCrazyhouse:
4617     case VariantBughouse:
4618       pieces = FIDEArray;
4619       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4620       gameInfo.holdingsSize = 5;
4621       break;
4622     case VariantWildCastle:
4623       pieces = FIDEArray;
4624       /* !!?shuffle with kings guaranteed to be on d or e file */
4625       shuffleOpenings = 1;
4626       break;
4627     case VariantNoCastle:
4628       pieces = FIDEArray;
4629       nrCastlingRights = 0;
4630       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4631       /* !!?unconstrained back-rank shuffle */
4632       shuffleOpenings = 1;
4633       break;
4634     }
4635
4636     overrule = 0;
4637     if(appData.NrFiles >= 0) {
4638         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4639         gameInfo.boardWidth = appData.NrFiles;
4640     }
4641     if(appData.NrRanks >= 0) {
4642         gameInfo.boardHeight = appData.NrRanks;
4643     }
4644     if(appData.holdingsSize >= 0) {
4645         i = appData.holdingsSize;
4646         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4647         gameInfo.holdingsSize = i;
4648     }
4649     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4650     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4651         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4652
4653     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4654     if(pawnRow < 1) pawnRow = 1;
4655
4656     /* User pieceToChar list overrules defaults */
4657     if(appData.pieceToCharTable != NULL)
4658         SetCharTable(pieceToChar, appData.pieceToCharTable);
4659
4660     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4661
4662         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4663             s = (ChessSquare) 0; /* account holding counts in guard band */
4664         for( i=0; i<BOARD_HEIGHT; i++ )
4665             initialPosition[i][j] = s;
4666
4667         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4668         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4669         initialPosition[pawnRow][j] = WhitePawn;
4670         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4671         if(gameInfo.variant == VariantXiangqi) {
4672             if(j&1) {
4673                 initialPosition[pawnRow][j] = 
4674                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4675                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4676                    initialPosition[2][j] = WhiteCannon;
4677                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4678                 }
4679             }
4680         }
4681         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4682     }
4683     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4684
4685             j=BOARD_LEFT+1;
4686             initialPosition[1][j] = WhiteBishop;
4687             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4688             j=BOARD_RGHT-2;
4689             initialPosition[1][j] = WhiteRook;
4690             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4691     }
4692
4693     if( nrCastlingRights == -1) {
4694         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4695         /*       This sets default castling rights from none to normal corners   */
4696         /* Variants with other castling rights must set them themselves above    */
4697         nrCastlingRights = 6;
4698        
4699         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4700         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4701         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4702         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4703         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4704         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4705      }
4706
4707      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4708      if(gameInfo.variant == VariantGreat) { // promotion commoners
4709         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4710         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4711         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4712         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4713      }
4714 #if 0
4715     if(gameInfo.variant == VariantFischeRandom) {
4716       if( appData.defaultFrcPosition < 0 ) {
4717         ShuffleFRC( initialPosition );
4718       }
4719       else {
4720         SetupFRC( initialPosition, appData.defaultFrcPosition );
4721       }
4722       startedFromSetupPosition = TRUE;
4723     } else 
4724 #else
4725   if (appData.debugMode) {
4726     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4727   }
4728     if(shuffleOpenings) {
4729         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4730         startedFromSetupPosition = TRUE;
4731     }
4732 #endif
4733     if(startedFromPositionFile) {
4734       /* [HGM] loadPos: use PositionFile for every new game */
4735       CopyBoard(initialPosition, filePosition);
4736       for(i=0; i<nrCastlingRights; i++)
4737           castlingRights[0][i] = initialRights[i] = fileRights[i];
4738       startedFromSetupPosition = TRUE;
4739     }
4740
4741     CopyBoard(boards[0], initialPosition);
4742
4743     if(oldx != gameInfo.boardWidth ||
4744        oldy != gameInfo.boardHeight ||
4745        oldh != gameInfo.holdingsWidth
4746 #ifdef GOTHIC
4747        || oldv == VariantGothic ||        // For licensing popups
4748        gameInfo.variant == VariantGothic
4749 #endif
4750 #ifdef FALCON
4751        || oldv == VariantFalcon ||
4752        gameInfo.variant == VariantFalcon
4753 #endif
4754                                          )
4755             InitDrawingSizes(-2 ,0);
4756
4757     if (redraw)
4758       DrawPosition(TRUE, boards[currentMove]);
4759 }
4760
4761 void
4762 SendBoard(cps, moveNum)
4763      ChessProgramState *cps;
4764      int moveNum;
4765 {
4766     char message[MSG_SIZ];
4767     
4768     if (cps->useSetboard) {
4769       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4770       sprintf(message, "setboard %s\n", fen);
4771       SendToProgram(message, cps);
4772       free(fen);
4773
4774     } else {
4775       ChessSquare *bp;
4776       int i, j;
4777       /* Kludge to set black to move, avoiding the troublesome and now
4778        * deprecated "black" command.
4779        */
4780       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4781
4782       SendToProgram("edit\n", cps);
4783       SendToProgram("#\n", cps);
4784       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4785         bp = &boards[moveNum][i][BOARD_LEFT];
4786         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4787           if ((int) *bp < (int) BlackPawn) {
4788             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4789                     AAA + j, ONE + i);
4790             if(message[0] == '+' || message[0] == '~') {
4791                 sprintf(message, "%c%c%c+\n",
4792                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4793                         AAA + j, ONE + i);
4794             }
4795             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4796                 message[1] = BOARD_RGHT   - 1 - j + '1';
4797                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4798             }
4799             SendToProgram(message, cps);
4800           }
4801         }
4802       }
4803     
4804       SendToProgram("c\n", cps);
4805       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4806         bp = &boards[moveNum][i][BOARD_LEFT];
4807         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4808           if (((int) *bp != (int) EmptySquare)
4809               && ((int) *bp >= (int) BlackPawn)) {
4810             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4811                     AAA + j, ONE + i);
4812             if(message[0] == '+' || message[0] == '~') {
4813                 sprintf(message, "%c%c%c+\n",
4814                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4815                         AAA + j, ONE + i);
4816             }
4817             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4818                 message[1] = BOARD_RGHT   - 1 - j + '1';
4819                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4820             }
4821             SendToProgram(message, cps);
4822           }
4823         }
4824       }
4825     
4826       SendToProgram(".\n", cps);
4827     }
4828     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4829 }
4830
4831 int
4832 IsPromotion(fromX, fromY, toX, toY)
4833      int fromX, fromY, toX, toY;
4834 {
4835     /* [HGM] add Shogi promotions */
4836     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4837     ChessSquare piece;
4838
4839     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4840       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4841    /* [HGM] Note to self: line above also weeds out drops */
4842     piece = boards[currentMove][fromY][fromX];
4843     if(gameInfo.variant == VariantShogi) {
4844         promotionZoneSize = 3;
4845         highestPromotingPiece = (int)WhiteKing;
4846         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4847            and if in normal chess we then allow promotion to King, why not
4848            allow promotion of other piece in Shogi?                         */
4849     }
4850     if((int)piece >= BlackPawn) {
4851         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4852              return FALSE;
4853         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4854     } else {
4855         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4856            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4857     }
4858     return ( (int)piece <= highestPromotingPiece );
4859 }
4860
4861 int
4862 InPalace(row, column)
4863      int row, column;
4864 {   /* [HGM] for Xiangqi */
4865     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4866          column < (BOARD_WIDTH + 4)/2 &&
4867          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4868     return FALSE;
4869 }
4870
4871 int
4872 PieceForSquare (x, y)
4873      int x;
4874      int y;
4875 {
4876   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4877      return -1;
4878   else
4879      return boards[currentMove][y][x];
4880 }
4881
4882 int
4883 OKToStartUserMove(x, y)
4884      int x, y;
4885 {
4886     ChessSquare from_piece;
4887     int white_piece;
4888
4889     if (matchMode) return FALSE;
4890     if (gameMode == EditPosition) return TRUE;
4891
4892     if (x >= 0 && y >= 0)
4893       from_piece = boards[currentMove][y][x];
4894     else
4895       from_piece = EmptySquare;
4896
4897     if (from_piece == EmptySquare) return FALSE;
4898
4899     white_piece = (int)from_piece >= (int)WhitePawn &&
4900       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4901
4902     switch (gameMode) {
4903       case PlayFromGameFile:
4904       case AnalyzeFile:
4905       case TwoMachinesPlay:
4906       case EndOfGame:
4907         return FALSE;
4908
4909       case IcsObserving:
4910       case IcsIdle:
4911         return FALSE;
4912
4913       case MachinePlaysWhite:
4914       case IcsPlayingBlack:
4915         if (appData.zippyPlay) return FALSE;
4916         if (white_piece) {
4917             DisplayMoveError(_("You are playing Black"));
4918             return FALSE;
4919         }
4920         break;
4921
4922       case MachinePlaysBlack:
4923       case IcsPlayingWhite:
4924         if (appData.zippyPlay) return FALSE;
4925         if (!white_piece) {
4926             DisplayMoveError(_("You are playing White"));
4927             return FALSE;
4928         }
4929         break;
4930
4931       case EditGame:
4932         if (!white_piece && WhiteOnMove(currentMove)) {
4933             DisplayMoveError(_("It is White's turn"));
4934             return FALSE;
4935         }           
4936         if (white_piece && !WhiteOnMove(currentMove)) {
4937             DisplayMoveError(_("It is Black's turn"));
4938             return FALSE;
4939         }           
4940         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4941             /* Editing correspondence game history */
4942             /* Could disallow this or prompt for confirmation */
4943             cmailOldMove = -1;
4944         }
4945         if (currentMove < forwardMostMove) {
4946             /* Discarding moves */
4947             /* Could prompt for confirmation here,
4948                but I don't think that's such a good idea */
4949             forwardMostMove = currentMove;
4950         }
4951         break;
4952
4953       case BeginningOfGame:
4954         if (appData.icsActive) return FALSE;
4955         if (!appData.noChessProgram) {
4956             if (!white_piece) {
4957                 DisplayMoveError(_("You are playing White"));
4958                 return FALSE;
4959             }
4960         }
4961         break;
4962         
4963       case Training:
4964         if (!white_piece && WhiteOnMove(currentMove)) {
4965             DisplayMoveError(_("It is White's turn"));
4966             return FALSE;
4967         }           
4968         if (white_piece && !WhiteOnMove(currentMove)) {
4969             DisplayMoveError(_("It is Black's turn"));
4970             return FALSE;
4971         }           
4972         break;
4973
4974       default:
4975       case IcsExamining:
4976         break;
4977     }
4978     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4979         && gameMode != AnalyzeFile && gameMode != Training) {
4980         DisplayMoveError(_("Displayed position is not current"));
4981         return FALSE;
4982     }
4983     return TRUE;
4984 }
4985
4986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4988 int lastLoadGameUseList = FALSE;
4989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4990 ChessMove lastLoadGameStart = (ChessMove) 0;
4991
4992
4993 ChessMove
4994 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4995      int fromX, fromY, toX, toY;
4996      int promoChar;
4997 {
4998     ChessMove moveType;
4999     ChessSquare pdown, pup;
5000
5001     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5002     if ((fromX == toX) && (fromY == toY)) {
5003         return ImpossibleMove;
5004     }
5005
5006     /* [HGM] suppress all moves into holdings area and guard band */
5007     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5008             return ImpossibleMove;
5009
5010     /* [HGM] <sameColor> moved to here from winboard.c */
5011     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5012     pdown = boards[currentMove][fromY][fromX];
5013     pup = boards[currentMove][toY][toX];
5014     if (    gameMode != EditPosition &&
5015             (WhitePawn <= pdown && pdown < BlackPawn &&
5016              WhitePawn <= pup && pup < BlackPawn  ||
5017              BlackPawn <= pdown && pdown < EmptySquare &&
5018              BlackPawn <= pup && pup < EmptySquare 
5019             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5020                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5021                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5022         )           )
5023          return ImpossibleMove;
5024
5025     /* Check if the user is playing in turn.  This is complicated because we
5026        let the user "pick up" a piece before it is his turn.  So the piece he
5027        tried to pick up may have been captured by the time he puts it down!
5028        Therefore we use the color the user is supposed to be playing in this
5029        test, not the color of the piece that is currently on the starting
5030        square---except in EditGame mode, where the user is playing both
5031        sides; fortunately there the capture race can't happen.  (It can
5032        now happen in IcsExamining mode, but that's just too bad.  The user
5033        will get a somewhat confusing message in that case.)
5034        */
5035
5036     switch (gameMode) {
5037       case PlayFromGameFile:
5038       case AnalyzeFile:
5039       case TwoMachinesPlay:
5040       case EndOfGame:
5041       case IcsObserving:
5042       case IcsIdle:
5043         /* We switched into a game mode where moves are not accepted,
5044            perhaps while the mouse button was down. */
5045         return ImpossibleMove;
5046
5047       case MachinePlaysWhite:
5048         /* User is moving for Black */
5049         if (WhiteOnMove(currentMove)) {
5050             DisplayMoveError(_("It is White's turn"));
5051             return ImpossibleMove;
5052         }
5053         break;
5054
5055       case MachinePlaysBlack:
5056         /* User is moving for White */
5057         if (!WhiteOnMove(currentMove)) {
5058             DisplayMoveError(_("It is Black's turn"));
5059             return ImpossibleMove;
5060         }
5061         break;
5062
5063       case EditGame:
5064       case IcsExamining:
5065       case BeginningOfGame:
5066       case AnalyzeMode:
5067       case Training:
5068         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5069             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5070             /* User is moving for Black */
5071             if (WhiteOnMove(currentMove)) {
5072                 DisplayMoveError(_("It is White's turn"));
5073                 return ImpossibleMove;
5074             }
5075         } else {
5076             /* User is moving for White */
5077             if (!WhiteOnMove(currentMove)) {
5078                 DisplayMoveError(_("It is Black's turn"));
5079                 return ImpossibleMove;
5080             }
5081         }
5082         break;
5083
5084       case IcsPlayingBlack:
5085         /* User is moving for Black */
5086         if (WhiteOnMove(currentMove)) {
5087             if (!appData.premove) {
5088                 DisplayMoveError(_("It is White's turn"));
5089             } else if (toX >= 0 && toY >= 0) {
5090                 premoveToX = toX;
5091                 premoveToY = toY;
5092                 premoveFromX = fromX;
5093                 premoveFromY = fromY;
5094                 premovePromoChar = promoChar;
5095                 gotPremove = 1;
5096                 if (appData.debugMode) 
5097                     fprintf(debugFP, "Got premove: fromX %d,"
5098                             "fromY %d, toX %d, toY %d\n",
5099                             fromX, fromY, toX, toY);
5100             }
5101             return ImpossibleMove;
5102         }
5103         break;
5104
5105       case IcsPlayingWhite:
5106         /* User is moving for White */
5107         if (!WhiteOnMove(currentMove)) {
5108             if (!appData.premove) {
5109                 DisplayMoveError(_("It is Black's turn"));
5110             } else if (toX >= 0 && toY >= 0) {
5111                 premoveToX = toX;
5112                 premoveToY = toY;
5113                 premoveFromX = fromX;
5114                 premoveFromY = fromY;
5115                 premovePromoChar = promoChar;
5116                 gotPremove = 1;
5117                 if (appData.debugMode) 
5118                     fprintf(debugFP, "Got premove: fromX %d,"
5119                             "fromY %d, toX %d, toY %d\n",
5120                             fromX, fromY, toX, toY);
5121             }
5122             return ImpossibleMove;
5123         }
5124         break;
5125
5126       default:
5127         break;
5128
5129       case EditPosition:
5130         /* EditPosition, empty square, or different color piece;
5131            click-click move is possible */
5132         if (toX == -2 || toY == -2) {
5133             boards[0][fromY][fromX] = EmptySquare;
5134             return AmbiguousMove;
5135         } else if (toX >= 0 && toY >= 0) {
5136             boards[0][toY][toX] = boards[0][fromY][fromX];
5137             boards[0][fromY][fromX] = EmptySquare;
5138             return AmbiguousMove;
5139         }
5140         return ImpossibleMove;
5141     }
5142
5143     /* [HGM] If move started in holdings, it means a drop */
5144     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5145          if( pup != EmptySquare ) return ImpossibleMove;
5146          if(appData.testLegality) {
5147              /* it would be more logical if LegalityTest() also figured out
5148               * which drops are legal. For now we forbid pawns on back rank.
5149               * Shogi is on its own here...
5150               */
5151              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5152                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5153                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5154          }
5155          return WhiteDrop; /* Not needed to specify white or black yet */
5156     }
5157
5158     userOfferedDraw = FALSE;
5159         
5160     /* [HGM] always test for legality, to get promotion info */
5161     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162                           epStatus[currentMove], castlingRights[currentMove],
5163                                          fromY, fromX, toY, toX, promoChar);
5164
5165     /* [HGM] but possibly ignore an IllegalMove result */
5166     if (appData.testLegality) {
5167         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5168             DisplayMoveError(_("Illegal move"));
5169             return ImpossibleMove;
5170         }
5171     }
5172 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5173     return moveType;
5174     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5175        function is made into one that returns an OK move type if FinishMove
5176        should be called. This to give the calling driver routine the
5177        opportunity to finish the userMove input with a promotion popup,
5178        without bothering the user with this for invalid or illegal moves */
5179
5180 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5181 }
5182
5183 /* Common tail of UserMoveEvent and DropMenuEvent */
5184 int
5185 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5186      ChessMove moveType;
5187      int fromX, fromY, toX, toY;
5188      /*char*/int promoChar;
5189 {
5190     char *bookHit = 0;
5191 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5192     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5193         // [HGM] superchess: suppress promotions to non-available piece
5194         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5195         if(WhiteOnMove(currentMove)) {
5196             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5197         } else {
5198             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5199         }
5200     }
5201
5202     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5203        move type in caller when we know the move is a legal promotion */
5204     if(moveType == NormalMove && promoChar)
5205         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5206 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5207     /* [HGM] convert drag-and-drop piece drops to standard form */
5208     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5209          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5210          fromX = boards[currentMove][fromY][fromX];
5211          fromY = DROP_RANK;
5212     }
5213
5214     /* [HGM] <popupFix> The following if has been moved here from
5215        UserMoveEvent(). Because it seemed to belon here (why not allow
5216        piece drops in training games?), and because it can only be
5217        performed after it is known to what we promote. */
5218     if (gameMode == Training) {
5219       /* compare the move played on the board to the next move in the
5220        * game. If they match, display the move and the opponent's response. 
5221        * If they don't match, display an error message.
5222        */
5223       int saveAnimate;
5224       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5225       CopyBoard(testBoard, boards[currentMove]);
5226       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5227
5228       if (CompareBoards(testBoard, boards[currentMove+1])) {
5229         ForwardInner(currentMove+1);
5230
5231         /* Autoplay the opponent's response.
5232          * if appData.animate was TRUE when Training mode was entered,
5233          * the response will be animated.
5234          */
5235         saveAnimate = appData.animate;
5236         appData.animate = animateTraining;
5237         ForwardInner(currentMove+1);
5238         appData.animate = saveAnimate;
5239
5240         /* check for the end of the game */
5241         if (currentMove >= forwardMostMove) {
5242           gameMode = PlayFromGameFile;
5243           ModeHighlight();
5244           SetTrainingModeOff();
5245           DisplayInformation(_("End of game"));
5246         }
5247       } else {
5248         DisplayError(_("Incorrect move"), 0);
5249       }
5250       return 1;
5251     }
5252
5253   /* Ok, now we know that the move is good, so we can kill
5254      the previous line in Analysis Mode */
5255   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5256     forwardMostMove = currentMove;
5257   }
5258
5259   /* If we need the chess program but it's dead, restart it */
5260   ResurrectChessProgram();
5261
5262   /* A user move restarts a paused game*/
5263   if (pausing)
5264     PauseEvent();
5265
5266   thinkOutput[0] = NULLCHAR;
5267
5268   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5269
5270   if (gameMode == BeginningOfGame) {
5271     if (appData.noChessProgram) {
5272       gameMode = EditGame;
5273       SetGameInfo();
5274     } else {
5275       char buf[MSG_SIZ];
5276       gameMode = MachinePlaysBlack;
5277       StartClocks();
5278       SetGameInfo();
5279       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5280       DisplayTitle(buf);
5281       if (first.sendName) {
5282         sprintf(buf, "name %s\n", gameInfo.white);
5283         SendToProgram(buf, &first);
5284       }
5285       StartClocks();
5286     }
5287     ModeHighlight();
5288   }
5289 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5290   /* Relay move to ICS or chess engine */
5291   if (appData.icsActive) {
5292     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5293         gameMode == IcsExamining) {
5294       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5295       ics_user_moved = 1;
5296     }
5297   } else {
5298     if (first.sendTime && (gameMode == BeginningOfGame ||
5299                            gameMode == MachinePlaysWhite ||
5300                            gameMode == MachinePlaysBlack)) {
5301       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5302     }
5303     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5304          // [HGM] book: if program might be playing, let it use book
5305         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5306         first.maybeThinking = TRUE;
5307     } else SendMoveToProgram(forwardMostMove-1, &first);
5308     if (currentMove == cmailOldMove + 1) {
5309       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5310     }
5311   }
5312
5313   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5314
5315   switch (gameMode) {
5316   case EditGame:
5317     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5318                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5319     case MT_NONE:
5320     case MT_CHECK:
5321       break;
5322     case MT_CHECKMATE:
5323     case MT_STAINMATE:
5324       if (WhiteOnMove(currentMove)) {
5325         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5326       } else {
5327         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5328       }
5329       break;
5330     case MT_STALEMATE:
5331       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5332       break;
5333     }
5334     break;
5335     
5336   case MachinePlaysBlack:
5337   case MachinePlaysWhite:
5338     /* disable certain menu options while machine is thinking */
5339     SetMachineThinkingEnables();
5340     break;
5341
5342   default:
5343     break;
5344   }
5345
5346   if(bookHit) { // [HGM] book: simulate book reply
5347         static char bookMove[MSG_SIZ]; // a bit generous?
5348
5349         programStats.nodes = programStats.depth = programStats.time = 
5350         programStats.score = programStats.got_only_move = 0;
5351         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5352
5353         strcpy(bookMove, "move ");
5354         strcat(bookMove, bookHit);
5355         HandleMachineMove(bookMove, &first);
5356   }
5357   return 1;
5358 }
5359
5360 void
5361 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5362      int fromX, fromY, toX, toY;
5363      int promoChar;
5364 {
5365     /* [HGM] This routine was added to allow calling of its two logical
5366        parts from other modules in the old way. Before, UserMoveEvent()
5367        automatically called FinishMove() if the move was OK, and returned
5368        otherwise. I separated the two, in order to make it possible to
5369        slip a promotion popup in between. But that it always needs two
5370        calls, to the first part, (now called UserMoveTest() ), and to
5371        FinishMove if the first part succeeded. Calls that do not need
5372        to do anything in between, can call this routine the old way. 
5373     */
5374     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5375 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5376     if(moveType != ImpossibleMove)
5377         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5378 }
5379
5380 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5381 {
5382 //    char * hint = lastHint;
5383     FrontEndProgramStats stats;
5384
5385     stats.which = cps == &first ? 0 : 1;
5386     stats.depth = cpstats->depth;
5387     stats.nodes = cpstats->nodes;
5388     stats.score = cpstats->score;
5389     stats.time = cpstats->time;
5390     stats.pv = cpstats->movelist;
5391     stats.hint = lastHint;
5392     stats.an_move_index = 0;
5393     stats.an_move_count = 0;
5394
5395     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5396         stats.hint = cpstats->move_name;
5397         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5398         stats.an_move_count = cpstats->nr_moves;
5399     }
5400
5401     SetProgramStats( &stats );
5402 }
5403
5404 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5405 {   // [HGM] book: this routine intercepts moves to simulate book replies
5406     char *bookHit = NULL;
5407
5408     //first determine if the incoming move brings opponent into his book
5409     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5410         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5411     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5412     if(bookHit != NULL && !cps->bookSuspend) {
5413         // make sure opponent is not going to reply after receiving move to book position
5414         SendToProgram("force\n", cps);
5415         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5416     }
5417     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5418     // now arrange restart after book miss
5419     if(bookHit) {
5420         // after a book hit we never send 'go', and the code after the call to this routine
5421         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5422         char buf[MSG_SIZ];
5423         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5424         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5425         SendToProgram(buf, cps);
5426         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5427     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5428         SendToProgram("go\n", cps);
5429         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5430     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5431         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5432             SendToProgram("go\n", cps); 
5433         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5434     }
5435     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5436 }
5437
5438 char *savedMessage;
5439 ChessProgramState *savedState;
5440 void DeferredBookMove(void)
5441 {
5442         if(savedState->lastPing != savedState->lastPong)
5443                     ScheduleDelayedEvent(DeferredBookMove, 10);
5444         else
5445         HandleMachineMove(savedMessage, savedState);
5446 }
5447
5448 void
5449 HandleMachineMove(message, cps)
5450      char *message;
5451      ChessProgramState *cps;
5452 {
5453     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5454     char realname[MSG_SIZ];
5455     int fromX, fromY, toX, toY;
5456     ChessMove moveType;
5457     char promoChar;
5458     char *p;
5459     int machineWhite;
5460     char *bookHit;
5461
5462 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5463     /*
5464      * Kludge to ignore BEL characters
5465      */
5466     while (*message == '\007') message++;
5467
5468     /*
5469      * [HGM] engine debug message: ignore lines starting with '#' character
5470      */
5471     if(cps->debug && *message == '#') return;
5472
5473     /*
5474      * Look for book output
5475      */
5476     if (cps == &first && bookRequested) {
5477         if (message[0] == '\t' || message[0] == ' ') {
5478             /* Part of the book output is here; append it */
5479             strcat(bookOutput, message);
5480             strcat(bookOutput, "  \n");
5481             return;
5482         } else if (bookOutput[0] != NULLCHAR) {
5483             /* All of book output has arrived; display it */
5484             char *p = bookOutput;
5485             while (*p != NULLCHAR) {
5486                 if (*p == '\t') *p = ' ';
5487                 p++;
5488             }
5489             DisplayInformation(bookOutput);
5490             bookRequested = FALSE;
5491             /* Fall through to parse the current output */
5492         }
5493     }
5494
5495     /*
5496      * Look for machine move.
5497      */
5498     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5499         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5500     {
5501         /* This method is only useful on engines that support ping */
5502         if (cps->lastPing != cps->lastPong) {
5503           if (gameMode == BeginningOfGame) {
5504             /* Extra move from before last new; ignore */
5505             if (appData.debugMode) {
5506                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5507             }
5508           } else {
5509             if (appData.debugMode) {
5510                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5511                         cps->which, gameMode);
5512             }
5513
5514             SendToProgram("undo\n", cps);
5515           }
5516           return;
5517         }
5518
5519         switch (gameMode) {
5520           case BeginningOfGame:
5521             /* Extra move from before last reset; ignore */
5522             if (appData.debugMode) {
5523                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5524             }
5525             return;
5526
5527           case EndOfGame:
5528           case IcsIdle:
5529           default:
5530             /* Extra move after we tried to stop.  The mode test is
5531                not a reliable way of detecting this problem, but it's
5532                the best we can do on engines that don't support ping.
5533             */
5534             if (appData.debugMode) {
5535                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5536                         cps->which, gameMode);
5537             }
5538             SendToProgram("undo\n", cps);
5539             return;
5540
5541           case MachinePlaysWhite:
5542           case IcsPlayingWhite:
5543             machineWhite = TRUE;
5544             break;
5545
5546           case MachinePlaysBlack:
5547           case IcsPlayingBlack:
5548             machineWhite = FALSE;
5549             break;
5550
5551           case TwoMachinesPlay:
5552             machineWhite = (cps->twoMachinesColor[0] == 'w');
5553             break;
5554         }
5555         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5556             if (appData.debugMode) {
5557                 fprintf(debugFP,
5558                         "Ignoring move out of turn by %s, gameMode %d"
5559                         ", forwardMost %d\n",
5560                         cps->which, gameMode, forwardMostMove);
5561             }
5562             return;
5563         }
5564
5565     if (appData.debugMode) { int f = forwardMostMove;
5566         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5567                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5568     }
5569         if(cps->alphaRank) AlphaRank(machineMove, 4);
5570         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5571                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5572             /* Machine move could not be parsed; ignore it. */
5573             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5574                     machineMove, cps->which);
5575             DisplayError(buf1, 0);
5576             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5577                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5578             if (gameMode == TwoMachinesPlay) {
5579               GameEnds(machineWhite ? BlackWins : WhiteWins,
5580                        buf1, GE_XBOARD);
5581             }
5582             return;
5583         }
5584
5585         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5586         /* So we have to redo legality test with true e.p. status here,  */
5587         /* to make sure an illegal e.p. capture does not slip through,   */
5588         /* to cause a forfeit on a justified illegal-move complaint      */
5589         /* of the opponent.                                              */
5590         if( gameMode==TwoMachinesPlay && appData.testLegality
5591             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5592                                                               ) {
5593            ChessMove moveType;
5594            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5595                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5596                              fromY, fromX, toY, toX, promoChar);
5597             if (appData.debugMode) {
5598                 int i;
5599                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5600                     castlingRights[forwardMostMove][i], castlingRank[i]);
5601                 fprintf(debugFP, "castling rights\n");
5602             }
5603             if(moveType == IllegalMove) {
5604                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5605                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5606                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5607                            buf1, GE_XBOARD);
5608                 return;
5609            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5610            /* [HGM] Kludge to handle engines that send FRC-style castling
5611               when they shouldn't (like TSCP-Gothic) */
5612            switch(moveType) {
5613              case WhiteASideCastleFR:
5614              case BlackASideCastleFR:
5615                toX+=2;
5616                currentMoveString[2]++;
5617                break;
5618              case WhiteHSideCastleFR:
5619              case BlackHSideCastleFR:
5620                toX--;
5621                currentMoveString[2]--;
5622                break;
5623              default: ; // nothing to do, but suppresses warning of pedantic compilers
5624            }
5625         }
5626         hintRequested = FALSE;
5627         lastHint[0] = NULLCHAR;
5628         bookRequested = FALSE;
5629         /* Program may be pondering now */
5630         cps->maybeThinking = TRUE;
5631         if (cps->sendTime == 2) cps->sendTime = 1;
5632         if (cps->offeredDraw) cps->offeredDraw--;
5633
5634 #if ZIPPY
5635         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5636             first.initDone) {
5637           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5638           ics_user_moved = 1;
5639           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5640                 char buf[3*MSG_SIZ];
5641
5642                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5643                         programStats.score / 100.,
5644                         programStats.depth,
5645                         programStats.time / 100.,
5646                         u64ToDouble(programStats.nodes),
5647                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5648                         programStats.movelist);
5649                 SendToICS(buf);
5650           }
5651         }
5652 #endif
5653         /* currentMoveString is set as a side-effect of ParseOneMove */
5654         strcpy(machineMove, currentMoveString);
5655         strcat(machineMove, "\n");
5656         strcpy(moveList[forwardMostMove], machineMove);
5657
5658         /* [AS] Save move info and clear stats for next move */
5659         pvInfoList[ forwardMostMove ].score = programStats.score;
5660         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5661         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5662         ClearProgramStats();
5663         thinkOutput[0] = NULLCHAR;
5664         hiddenThinkOutputState = 0;
5665
5666         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5667
5668         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5669         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5670             int count = 0;
5671
5672             while( count < adjudicateLossPlies ) {
5673                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5674
5675                 if( count & 1 ) {
5676                     score = -score; /* Flip score for winning side */
5677                 }
5678
5679                 if( score > adjudicateLossThreshold ) {
5680                     break;
5681                 }
5682
5683                 count++;
5684             }
5685
5686             if( count >= adjudicateLossPlies ) {
5687                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5688
5689                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5690                     "Xboard adjudication", 
5691                     GE_XBOARD );
5692
5693                 return;
5694             }
5695         }
5696
5697         if( gameMode == TwoMachinesPlay ) {
5698           // [HGM] some adjudications useful with buggy engines
5699             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5700           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5701
5702
5703             if( appData.testLegality )
5704             {   /* [HGM] Some more adjudications for obstinate engines */
5705                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5706                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5707                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5708                 static int moveCount = 6;
5709                 ChessMove result;
5710                 char *reason = NULL;
5711
5712                 /* Count what is on board. */
5713                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5714                 {   ChessSquare p = boards[forwardMostMove][i][j];
5715                     int m=i;
5716
5717                     switch((int) p)
5718                     {   /* count B,N,R and other of each side */
5719                         case WhiteKing:
5720                         case BlackKing:
5721                              NrK++; break; // [HGM] atomic: count Kings
5722                         case WhiteKnight:
5723                              NrWN++; break;
5724                         case WhiteBishop:
5725                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5726                              bishopsColor |= 1 << ((i^j)&1);
5727                              NrWB++; break;
5728                         case BlackKnight:
5729                              NrBN++; break;
5730                         case BlackBishop:
5731                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5732                              bishopsColor |= 1 << ((i^j)&1);
5733                              NrBB++; break;
5734                         case WhiteRook:
5735                              NrWR++; break;
5736                         case BlackRook:
5737                              NrBR++; break;
5738                         case WhiteQueen:
5739                              NrWQ++; break;
5740                         case BlackQueen:
5741                              NrBQ++; break;
5742                         case EmptySquare: 
5743                              break;
5744                         case BlackPawn:
5745                              m = 7-i;
5746                         case WhitePawn:
5747                              PawnAdvance += m; NrPawns++;
5748                     }
5749                     NrPieces += (p != EmptySquare);
5750                     NrW += ((int)p < (int)BlackPawn);
5751                     if(gameInfo.variant == VariantXiangqi && 
5752                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5753                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5754                         NrW -= ((int)p < (int)BlackPawn);
5755                     }
5756                 }
5757
5758                 /* Some material-based adjudications that have to be made before stalemate test */
5759                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5760                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5761                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5762                      if(appData.checkMates) {
5763                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5764                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5765                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5766                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5767                          return;
5768                      }
5769                 }
5770
5771                 /* Bare King in Shatranj (loses) or Losers (wins) */
5772                 if( NrW == 1 || NrPieces - NrW == 1) {
5773                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5774                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5775                      if(appData.checkMates) {
5776                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5777                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5778                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5779                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5780                          return;
5781                      }
5782                   } else
5783                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5784                   {    /* bare King */
5785                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5786                         if(appData.checkMates) {
5787                             /* but only adjudicate if adjudication enabled */
5788                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5789                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5790                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5791                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5792                             return;
5793                         }
5794                   }
5795                 } else bare = 1;
5796
5797
5798             // don't wait for engine to announce game end if we can judge ourselves
5799             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5800                                        castlingRights[forwardMostMove]) ) {
5801               case MT_CHECK:
5802                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5803                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5804                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5805                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5806                             checkCnt++;
5807                         if(checkCnt >= 2) {
5808                             reason = "Xboard adjudication: 3rd check";
5809                             epStatus[forwardMostMove] = EP_CHECKMATE;
5810                             break;
5811                         }
5812                     }
5813                 }
5814               case MT_NONE:
5815               default:
5816                 break;
5817               case MT_STALEMATE:
5818               case MT_STAINMATE:
5819                 reason = "Xboard adjudication: Stalemate";
5820                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5821                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5822                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5823                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5824                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5825                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5826                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5827                                                                         EP_CHECKMATE : EP_WINS);
5828                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5829                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5830                 }
5831                 break;
5832               case MT_CHECKMATE:
5833                 reason = "Xboard adjudication: Checkmate";
5834                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5835                 break;
5836             }
5837
5838                 switch(i = epStatus[forwardMostMove]) {
5839                     case EP_STALEMATE:
5840                         result = GameIsDrawn; break;
5841                     case EP_CHECKMATE:
5842                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5843                     case EP_WINS:
5844                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5845                     default:
5846                         result = (ChessMove) 0;
5847                 }
5848                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5849                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5850                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851                     GameEnds( result, reason, GE_XBOARD );
5852                     return;
5853                 }
5854
5855                 /* Next absolutely insufficient mating material. */
5856                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5857                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5858                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5859                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5860                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5861
5862                      /* always flag draws, for judging claims */
5863                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5864
5865                      if(appData.materialDraws) {
5866                          /* but only adjudicate them if adjudication enabled */
5867                          SendToProgram("force\n", cps->other); // suppress reply
5868                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5869                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5870                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5871                          return;
5872                      }
5873                 }
5874
5875                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5876                 if(NrPieces == 4 && 
5877                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5878                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5879                    || NrWN==2 || NrBN==2     /* KNNK */
5880                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5881                   ) ) {
5882                      if(--moveCount < 0 && appData.trivialDraws)
5883                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5884                           SendToProgram("force\n", cps->other); // suppress reply
5885                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5886                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5887                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5888                           return;
5889                      }
5890                 } else moveCount = 6;
5891             }
5892           }
5893 #if 1
5894     if (appData.debugMode) { int i;
5895       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5896               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5897               appData.drawRepeats);
5898       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5899            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5900
5901     }
5902 #endif
5903                 /* Check for rep-draws */
5904                 count = 0;
5905                 for(k = forwardMostMove-2;
5906                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5907                         epStatus[k] < EP_UNKNOWN &&
5908                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5909                     k-=2)
5910                 {   int rights=0;
5911 #if 0
5912     if (appData.debugMode) {
5913       fprintf(debugFP, " loop\n");
5914     }
5915 #endif
5916                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5917 #if 0
5918     if (appData.debugMode) {
5919       fprintf(debugFP, "match\n");
5920     }
5921 #endif
5922                         /* compare castling rights */
5923                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5924                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5925                                 rights++; /* King lost rights, while rook still had them */
5926                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5927                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5928                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5929                                    rights++; /* but at least one rook lost them */
5930                         }
5931                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5932                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5933                                 rights++; 
5934                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5935                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5936                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5937                                    rights++;
5938                         }
5939 #if 0
5940     if (appData.debugMode) {
5941       for(i=0; i<nrCastlingRights; i++)
5942       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5943     }
5944
5945     if (appData.debugMode) {
5946       fprintf(debugFP, " %d %d\n", rights, k);
5947     }
5948 #endif
5949                         if( rights == 0 && ++count > appData.drawRepeats-2
5950                             && appData.drawRepeats > 1) {
5951                              /* adjudicate after user-specified nr of repeats */
5952                              SendToProgram("force\n", cps->other); // suppress reply
5953                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5954                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5955                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5956                                 // [HGM] xiangqi: check for forbidden perpetuals
5957                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5958                                 for(m=forwardMostMove; m>k; m-=2) {
5959                                     if(MateTest(boards[m], PosFlags(m), 
5960                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5961                                         ourPerpetual = 0; // the current mover did not always check
5962                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5963                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5964                                         hisPerpetual = 0; // the opponent did not always check
5965                                 }
5966                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5967                                                                         ourPerpetual, hisPerpetual);
5968                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5969                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5970                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5971                                     return;
5972                                 }
5973                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5974                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5975                                 // Now check for perpetual chases
5976                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5977                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5978                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5979                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5980                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5981                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5982                                         return;
5983                                     }
5984                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5985                                         break; // Abort repetition-checking loop.
5986                                 }
5987                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5988                              }
5989                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5990                              return;
5991                         }
5992                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5993                              epStatus[forwardMostMove] = EP_REP_DRAW;
5994                     }
5995                 }
5996
5997                 /* Now we test for 50-move draws. Determine ply count */
5998                 count = forwardMostMove;
5999                 /* look for last irreversble move */
6000                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6001                     count--;
6002                 /* if we hit starting position, add initial plies */
6003                 if( count == backwardMostMove )
6004                     count -= initialRulePlies;
6005                 count = forwardMostMove - count; 
6006                 if( count >= 100)
6007                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6008                          /* this is used to judge if draw claims are legal */
6009                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6010                          SendToProgram("force\n", cps->other); // suppress reply
6011                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6012                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6013                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6014                          return;
6015                 }
6016
6017                 /* if draw offer is pending, treat it as a draw claim
6018                  * when draw condition present, to allow engines a way to
6019                  * claim draws before making their move to avoid a race
6020                  * condition occurring after their move
6021                  */
6022                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6023                          char *p = NULL;
6024                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6025                              p = "Draw claim: 50-move rule";
6026                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6027                              p = "Draw claim: 3-fold repetition";
6028                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6029                              p = "Draw claim: insufficient mating material";
6030                          if( p != NULL ) {
6031                              SendToProgram("force\n", cps->other); // suppress reply
6032                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6033                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6034                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6035                              return;
6036                          }
6037                 }
6038
6039
6040                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6041                     SendToProgram("force\n", cps->other); // suppress reply
6042                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6043                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6044
6045                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6046
6047                     return;
6048                 }
6049         }
6050
6051         bookHit = NULL;
6052         if (gameMode == TwoMachinesPlay) {
6053             /* [HGM] relaying draw offers moved to after reception of move */
6054             /* and interpreting offer as claim if it brings draw condition */
6055             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6056                 SendToProgram("draw\n", cps->other);
6057             }
6058             if (cps->other->sendTime) {
6059                 SendTimeRemaining(cps->other,
6060                                   cps->other->twoMachinesColor[0] == 'w');
6061             }
6062             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6063             if (firstMove && !bookHit) {
6064                 firstMove = FALSE;
6065                 if (cps->other->useColors) {
6066                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6067                 }
6068                 SendToProgram("go\n", cps->other);
6069             }
6070             cps->other->maybeThinking = TRUE;
6071         }
6072
6073         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6074         
6075         if (!pausing && appData.ringBellAfterMoves) {
6076             RingBell();
6077         }
6078
6079         /* 
6080          * Reenable menu items that were disabled while
6081          * machine was thinking
6082          */
6083         if (gameMode != TwoMachinesPlay)
6084             SetUserThinkingEnables();
6085
6086         // [HGM] book: after book hit opponent has received move and is now in force mode
6087         // force the book reply into it, and then fake that it outputted this move by jumping
6088         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6089         if(bookHit) {
6090                 static char bookMove[MSG_SIZ]; // a bit generous?
6091
6092                 strcpy(bookMove, "move ");
6093                 strcat(bookMove, bookHit);
6094                 message = bookMove;
6095                 cps = cps->other;
6096                 programStats.nodes = programStats.depth = programStats.time = 
6097                 programStats.score = programStats.got_only_move = 0;
6098                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6099
6100                 if(cps->lastPing != cps->lastPong) {
6101                     savedMessage = message; // args for deferred call
6102                     savedState = cps;
6103                     ScheduleDelayedEvent(DeferredBookMove, 10);
6104                     return;
6105                 }
6106                 goto FakeBookMove;
6107         }
6108
6109         return;
6110     }
6111
6112     /* Set special modes for chess engines.  Later something general
6113      *  could be added here; for now there is just one kludge feature,
6114      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6115      *  when "xboard" is given as an interactive command.
6116      */
6117     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6118         cps->useSigint = FALSE;
6119         cps->useSigterm = FALSE;
6120     }
6121
6122     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6123      * want this, I was asked to put it in, and obliged.
6124      */
6125     if (!strncmp(message, "setboard ", 9)) {
6126         Board initial_position; int i;
6127
6128         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6129
6130         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6131             DisplayError(_("Bad FEN received from engine"), 0);
6132             return ;
6133         } else {
6134            Reset(FALSE, FALSE);
6135            CopyBoard(boards[0], initial_position);
6136            initialRulePlies = FENrulePlies;
6137            epStatus[0] = FENepStatus;
6138            for( i=0; i<nrCastlingRights; i++ )
6139                 castlingRights[0][i] = FENcastlingRights[i];
6140            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6141            else gameMode = MachinePlaysBlack;                 
6142            DrawPosition(FALSE, boards[currentMove]);
6143         }
6144         return;
6145     }
6146
6147     /*
6148      * Look for communication commands
6149      */
6150     if (!strncmp(message, "telluser ", 9)) {
6151         DisplayNote(message + 9);
6152         return;
6153     }
6154     if (!strncmp(message, "tellusererror ", 14)) {
6155         DisplayError(message + 14, 0);
6156         return;
6157     }
6158     if (!strncmp(message, "tellopponent ", 13)) {
6159       if (appData.icsActive) {
6160         if (loggedOn) {
6161           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6162           SendToICS(buf1);
6163         }
6164       } else {
6165         DisplayNote(message + 13);
6166       }
6167       return;
6168     }
6169     if (!strncmp(message, "tellothers ", 11)) {
6170       if (appData.icsActive) {
6171         if (loggedOn) {
6172           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6173           SendToICS(buf1);
6174         }
6175       }
6176       return;
6177     }
6178     if (!strncmp(message, "tellall ", 8)) {
6179       if (appData.icsActive) {
6180         if (loggedOn) {
6181           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6182           SendToICS(buf1);
6183         }
6184       } else {
6185         DisplayNote(message + 8);
6186       }
6187       return;
6188     }
6189     if (strncmp(message, "warning", 7) == 0) {
6190         /* Undocumented feature, use tellusererror in new code */
6191         DisplayError(message, 0);
6192         return;
6193     }
6194     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6195         strcpy(realname, cps->tidy);
6196         strcat(realname, " query");
6197         AskQuestion(realname, buf2, buf1, cps->pr);
6198         return;
6199     }
6200     /* Commands from the engine directly to ICS.  We don't allow these to be 
6201      *  sent until we are logged on. Crafty kibitzes have been known to 
6202      *  interfere with the login process.
6203      */
6204     if (loggedOn) {
6205         if (!strncmp(message, "tellics ", 8)) {
6206             SendToICS(message + 8);
6207             SendToICS("\n");
6208             return;
6209         }
6210         if (!strncmp(message, "tellicsnoalias ", 15)) {
6211             SendToICS(ics_prefix);
6212             SendToICS(message + 15);
6213             SendToICS("\n");
6214             return;
6215         }
6216         /* The following are for backward compatibility only */
6217         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6218             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6219             SendToICS(ics_prefix);
6220             SendToICS(message);
6221             SendToICS("\n");
6222             return;
6223         }
6224     }
6225     if (strncmp(message, "feature ", 8) == 0) {
6226       ParseFeatures(message+8, cps);
6227     }
6228     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6229         return;
6230     }
6231     /*
6232      * If the move is illegal, cancel it and redraw the board.
6233      * Also deal with other error cases.  Matching is rather loose
6234      * here to accommodate engines written before the spec.
6235      */
6236     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6237         strncmp(message, "Error", 5) == 0) {
6238         if (StrStr(message, "name") || 
6239             StrStr(message, "rating") || StrStr(message, "?") ||
6240             StrStr(message, "result") || StrStr(message, "board") ||
6241             StrStr(message, "bk") || StrStr(message, "computer") ||
6242             StrStr(message, "variant") || StrStr(message, "hint") ||
6243             StrStr(message, "random") || StrStr(message, "depth") ||
6244             StrStr(message, "accepted")) {
6245             return;
6246         }
6247         if (StrStr(message, "protover")) {
6248           /* Program is responding to input, so it's apparently done
6249              initializing, and this error message indicates it is
6250              protocol version 1.  So we don't need to wait any longer
6251              for it to initialize and send feature commands. */
6252           FeatureDone(cps, 1);
6253           cps->protocolVersion = 1;
6254           return;
6255         }
6256         cps->maybeThinking = FALSE;
6257
6258         if (StrStr(message, "draw")) {
6259             /* Program doesn't have "draw" command */
6260             cps->sendDrawOffers = 0;
6261             return;
6262         }
6263         if (cps->sendTime != 1 &&
6264             (StrStr(message, "time") || StrStr(message, "otim"))) {
6265           /* Program apparently doesn't have "time" or "otim" command */
6266           cps->sendTime = 0;
6267           return;
6268         }
6269         if (StrStr(message, "analyze")) {
6270             cps->analysisSupport = FALSE;
6271             cps->analyzing = FALSE;
6272             Reset(FALSE, TRUE);
6273             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6274             DisplayError(buf2, 0);
6275             return;
6276         }
6277         if (StrStr(message, "(no matching move)st")) {
6278           /* Special kludge for GNU Chess 4 only */
6279           cps->stKludge = TRUE;
6280           SendTimeControl(cps, movesPerSession, timeControl,
6281                           timeIncrement, appData.searchDepth,
6282                           searchTime);
6283           return;
6284         }
6285         if (StrStr(message, "(no matching move)sd")) {
6286           /* Special kludge for GNU Chess 4 only */
6287           cps->sdKludge = TRUE;
6288           SendTimeControl(cps, movesPerSession, timeControl,
6289                           timeIncrement, appData.searchDepth,
6290                           searchTime);
6291           return;
6292         }
6293         if (!StrStr(message, "llegal")) {
6294             return;
6295         }
6296         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6297             gameMode == IcsIdle) return;
6298         if (forwardMostMove <= backwardMostMove) return;
6299 #if 0
6300         /* Following removed: it caused a bug where a real illegal move
6301            message in analyze mored would be ignored. */
6302         if (cps == &first && programStats.ok_to_send == 0) {
6303             /* Bogus message from Crafty responding to "."  This filtering
6304                can miss some of the bad messages, but fortunately the bug 
6305                is fixed in current Crafty versions, so it doesn't matter. */
6306             return;
6307         }
6308 #endif
6309         if (pausing) PauseEvent();
6310         if (gameMode == PlayFromGameFile) {
6311             /* Stop reading this game file */
6312             gameMode = EditGame;
6313             ModeHighlight();
6314         }
6315         currentMove = --forwardMostMove;
6316         DisplayMove(currentMove-1); /* before DisplayMoveError */
6317         SwitchClocks();
6318         DisplayBothClocks();
6319         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6320                 parseList[currentMove], cps->which);
6321         DisplayMoveError(buf1);
6322         DrawPosition(FALSE, boards[currentMove]);
6323
6324         /* [HGM] illegal-move claim should forfeit game when Xboard */
6325         /* only passes fully legal moves                            */
6326         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6327             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6328                                 "False illegal-move claim", GE_XBOARD );
6329         }
6330         return;
6331     }
6332     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6333         /* Program has a broken "time" command that
6334            outputs a string not ending in newline.
6335            Don't use it. */
6336         cps->sendTime = 0;
6337     }
6338     
6339     /*
6340      * If chess program startup fails, exit with an error message.
6341      * Attempts to recover here are futile.
6342      */
6343     if ((StrStr(message, "unknown host") != NULL)
6344         || (StrStr(message, "No remote directory") != NULL)
6345         || (StrStr(message, "not found") != NULL)
6346         || (StrStr(message, "No such file") != NULL)
6347         || (StrStr(message, "can't alloc") != NULL)
6348         || (StrStr(message, "Permission denied") != NULL)) {
6349
6350         cps->maybeThinking = FALSE;
6351         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6352                 cps->which, cps->program, cps->host, message);
6353         RemoveInputSource(cps->isr);
6354         DisplayFatalError(buf1, 0, 1);
6355         return;
6356     }
6357     
6358     /* 
6359      * Look for hint output
6360      */
6361     if (sscanf(message, "Hint: %s", buf1) == 1) {
6362         if (cps == &first && hintRequested) {
6363             hintRequested = FALSE;
6364             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6365                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6366                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6367                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6368                                     fromY, fromX, toY, toX, promoChar, buf1);
6369                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6370                 DisplayInformation(buf2);
6371             } else {
6372                 /* Hint move could not be parsed!? */
6373               snprintf(buf2, sizeof(buf2),
6374                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6375                         buf1, cps->which);
6376                 DisplayError(buf2, 0);
6377             }
6378         } else {
6379             strcpy(lastHint, buf1);
6380         }
6381         return;
6382     }
6383
6384     /*
6385      * Ignore other messages if game is not in progress
6386      */
6387     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6388         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6389
6390     /*
6391      * look for win, lose, draw, or draw offer
6392      */
6393     if (strncmp(message, "1-0", 3) == 0) {
6394         char *p, *q, *r = "";
6395         p = strchr(message, '{');
6396         if (p) {
6397             q = strchr(p, '}');
6398             if (q) {
6399                 *q = NULLCHAR;
6400                 r = p + 1;
6401             }
6402         }
6403         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6404         return;
6405     } else if (strncmp(message, "0-1", 3) == 0) {
6406         char *p, *q, *r = "";
6407         p = strchr(message, '{');
6408         if (p) {
6409             q = strchr(p, '}');
6410             if (q) {
6411                 *q = NULLCHAR;
6412                 r = p + 1;
6413             }
6414         }
6415         /* Kludge for Arasan 4.1 bug */
6416         if (strcmp(r, "Black resigns") == 0) {
6417             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6418             return;
6419         }
6420         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6421         return;
6422     } else if (strncmp(message, "1/2", 3) == 0) {
6423         char *p, *q, *r = "";
6424         p = strchr(message, '{');
6425         if (p) {
6426             q = strchr(p, '}');
6427             if (q) {
6428                 *q = NULLCHAR;
6429                 r = p + 1;
6430             }
6431         }
6432             
6433         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6434         return;
6435
6436     } else if (strncmp(message, "White resign", 12) == 0) {
6437         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6438         return;
6439     } else if (strncmp(message, "Black resign", 12) == 0) {
6440         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6441         return;
6442     } else if (strncmp(message, "White matches", 13) == 0 ||
6443                strncmp(message, "Black matches", 13) == 0   ) {
6444         /* [HGM] ignore GNUShogi noises */
6445         return;
6446     } else if (strncmp(message, "White", 5) == 0 &&
6447                message[5] != '(' &&
6448                StrStr(message, "Black") == NULL) {
6449         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6450         return;
6451     } else if (strncmp(message, "Black", 5) == 0 &&
6452                message[5] != '(') {
6453         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6454         return;
6455     } else if (strcmp(message, "resign") == 0 ||
6456                strcmp(message, "computer resigns") == 0) {
6457         switch (gameMode) {
6458           case MachinePlaysBlack:
6459           case IcsPlayingBlack:
6460             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6461             break;
6462           case MachinePlaysWhite:
6463           case IcsPlayingWhite:
6464             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6465             break;
6466           case TwoMachinesPlay:
6467             if (cps->twoMachinesColor[0] == 'w')
6468               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6469             else
6470               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6471             break;
6472           default:
6473             /* can't happen */
6474             break;
6475         }
6476         return;
6477     } else if (strncmp(message, "opponent mates", 14) == 0) {
6478         switch (gameMode) {
6479           case MachinePlaysBlack:
6480           case IcsPlayingBlack:
6481             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6482             break;
6483           case MachinePlaysWhite:
6484           case IcsPlayingWhite:
6485             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6486             break;
6487           case TwoMachinesPlay:
6488             if (cps->twoMachinesColor[0] == 'w')
6489               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6490             else
6491               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6492             break;
6493           default:
6494             /* can't happen */
6495             break;
6496         }
6497         return;
6498     } else if (strncmp(message, "computer mates", 14) == 0) {
6499         switch (gameMode) {
6500           case MachinePlaysBlack:
6501           case IcsPlayingBlack:
6502             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6503             break;
6504           case MachinePlaysWhite:
6505           case IcsPlayingWhite:
6506             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6507             break;
6508           case TwoMachinesPlay:
6509             if (cps->twoMachinesColor[0] == 'w')
6510               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6511             else
6512               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6513             break;
6514           default:
6515             /* can't happen */
6516             break;
6517         }
6518         return;
6519     } else if (strncmp(message, "checkmate", 9) == 0) {
6520         if (WhiteOnMove(forwardMostMove)) {
6521             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6522         } else {
6523             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6524         }
6525         return;
6526     } else if (strstr(message, "Draw") != NULL ||
6527                strstr(message, "game is a draw") != NULL) {
6528         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6529         return;
6530     } else if (strstr(message, "offer") != NULL &&
6531                strstr(message, "draw") != NULL) {
6532 #if ZIPPY
6533         if (appData.zippyPlay && first.initDone) {
6534             /* Relay offer to ICS */
6535             SendToICS(ics_prefix);
6536             SendToICS("draw\n");
6537         }
6538 #endif
6539         cps->offeredDraw = 2; /* valid until this engine moves twice */
6540         if (gameMode == TwoMachinesPlay) {
6541             if (cps->other->offeredDraw) {
6542                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6543             /* [HGM] in two-machine mode we delay relaying draw offer      */
6544             /* until after we also have move, to see if it is really claim */
6545             }
6546 #if 0
6547               else {
6548                 if (cps->other->sendDrawOffers) {
6549                     SendToProgram("draw\n", cps->other);
6550                 }
6551             }
6552 #endif
6553         } else if (gameMode == MachinePlaysWhite ||
6554                    gameMode == MachinePlaysBlack) {
6555           if (userOfferedDraw) {
6556             DisplayInformation(_("Machine accepts your draw offer"));
6557             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6558           } else {
6559             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6560           }
6561         }
6562     }
6563
6564     
6565     /*
6566      * Look for thinking output
6567      */
6568     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6569           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6570                                 ) {
6571         int plylev, mvleft, mvtot, curscore, time;
6572         char mvname[MOVE_LEN];
6573         u64 nodes; // [DM]
6574         char plyext;
6575         int ignore = FALSE;
6576         int prefixHint = FALSE;
6577         mvname[0] = NULLCHAR;
6578
6579         switch (gameMode) {
6580           case MachinePlaysBlack:
6581           case IcsPlayingBlack:
6582             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6583             break;
6584           case MachinePlaysWhite:
6585           case IcsPlayingWhite:
6586             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6587             break;
6588           case AnalyzeMode:
6589           case AnalyzeFile:
6590             break;
6591           case IcsObserving: /* [DM] icsEngineAnalyze */
6592             if (!appData.icsEngineAnalyze) ignore = TRUE;
6593             break;
6594           case TwoMachinesPlay:
6595             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6596                 ignore = TRUE;
6597             }
6598             break;
6599           default:
6600             ignore = TRUE;
6601             break;
6602         }
6603
6604         if (!ignore) {
6605             buf1[0] = NULLCHAR;
6606             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6607                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6608
6609                 if (plyext != ' ' && plyext != '\t') {
6610                     time *= 100;
6611                 }
6612
6613                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6614                 if( cps->scoreIsAbsolute && 
6615                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6616                 {
6617                     curscore = -curscore;
6618                 }
6619
6620
6621                 programStats.depth = plylev;
6622                 programStats.nodes = nodes;
6623                 programStats.time = time;
6624                 programStats.score = curscore;
6625                 programStats.got_only_move = 0;
6626
6627                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6628                         int ticklen;
6629
6630                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6631                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6632                         if(WhiteOnMove(forwardMostMove)) 
6633                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6634                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6635                 }
6636
6637                 /* Buffer overflow protection */
6638                 if (buf1[0] != NULLCHAR) {
6639                     if (strlen(buf1) >= sizeof(programStats.movelist)
6640                         && appData.debugMode) {
6641                         fprintf(debugFP,
6642                                 "PV is too long; using the first %d bytes.\n",
6643                                 sizeof(programStats.movelist) - 1);
6644                     }
6645
6646                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6647                 } else {
6648                     sprintf(programStats.movelist, " no PV\n");
6649                 }
6650
6651                 if (programStats.seen_stat) {
6652                     programStats.ok_to_send = 1;
6653                 }
6654
6655                 if (strchr(programStats.movelist, '(') != NULL) {
6656                     programStats.line_is_book = 1;
6657                     programStats.nr_moves = 0;
6658                     programStats.moves_left = 0;
6659                 } else {
6660                     programStats.line_is_book = 0;
6661                 }
6662
6663                 SendProgramStatsToFrontend( cps, &programStats );
6664
6665                 /* 
6666                     [AS] Protect the thinkOutput buffer from overflow... this
6667                     is only useful if buf1 hasn't overflowed first!
6668                 */
6669                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6670                         plylev, 
6671                         (gameMode == TwoMachinesPlay ?
6672                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6673                         ((double) curscore) / 100.0,
6674                         prefixHint ? lastHint : "",
6675                         prefixHint ? " " : "" );
6676
6677                 if( buf1[0] != NULLCHAR ) {
6678                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6679
6680                     if( strlen(buf1) > max_len ) {
6681                         if( appData.debugMode) {
6682                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6683                         }
6684                         buf1[max_len+1] = '\0';
6685                     }
6686
6687                     strcat( thinkOutput, buf1 );
6688                 }
6689
6690                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6691                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6692                     DisplayMove(currentMove - 1);
6693                     DisplayAnalysis();
6694                 }
6695                 return;
6696
6697             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6698                 /* crafty (9.25+) says "(only move) <move>"
6699                  * if there is only 1 legal move
6700                  */
6701                 sscanf(p, "(only move) %s", buf1);
6702                 sprintf(thinkOutput, "%s (only move)", buf1);
6703                 sprintf(programStats.movelist, "%s (only move)", buf1);
6704                 programStats.depth = 1;
6705                 programStats.nr_moves = 1;
6706                 programStats.moves_left = 1;
6707                 programStats.nodes = 1;
6708                 programStats.time = 1;
6709                 programStats.got_only_move = 1;
6710
6711                 /* Not really, but we also use this member to
6712                    mean "line isn't going to change" (Crafty
6713                    isn't searching, so stats won't change) */
6714                 programStats.line_is_book = 1;
6715
6716                 SendProgramStatsToFrontend( cps, &programStats );
6717                 
6718                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6719                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6720                     DisplayMove(currentMove - 1);
6721                     DisplayAnalysis();
6722                 }
6723                 return;
6724             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6725                               &time, &nodes, &plylev, &mvleft,
6726                               &mvtot, mvname) >= 5) {
6727                 /* The stat01: line is from Crafty (9.29+) in response
6728                    to the "." command */
6729                 programStats.seen_stat = 1;
6730                 cps->maybeThinking = TRUE;
6731
6732                 if (programStats.got_only_move || !appData.periodicUpdates)
6733                   return;
6734
6735                 programStats.depth = plylev;
6736                 programStats.time = time;
6737                 programStats.nodes = nodes;
6738                 programStats.moves_left = mvleft;
6739                 programStats.nr_moves = mvtot;
6740                 strcpy(programStats.move_name, mvname);
6741                 programStats.ok_to_send = 1;
6742                 programStats.movelist[0] = '\0';
6743
6744                 SendProgramStatsToFrontend( cps, &programStats );
6745
6746                 DisplayAnalysis();
6747                 return;
6748
6749             } else if (strncmp(message,"++",2) == 0) {
6750                 /* Crafty 9.29+ outputs this */
6751                 programStats.got_fail = 2;
6752                 return;
6753
6754             } else if (strncmp(message,"--",2) == 0) {
6755                 /* Crafty 9.29+ outputs this */
6756                 programStats.got_fail = 1;
6757                 return;
6758
6759             } else if (thinkOutput[0] != NULLCHAR &&
6760                        strncmp(message, "    ", 4) == 0) {
6761                 unsigned message_len;
6762
6763                 p = message;
6764                 while (*p && *p == ' ') p++;
6765
6766                 message_len = strlen( p );
6767
6768                 /* [AS] Avoid buffer overflow */
6769                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6770                     strcat(thinkOutput, " ");
6771                     strcat(thinkOutput, p);
6772                 }
6773
6774                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6775                     strcat(programStats.movelist, " ");
6776                     strcat(programStats.movelist, p);
6777                 }
6778
6779                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6780                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6781                     DisplayMove(currentMove - 1);
6782                     DisplayAnalysis();
6783                 }
6784                 return;
6785             }
6786         }
6787         else {
6788             buf1[0] = NULLCHAR;
6789
6790             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6791                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6792             {
6793                 ChessProgramStats cpstats;
6794
6795                 if (plyext != ' ' && plyext != '\t') {
6796                     time *= 100;
6797                 }
6798
6799                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6800                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6801                     curscore = -curscore;
6802                 }
6803
6804                 cpstats.depth = plylev;
6805                 cpstats.nodes = nodes;
6806                 cpstats.time = time;
6807                 cpstats.score = curscore;
6808                 cpstats.got_only_move = 0;
6809                 cpstats.movelist[0] = '\0';
6810
6811                 if (buf1[0] != NULLCHAR) {
6812                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6813                 }
6814
6815                 cpstats.ok_to_send = 0;
6816                 cpstats.line_is_book = 0;
6817                 cpstats.nr_moves = 0;
6818                 cpstats.moves_left = 0;
6819
6820                 SendProgramStatsToFrontend( cps, &cpstats );
6821             }
6822         }
6823     }
6824 }
6825
6826
6827 /* Parse a game score from the character string "game", and
6828    record it as the history of the current game.  The game
6829    score is NOT assumed to start from the standard position. 
6830    The display is not updated in any way.
6831    */
6832 void
6833 ParseGameHistory(game)
6834      char *game;
6835 {
6836     ChessMove moveType;
6837     int fromX, fromY, toX, toY, boardIndex;
6838     char promoChar;
6839     char *p, *q;
6840     char buf[MSG_SIZ];
6841
6842     if (appData.debugMode)
6843       fprintf(debugFP, "Parsing game history: %s\n", game);
6844
6845     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6846     gameInfo.site = StrSave(appData.icsHost);
6847     gameInfo.date = PGNDate();
6848     gameInfo.round = StrSave("-");
6849
6850     /* Parse out names of players */
6851     while (*game == ' ') game++;
6852     p = buf;
6853     while (*game != ' ') *p++ = *game++;
6854     *p = NULLCHAR;
6855     gameInfo.white = StrSave(buf);
6856     while (*game == ' ') game++;
6857     p = buf;
6858     while (*game != ' ' && *game != '\n') *p++ = *game++;
6859     *p = NULLCHAR;
6860     gameInfo.black = StrSave(buf);
6861
6862     /* Parse moves */
6863     boardIndex = blackPlaysFirst ? 1 : 0;
6864     yynewstr(game);
6865     for (;;) {
6866         yyboardindex = boardIndex;
6867         moveType = (ChessMove) yylex();
6868         switch (moveType) {
6869           case IllegalMove:             /* maybe suicide chess, etc. */
6870   if (appData.debugMode) {
6871     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6872     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6873     setbuf(debugFP, NULL);
6874   }
6875           case WhitePromotionChancellor:
6876           case BlackPromotionChancellor:
6877           case WhitePromotionArchbishop:
6878           case BlackPromotionArchbishop:
6879           case WhitePromotionQueen:
6880           case BlackPromotionQueen:
6881           case WhitePromotionRook:
6882           case BlackPromotionRook:
6883           case WhitePromotionBishop:
6884           case BlackPromotionBishop:
6885           case WhitePromotionKnight:
6886           case BlackPromotionKnight:
6887           case WhitePromotionKing:
6888           case BlackPromotionKing:
6889           case NormalMove:
6890           case WhiteCapturesEnPassant:
6891           case BlackCapturesEnPassant:
6892           case WhiteKingSideCastle:
6893           case WhiteQueenSideCastle:
6894           case BlackKingSideCastle:
6895           case BlackQueenSideCastle:
6896           case WhiteKingSideCastleWild:
6897           case WhiteQueenSideCastleWild:
6898           case BlackKingSideCastleWild:
6899           case BlackQueenSideCastleWild:
6900           /* PUSH Fabien */
6901           case WhiteHSideCastleFR:
6902           case WhiteASideCastleFR:
6903           case BlackHSideCastleFR:
6904           case BlackASideCastleFR:
6905           /* POP Fabien */
6906             fromX = currentMoveString[0] - AAA;
6907             fromY = currentMoveString[1] - ONE;
6908             toX = currentMoveString[2] - AAA;
6909             toY = currentMoveString[3] - ONE;
6910             promoChar = currentMoveString[4];
6911             break;
6912           case WhiteDrop:
6913           case BlackDrop:
6914             fromX = moveType == WhiteDrop ?
6915               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6916             (int) CharToPiece(ToLower(currentMoveString[0]));
6917             fromY = DROP_RANK;
6918             toX = currentMoveString[2] - AAA;
6919             toY = currentMoveString[3] - ONE;
6920             promoChar = NULLCHAR;
6921             break;
6922           case AmbiguousMove:
6923             /* bug? */
6924             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6925   if (appData.debugMode) {
6926     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6928     setbuf(debugFP, NULL);
6929   }
6930             DisplayError(buf, 0);
6931             return;
6932           case ImpossibleMove:
6933             /* bug? */
6934             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6935   if (appData.debugMode) {
6936     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6937     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6938     setbuf(debugFP, NULL);
6939   }
6940             DisplayError(buf, 0);
6941             return;
6942           case (ChessMove) 0:   /* end of file */
6943             if (boardIndex < backwardMostMove) {
6944                 /* Oops, gap.  How did that happen? */
6945                 DisplayError(_("Gap in move list"), 0);
6946                 return;
6947             }
6948             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6949             if (boardIndex > forwardMostMove) {
6950                 forwardMostMove = boardIndex;
6951             }
6952             return;
6953           case ElapsedTime:
6954             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6955                 strcat(parseList[boardIndex-1], " ");
6956                 strcat(parseList[boardIndex-1], yy_text);
6957             }
6958             continue;
6959           case Comment:
6960           case PGNTag:
6961           case NAG:
6962           default:
6963             /* ignore */
6964             continue;
6965           case WhiteWins:
6966           case BlackWins:
6967           case GameIsDrawn:
6968           case GameUnfinished:
6969             if (gameMode == IcsExamining) {
6970                 if (boardIndex < backwardMostMove) {
6971                     /* Oops, gap.  How did that happen? */
6972                     return;
6973                 }
6974                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6975                 return;
6976             }
6977             gameInfo.result = moveType;
6978             p = strchr(yy_text, '{');
6979             if (p == NULL) p = strchr(yy_text, '(');
6980             if (p == NULL) {
6981                 p = yy_text;
6982                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6983             } else {
6984                 q = strchr(p, *p == '{' ? '}' : ')');
6985                 if (q != NULL) *q = NULLCHAR;
6986                 p++;
6987             }
6988             gameInfo.resultDetails = StrSave(p);
6989             continue;
6990         }
6991         if (boardIndex >= forwardMostMove &&
6992             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6993             backwardMostMove = blackPlaysFirst ? 1 : 0;
6994             return;
6995         }
6996         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6997                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6998                                  parseList[boardIndex]);
6999         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7000         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7001         /* currentMoveString is set as a side-effect of yylex */
7002         strcpy(moveList[boardIndex], currentMoveString);
7003         strcat(moveList[boardIndex], "\n");
7004         boardIndex++;
7005         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7006                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7007         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7008                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7009           case MT_NONE:
7010           case MT_STALEMATE:
7011           default:
7012             break;
7013           case MT_CHECK:
7014             if(gameInfo.variant != VariantShogi)
7015                 strcat(parseList[boardIndex - 1], "+");
7016             break;
7017           case MT_CHECKMATE:
7018           case MT_STAINMATE:
7019             strcat(parseList[boardIndex - 1], "#");
7020             break;
7021         }
7022     }
7023 }
7024
7025
7026 /* Apply a move to the given board  */
7027 void
7028 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7029      int fromX, fromY, toX, toY;
7030      int promoChar;
7031      Board board;
7032      char *castling;
7033      char *ep;
7034 {
7035   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7036
7037     /* [HGM] compute & store e.p. status and castling rights for new position */
7038     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7039     { int i;
7040
7041       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7042       oldEP = *ep;
7043       *ep = EP_NONE;
7044
7045       if( board[toY][toX] != EmptySquare ) 
7046            *ep = EP_CAPTURE;  
7047
7048       if( board[fromY][fromX] == WhitePawn ) {
7049            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7050                *ep = EP_PAWN_MOVE;
7051            if( toY-fromY==2) {
7052                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7053                         gameInfo.variant != VariantBerolina || toX < fromX)
7054                       *ep = toX | berolina;
7055                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7056                         gameInfo.variant != VariantBerolina || toX > fromX) 
7057                       *ep = toX;
7058            }
7059       } else 
7060       if( board[fromY][fromX] == BlackPawn ) {
7061            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7062                *ep = EP_PAWN_MOVE; 
7063            if( toY-fromY== -2) {
7064                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7065                         gameInfo.variant != VariantBerolina || toX < fromX)
7066                       *ep = toX | berolina;
7067                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7068                         gameInfo.variant != VariantBerolina || toX > fromX) 
7069                       *ep = toX;
7070            }
7071        }
7072
7073        for(i=0; i<nrCastlingRights; i++) {
7074            if(castling[i] == fromX && castlingRank[i] == fromY ||
7075               castling[i] == toX   && castlingRank[i] == toY   
7076              ) castling[i] = -1; // revoke for moved or captured piece
7077        }
7078
7079     }
7080
7081   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7082   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7083        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7084          
7085   if (fromX == toX && fromY == toY) return;
7086
7087   if (fromY == DROP_RANK) {
7088         /* must be first */
7089         piece = board[toY][toX] = (ChessSquare) fromX;
7090   } else {
7091      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7092      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7093      if(gameInfo.variant == VariantKnightmate)
7094          king += (int) WhiteUnicorn - (int) WhiteKing;
7095
7096     /* Code added by Tord: */
7097     /* FRC castling assumed when king captures friendly rook. */
7098     if (board[fromY][fromX] == WhiteKing &&
7099              board[toY][toX] == WhiteRook) {
7100       board[fromY][fromX] = EmptySquare;
7101       board[toY][toX] = EmptySquare;
7102       if(toX > fromX) {
7103         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7104       } else {
7105         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7106       }
7107     } else if (board[fromY][fromX] == BlackKing &&
7108                board[toY][toX] == BlackRook) {
7109       board[fromY][fromX] = EmptySquare;
7110       board[toY][toX] = EmptySquare;
7111       if(toX > fromX) {
7112         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7113       } else {
7114         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7115       }
7116     /* End of code added by Tord */
7117
7118     } else if (board[fromY][fromX] == king
7119         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7120         && toY == fromY && toX > fromX+1) {
7121         board[fromY][fromX] = EmptySquare;
7122         board[toY][toX] = king;
7123         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7124         board[fromY][BOARD_RGHT-1] = EmptySquare;
7125     } else if (board[fromY][fromX] == king
7126         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7127                && toY == fromY && toX < fromX-1) {
7128         board[fromY][fromX] = EmptySquare;
7129         board[toY][toX] = king;
7130         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7131         board[fromY][BOARD_LEFT] = EmptySquare;
7132     } else if (board[fromY][fromX] == WhitePawn
7133                && toY == BOARD_HEIGHT-1
7134                && gameInfo.variant != VariantXiangqi
7135                ) {
7136         /* white pawn promotion */
7137         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7138         if (board[toY][toX] == EmptySquare) {
7139             board[toY][toX] = WhiteQueen;
7140         }
7141         if(gameInfo.variant==VariantBughouse ||
7142            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7143             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7144         board[fromY][fromX] = EmptySquare;
7145     } else if ((fromY == BOARD_HEIGHT-4)
7146                && (toX != fromX)
7147                && gameInfo.variant != VariantXiangqi
7148                && gameInfo.variant != VariantBerolina
7149                && (board[fromY][fromX] == WhitePawn)
7150                && (board[toY][toX] == EmptySquare)) {
7151         board[fromY][fromX] = EmptySquare;
7152         board[toY][toX] = WhitePawn;
7153         captured = board[toY - 1][toX];
7154         board[toY - 1][toX] = EmptySquare;
7155     } else if ((fromY == BOARD_HEIGHT-4)
7156                && (toX == fromX)
7157                && gameInfo.variant == VariantBerolina
7158                && (board[fromY][fromX] == WhitePawn)
7159                && (board[toY][toX] == EmptySquare)) {
7160         board[fromY][fromX] = EmptySquare;
7161         board[toY][toX] = WhitePawn;
7162         if(oldEP & EP_BEROLIN_A) {
7163                 captured = board[fromY][fromX-1];
7164                 board[fromY][fromX-1] = EmptySquare;
7165         }else{  captured = board[fromY][fromX+1];
7166                 board[fromY][fromX+1] = EmptySquare;
7167         }
7168     } else if (board[fromY][fromX] == king
7169         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7170                && toY == fromY && toX > fromX+1) {
7171         board[fromY][fromX] = EmptySquare;
7172         board[toY][toX] = king;
7173         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7174         board[fromY][BOARD_RGHT-1] = EmptySquare;
7175     } else if (board[fromY][fromX] == king
7176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177                && toY == fromY && toX < fromX-1) {
7178         board[fromY][fromX] = EmptySquare;
7179         board[toY][toX] = king;
7180         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7181         board[fromY][BOARD_LEFT] = EmptySquare;
7182     } else if (fromY == 7 && fromX == 3
7183                && board[fromY][fromX] == BlackKing
7184                && toY == 7 && toX == 5) {
7185         board[fromY][fromX] = EmptySquare;
7186         board[toY][toX] = BlackKing;
7187         board[fromY][7] = EmptySquare;
7188         board[toY][4] = BlackRook;
7189     } else if (fromY == 7 && fromX == 3
7190                && board[fromY][fromX] == BlackKing
7191                && toY == 7 && toX == 1) {
7192         board[fromY][fromX] = EmptySquare;
7193         board[toY][toX] = BlackKing;
7194         board[fromY][0] = EmptySquare;
7195         board[toY][2] = BlackRook;
7196     } else if (board[fromY][fromX] == BlackPawn
7197                && toY == 0
7198                && gameInfo.variant != VariantXiangqi
7199                ) {
7200         /* black pawn promotion */
7201         board[0][toX] = CharToPiece(ToLower(promoChar));
7202         if (board[0][toX] == EmptySquare) {
7203             board[0][toX] = BlackQueen;
7204         }
7205         if(gameInfo.variant==VariantBughouse ||
7206            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7207             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7208         board[fromY][fromX] = EmptySquare;
7209     } else if ((fromY == 3)
7210                && (toX != fromX)
7211                && gameInfo.variant != VariantXiangqi
7212                && gameInfo.variant != VariantBerolina
7213                && (board[fromY][fromX] == BlackPawn)
7214                && (board[toY][toX] == EmptySquare)) {
7215         board[fromY][fromX] = EmptySquare;
7216         board[toY][toX] = BlackPawn;
7217         captured = board[toY + 1][toX];
7218         board[toY + 1][toX] = EmptySquare;
7219     } else if ((fromY == 3)
7220                && (toX == fromX)
7221                && gameInfo.variant == VariantBerolina
7222                && (board[fromY][fromX] == BlackPawn)
7223                && (board[toY][toX] == EmptySquare)) {
7224         board[fromY][fromX] = EmptySquare;
7225         board[toY][toX] = BlackPawn;
7226         if(oldEP & EP_BEROLIN_A) {
7227                 captured = board[fromY][fromX-1];
7228                 board[fromY][fromX-1] = EmptySquare;
7229         }else{  captured = board[fromY][fromX+1];
7230                 board[fromY][fromX+1] = EmptySquare;
7231         }
7232     } else {
7233         board[toY][toX] = board[fromY][fromX];
7234         board[fromY][fromX] = EmptySquare;
7235     }
7236
7237     /* [HGM] now we promote for Shogi, if needed */
7238     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7239         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7240   }
7241
7242     if (gameInfo.holdingsWidth != 0) {
7243
7244       /* !!A lot more code needs to be written to support holdings  */
7245       /* [HGM] OK, so I have written it. Holdings are stored in the */
7246       /* penultimate board files, so they are automaticlly stored   */
7247       /* in the game history.                                       */
7248       if (fromY == DROP_RANK) {
7249         /* Delete from holdings, by decreasing count */
7250         /* and erasing image if necessary            */
7251         p = (int) fromX;
7252         if(p < (int) BlackPawn) { /* white drop */
7253              p -= (int)WhitePawn;
7254              if(p >= gameInfo.holdingsSize) p = 0;
7255              if(--board[p][BOARD_WIDTH-2] == 0)
7256                   board[p][BOARD_WIDTH-1] = EmptySquare;
7257         } else {                  /* black drop */
7258              p -= (int)BlackPawn;
7259              if(p >= gameInfo.holdingsSize) p = 0;
7260              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7261                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7262         }
7263       }
7264       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7265           && gameInfo.variant != VariantBughouse        ) {
7266         /* [HGM] holdings: Add to holdings, if holdings exist */
7267         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7268                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7269                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7270         }
7271         p = (int) captured;
7272         if (p >= (int) BlackPawn) {
7273           p -= (int)BlackPawn;
7274           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7275                   /* in Shogi restore piece to its original  first */
7276                   captured = (ChessSquare) (DEMOTED captured);
7277                   p = DEMOTED p;
7278           }
7279           p = PieceToNumber((ChessSquare)p);
7280           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7281           board[p][BOARD_WIDTH-2]++;
7282           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7283         } else {
7284           p -= (int)WhitePawn;
7285           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7286                   captured = (ChessSquare) (DEMOTED captured);
7287                   p = DEMOTED p;
7288           }
7289           p = PieceToNumber((ChessSquare)p);
7290           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7291           board[BOARD_HEIGHT-1-p][1]++;
7292           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7293         }
7294       }
7295
7296     } else if (gameInfo.variant == VariantAtomic) {
7297       if (captured != EmptySquare) {
7298         int y, x;
7299         for (y = toY-1; y <= toY+1; y++) {
7300           for (x = toX-1; x <= toX+1; x++) {
7301             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7302                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7303               board[y][x] = EmptySquare;
7304             }
7305           }
7306         }
7307         board[toY][toX] = EmptySquare;
7308       }
7309     }
7310     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7311         /* [HGM] Shogi promotions */
7312         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7313     }
7314
7315     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7316                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7317         // [HGM] superchess: take promotion piece out of holdings
7318         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7319         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7320             if(!--board[k][BOARD_WIDTH-2])
7321                 board[k][BOARD_WIDTH-1] = EmptySquare;
7322         } else {
7323             if(!--board[BOARD_HEIGHT-1-k][1])
7324                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7325         }
7326     }
7327
7328 }
7329
7330 /* Updates forwardMostMove */
7331 void
7332 MakeMove(fromX, fromY, toX, toY, promoChar)
7333      int fromX, fromY, toX, toY;
7334      int promoChar;
7335 {
7336 //    forwardMostMove++; // [HGM] bare: moved downstream
7337
7338     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7339         int timeLeft; static int lastLoadFlag=0; int king, piece;
7340         piece = boards[forwardMostMove][fromY][fromX];
7341         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7342         if(gameInfo.variant == VariantKnightmate)
7343             king += (int) WhiteUnicorn - (int) WhiteKing;
7344         if(forwardMostMove == 0) {
7345             if(blackPlaysFirst) 
7346                 fprintf(serverMoves, "%s;", second.tidy);
7347             fprintf(serverMoves, "%s;", first.tidy);
7348             if(!blackPlaysFirst) 
7349                 fprintf(serverMoves, "%s;", second.tidy);
7350         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7351         lastLoadFlag = loadFlag;
7352         // print base move
7353         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7354         // print castling suffix
7355         if( toY == fromY && piece == king ) {
7356             if(toX-fromX > 1)
7357                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7358             if(fromX-toX >1)
7359                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7360         }
7361         // e.p. suffix
7362         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7363              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7364              boards[forwardMostMove][toY][toX] == EmptySquare
7365              && fromX != toX )
7366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7367         // promotion suffix
7368         if(promoChar != NULLCHAR)
7369                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7370         if(!loadFlag) {
7371             fprintf(serverMoves, "/%d/%d",
7372                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7373             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7374             else                      timeLeft = blackTimeRemaining/1000;
7375             fprintf(serverMoves, "/%d", timeLeft);
7376         }
7377         fflush(serverMoves);
7378     }
7379
7380     if (forwardMostMove+1 >= MAX_MOVES) {
7381       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7382                         0, 1);
7383       return;
7384     }
7385     SwitchClocks();
7386     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7387     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7388     if (commentList[forwardMostMove+1] != NULL) {
7389         free(commentList[forwardMostMove+1]);
7390         commentList[forwardMostMove+1] = NULL;
7391     }
7392     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7393     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7394     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7395                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7396     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7397     gameInfo.result = GameUnfinished;
7398     if (gameInfo.resultDetails != NULL) {
7399         free(gameInfo.resultDetails);
7400         gameInfo.resultDetails = NULL;
7401     }
7402     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7403                               moveList[forwardMostMove - 1]);
7404     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7405                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7406                              fromY, fromX, toY, toX, promoChar,
7407                              parseList[forwardMostMove - 1]);
7408     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7409                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7410                             castlingRights[forwardMostMove]) ) {
7411       case MT_NONE:
7412       case MT_STALEMATE:
7413       default:
7414         break;
7415       case MT_CHECK:
7416         if(gameInfo.variant != VariantShogi)
7417             strcat(parseList[forwardMostMove - 1], "+");
7418         break;
7419       case MT_CHECKMATE:
7420       case MT_STAINMATE:
7421         strcat(parseList[forwardMostMove - 1], "#");
7422         break;
7423     }
7424     if (appData.debugMode) {
7425         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7426     }
7427
7428 }
7429
7430 /* Updates currentMove if not pausing */
7431 void
7432 ShowMove(fromX, fromY, toX, toY)
7433 {
7434     int instant = (gameMode == PlayFromGameFile) ?
7435         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7436     if(appData.noGUI) return;
7437     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7438         if (!instant) {
7439             if (forwardMostMove == currentMove + 1) {
7440                 AnimateMove(boards[forwardMostMove - 1],
7441                             fromX, fromY, toX, toY);
7442             }
7443             if (appData.highlightLastMove) {
7444                 SetHighlights(fromX, fromY, toX, toY);
7445             }
7446         }
7447         currentMove = forwardMostMove;
7448     }
7449
7450     if (instant) return;
7451
7452     DisplayMove(currentMove - 1);
7453     DrawPosition(FALSE, boards[currentMove]);
7454     DisplayBothClocks();
7455     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7456 }
7457
7458 void SendEgtPath(ChessProgramState *cps)
7459 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7460         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7461
7462         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7463
7464         while(*p) {
7465             char c, *q = name+1, *r, *s;
7466
7467             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7468             while(*p && *p != ',') *q++ = *p++;
7469             *q++ = ':'; *q = 0;
7470             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7471                 strcmp(name, ",nalimov:") == 0 ) {
7472                 // take nalimov path from the menu-changeable option first, if it is defined
7473                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7474                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7475             } else
7476             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7477                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7478                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7479                 s = r = StrStr(s, ":") + 1; // beginning of path info
7480                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7481                 c = *r; *r = 0;             // temporarily null-terminate path info
7482                     *--q = 0;               // strip of trailig ':' from name
7483                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7484                 *r = c;
7485                 SendToProgram(buf,cps);     // send egtbpath command for this format
7486             }
7487             if(*p == ',') p++; // read away comma to position for next format name
7488         }
7489 }
7490
7491 void
7492 InitChessProgram(cps, setup)
7493      ChessProgramState *cps;
7494      int setup; /* [HGM] needed to setup FRC opening position */
7495 {
7496     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7497     if (appData.noChessProgram) return;
7498     hintRequested = FALSE;
7499     bookRequested = FALSE;
7500
7501     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7502     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7503     if(cps->memSize) { /* [HGM] memory */
7504         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7505         SendToProgram(buf, cps);
7506     }
7507     SendEgtPath(cps); /* [HGM] EGT */
7508     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7509         sprintf(buf, "cores %d\n", appData.smpCores);
7510         SendToProgram(buf, cps);
7511     }
7512
7513     SendToProgram(cps->initString, cps);
7514     if (gameInfo.variant != VariantNormal &&
7515         gameInfo.variant != VariantLoadable
7516         /* [HGM] also send variant if board size non-standard */
7517         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7518                                             ) {
7519       char *v = VariantName(gameInfo.variant);
7520       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7521         /* [HGM] in protocol 1 we have to assume all variants valid */
7522         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7523         DisplayFatalError(buf, 0, 1);
7524         return;
7525       }
7526
7527       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7528       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7529       if( gameInfo.variant == VariantXiangqi )
7530            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7531       if( gameInfo.variant == VariantShogi )
7532            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7533       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7534            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7535       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7536                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7537            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7538       if( gameInfo.variant == VariantCourier )
7539            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7540       if( gameInfo.variant == VariantSuper )
7541            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7542       if( gameInfo.variant == VariantGreat )
7543            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7544
7545       if(overruled) {
7546            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7547                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7548            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7549            if(StrStr(cps->variants, b) == NULL) { 
7550                // specific sized variant not known, check if general sizing allowed
7551                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7552                    if(StrStr(cps->variants, "boardsize") == NULL) {
7553                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7554                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7555                        DisplayFatalError(buf, 0, 1);
7556                        return;
7557                    }
7558                    /* [HGM] here we really should compare with the maximum supported board size */
7559                }
7560            }
7561       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7562       sprintf(buf, "variant %s\n", b);
7563       SendToProgram(buf, cps);
7564     }
7565     currentlyInitializedVariant = gameInfo.variant;
7566
7567     /* [HGM] send opening position in FRC to first engine */
7568     if(setup) {
7569           SendToProgram("force\n", cps);
7570           SendBoard(cps, 0);
7571           /* engine is now in force mode! Set flag to wake it up after first move. */
7572           setboardSpoiledMachineBlack = 1;
7573     }
7574
7575     if (cps->sendICS) {
7576       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7577       SendToProgram(buf, cps);
7578     }
7579     cps->maybeThinking = FALSE;
7580     cps->offeredDraw = 0;
7581     if (!appData.icsActive) {
7582         SendTimeControl(cps, movesPerSession, timeControl,
7583                         timeIncrement, appData.searchDepth,
7584                         searchTime);
7585     }
7586     if (appData.showThinking 
7587         // [HGM] thinking: four options require thinking output to be sent
7588         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7589                                 ) {
7590         SendToProgram("post\n", cps);
7591     }
7592     SendToProgram("hard\n", cps);
7593     if (!appData.ponderNextMove) {
7594         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7595            it without being sure what state we are in first.  "hard"
7596            is not a toggle, so that one is OK.
7597          */
7598         SendToProgram("easy\n", cps);
7599     }
7600     if (cps->usePing) {
7601       sprintf(buf, "ping %d\n", ++cps->lastPing);
7602       SendToProgram(buf, cps);
7603     }
7604     cps->initDone = TRUE;
7605 }   
7606
7607
7608 void
7609 StartChessProgram(cps)
7610      ChessProgramState *cps;
7611 {
7612     char buf[MSG_SIZ];
7613     int err;
7614
7615     if (appData.noChessProgram) return;
7616     cps->initDone = FALSE;
7617
7618     if (strcmp(cps->host, "localhost") == 0) {
7619         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7620     } else if (*appData.remoteShell == NULLCHAR) {
7621         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7622     } else {
7623         if (*appData.remoteUser == NULLCHAR) {
7624           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7625                     cps->program);
7626         } else {
7627           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7628                     cps->host, appData.remoteUser, cps->program);
7629         }
7630         err = StartChildProcess(buf, "", &cps->pr);
7631     }
7632     
7633     if (err != 0) {
7634         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7635         DisplayFatalError(buf, err, 1);
7636         cps->pr = NoProc;
7637         cps->isr = NULL;
7638         return;
7639     }
7640     
7641     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7642     if (cps->protocolVersion > 1) {
7643       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7644       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7645       cps->comboCnt = 0;  //                and values of combo boxes
7646       SendToProgram(buf, cps);
7647     } else {
7648       SendToProgram("xboard\n", cps);
7649     }
7650 }
7651
7652
7653 void
7654 TwoMachinesEventIfReady P((void))
7655 {
7656   if (first.lastPing != first.lastPong) {
7657     DisplayMessage("", _("Waiting for first chess program"));
7658     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7659     return;
7660   }
7661   if (second.lastPing != second.lastPong) {
7662     DisplayMessage("", _("Waiting for second chess program"));
7663     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7664     return;
7665   }
7666   ThawUI();
7667   TwoMachinesEvent();
7668 }
7669
7670 void
7671 NextMatchGame P((void))
7672 {
7673     int index; /* [HGM] autoinc: step lod index during match */
7674     Reset(FALSE, TRUE);
7675     if (*appData.loadGameFile != NULLCHAR) {
7676         index = appData.loadGameIndex;
7677         if(index < 0) { // [HGM] autoinc
7678             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7679             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7680         } 
7681         LoadGameFromFile(appData.loadGameFile,
7682                          index,
7683                          appData.loadGameFile, FALSE);
7684     } else if (*appData.loadPositionFile != NULLCHAR) {
7685         index = appData.loadPositionIndex;
7686         if(index < 0) { // [HGM] autoinc
7687             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7688             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7689         } 
7690         LoadPositionFromFile(appData.loadPositionFile,
7691                              index,
7692                              appData.loadPositionFile);
7693     }
7694     TwoMachinesEventIfReady();
7695 }
7696
7697 void UserAdjudicationEvent( int result )
7698 {
7699     ChessMove gameResult = GameIsDrawn;
7700
7701     if( result > 0 ) {
7702         gameResult = WhiteWins;
7703     }
7704     else if( result < 0 ) {
7705         gameResult = BlackWins;
7706     }
7707
7708     if( gameMode == TwoMachinesPlay ) {
7709         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7710     }
7711 }
7712
7713
7714 void
7715 GameEnds(result, resultDetails, whosays)
7716      ChessMove result;
7717      char *resultDetails;
7718      int whosays;
7719 {
7720     GameMode nextGameMode;
7721     int isIcsGame;
7722     char buf[MSG_SIZ];
7723
7724     if(endingGame) return; /* [HGM] crash: forbid recursion */
7725     endingGame = 1;
7726
7727     if (appData.debugMode) {
7728       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7729               result, resultDetails ? resultDetails : "(null)", whosays);
7730     }
7731
7732     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7733         /* If we are playing on ICS, the server decides when the
7734            game is over, but the engine can offer to draw, claim 
7735            a draw, or resign. 
7736          */
7737 #if ZIPPY
7738         if (appData.zippyPlay && first.initDone) {
7739             if (result == GameIsDrawn) {
7740                 /* In case draw still needs to be claimed */
7741                 SendToICS(ics_prefix);
7742                 SendToICS("draw\n");
7743             } else if (StrCaseStr(resultDetails, "resign")) {
7744                 SendToICS(ics_prefix);
7745                 SendToICS("resign\n");
7746             }
7747         }
7748 #endif
7749         endingGame = 0; /* [HGM] crash */
7750         return;
7751     }
7752
7753     /* If we're loading the game from a file, stop */
7754     if (whosays == GE_FILE) {
7755       (void) StopLoadGameTimer();
7756       gameFileFP = NULL;
7757     }
7758
7759     /* Cancel draw offers */
7760     first.offeredDraw = second.offeredDraw = 0;
7761
7762     /* If this is an ICS game, only ICS can really say it's done;
7763        if not, anyone can. */
7764     isIcsGame = (gameMode == IcsPlayingWhite || 
7765                  gameMode == IcsPlayingBlack || 
7766                  gameMode == IcsObserving    || 
7767                  gameMode == IcsExamining);
7768
7769     if (!isIcsGame || whosays == GE_ICS) {
7770         /* OK -- not an ICS game, or ICS said it was done */
7771         StopClocks();
7772         if (!isIcsGame && !appData.noChessProgram) 
7773           SetUserThinkingEnables();
7774     
7775         /* [HGM] if a machine claims the game end we verify this claim */
7776         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7777             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7778                 char claimer;
7779                 ChessMove trueResult = (ChessMove) -1;
7780
7781                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7782                                             first.twoMachinesColor[0] :
7783                                             second.twoMachinesColor[0] ;
7784
7785                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7786                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7787                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7788                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7789                 } else
7790                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7791                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7792                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7793                 } else
7794                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7795                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7796                 }
7797
7798                 // now verify win claims, but not in drop games, as we don't understand those yet
7799                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7800                                                  || gameInfo.variant == VariantGreat) &&
7801                     (result == WhiteWins && claimer == 'w' ||
7802                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7803                       if (appData.debugMode) {
7804                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7805                                 result, epStatus[forwardMostMove], forwardMostMove);
7806                       }
7807                       if(result != trueResult) {
7808                               sprintf(buf, "False win claim: '%s'", resultDetails);
7809                               result = claimer == 'w' ? BlackWins : WhiteWins;
7810                               resultDetails = buf;
7811                       }
7812                 } else
7813                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7814                     && (forwardMostMove <= backwardMostMove ||
7815                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7816                         (claimer=='b')==(forwardMostMove&1))
7817                                                                                   ) {
7818                       /* [HGM] verify: draws that were not flagged are false claims */
7819                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7820                       result = claimer == 'w' ? BlackWins : WhiteWins;
7821                       resultDetails = buf;
7822                 }
7823                 /* (Claiming a loss is accepted no questions asked!) */
7824             }
7825             /* [HGM] bare: don't allow bare King to win */
7826             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7827                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7828                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7829                && result != GameIsDrawn)
7830             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7831                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7832                         int p = (int)boards[forwardMostMove][i][j] - color;
7833                         if(p >= 0 && p <= (int)WhiteKing) k++;
7834                 }
7835                 if (appData.debugMode) {
7836                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7837                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7838                 }
7839                 if(k <= 1) {
7840                         result = GameIsDrawn;
7841                         sprintf(buf, "%s but bare king", resultDetails);
7842                         resultDetails = buf;
7843                 }
7844             }
7845         }
7846
7847
7848         if(serverMoves != NULL && !loadFlag) { char c = '=';
7849             if(result==WhiteWins) c = '+';
7850             if(result==BlackWins) c = '-';
7851             if(resultDetails != NULL)
7852                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7853         }
7854         if (resultDetails != NULL) {
7855             gameInfo.result = result;
7856             gameInfo.resultDetails = StrSave(resultDetails);
7857
7858             /* display last move only if game was not loaded from file */
7859             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7860                 DisplayMove(currentMove - 1);
7861     
7862             if (forwardMostMove != 0) {
7863                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7864                     if (*appData.saveGameFile != NULLCHAR) {
7865                         SaveGameToFile(appData.saveGameFile, TRUE);
7866                     } else if (appData.autoSaveGames) {
7867                         AutoSaveGame();
7868                     }
7869                     if (*appData.savePositionFile != NULLCHAR) {
7870                         SavePositionToFile(appData.savePositionFile);
7871                     }
7872                 }
7873             }
7874
7875             /* Tell program how game ended in case it is learning */
7876             /* [HGM] Moved this to after saving the PGN, just in case */
7877             /* engine died and we got here through time loss. In that */
7878             /* case we will get a fatal error writing the pipe, which */
7879             /* would otherwise lose us the PGN.                       */
7880             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7881             /* output during GameEnds should never be fatal anymore   */
7882             if (gameMode == MachinePlaysWhite ||
7883                 gameMode == MachinePlaysBlack ||
7884                 gameMode == TwoMachinesPlay ||
7885                 gameMode == IcsPlayingWhite ||
7886                 gameMode == IcsPlayingBlack ||
7887                 gameMode == BeginningOfGame) {
7888                 char buf[MSG_SIZ];
7889                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7890                         resultDetails);
7891                 if (first.pr != NoProc) {
7892                     SendToProgram(buf, &first);
7893                 }
7894                 if (second.pr != NoProc &&
7895                     gameMode == TwoMachinesPlay) {
7896                     SendToProgram(buf, &second);
7897                 }
7898             }
7899         }
7900
7901         if (appData.icsActive) {
7902             if (appData.quietPlay &&
7903                 (gameMode == IcsPlayingWhite ||
7904                  gameMode == IcsPlayingBlack)) {
7905                 SendToICS(ics_prefix);
7906                 SendToICS("set shout 1\n");
7907             }
7908             nextGameMode = IcsIdle;
7909             ics_user_moved = FALSE;
7910             /* clean up premove.  It's ugly when the game has ended and the
7911              * premove highlights are still on the board.
7912              */
7913             if (gotPremove) {
7914               gotPremove = FALSE;
7915               ClearPremoveHighlights();
7916               DrawPosition(FALSE, boards[currentMove]);
7917             }
7918             if (whosays == GE_ICS) {
7919                 switch (result) {
7920                 case WhiteWins:
7921                     if (gameMode == IcsPlayingWhite)
7922                         PlayIcsWinSound();
7923                     else if(gameMode == IcsPlayingBlack)
7924                         PlayIcsLossSound();
7925                     break;
7926                 case BlackWins:
7927                     if (gameMode == IcsPlayingBlack)
7928                         PlayIcsWinSound();
7929                     else if(gameMode == IcsPlayingWhite)
7930                         PlayIcsLossSound();
7931                     break;
7932                 case GameIsDrawn:
7933                     PlayIcsDrawSound();
7934                     break;
7935                 default:
7936                     PlayIcsUnfinishedSound();
7937                 }
7938             }
7939         } else if (gameMode == EditGame ||
7940                    gameMode == PlayFromGameFile || 
7941                    gameMode == AnalyzeMode || 
7942                    gameMode == AnalyzeFile) {
7943             nextGameMode = gameMode;
7944         } else {
7945             nextGameMode = EndOfGame;
7946         }
7947         pausing = FALSE;
7948         ModeHighlight();
7949     } else {
7950         nextGameMode = gameMode;
7951     }
7952
7953     if (appData.noChessProgram) {
7954         gameMode = nextGameMode;
7955         ModeHighlight();
7956         endingGame = 0; /* [HGM] crash */
7957         return;
7958     }
7959
7960     if (first.reuse) {
7961         /* Put first chess program into idle state */
7962         if (first.pr != NoProc &&
7963             (gameMode == MachinePlaysWhite ||
7964              gameMode == MachinePlaysBlack ||
7965              gameMode == TwoMachinesPlay ||
7966              gameMode == IcsPlayingWhite ||
7967              gameMode == IcsPlayingBlack ||
7968              gameMode == BeginningOfGame)) {
7969             SendToProgram("force\n", &first);
7970             if (first.usePing) {
7971               char buf[MSG_SIZ];
7972               sprintf(buf, "ping %d\n", ++first.lastPing);
7973               SendToProgram(buf, &first);
7974             }
7975         }
7976     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7977         /* Kill off first chess program */
7978         if (first.isr != NULL)
7979           RemoveInputSource(first.isr);
7980         first.isr = NULL;
7981     
7982         if (first.pr != NoProc) {
7983             ExitAnalyzeMode();
7984             DoSleep( appData.delayBeforeQuit );
7985             SendToProgram("quit\n", &first);
7986             DoSleep( appData.delayAfterQuit );
7987             DestroyChildProcess(first.pr, first.useSigterm);
7988         }
7989         first.pr = NoProc;
7990     }
7991     if (second.reuse) {
7992         /* Put second chess program into idle state */
7993         if (second.pr != NoProc &&
7994             gameMode == TwoMachinesPlay) {
7995             SendToProgram("force\n", &second);
7996             if (second.usePing) {
7997               char buf[MSG_SIZ];
7998               sprintf(buf, "ping %d\n", ++second.lastPing);
7999               SendToProgram(buf, &second);
8000             }
8001         }
8002     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8003         /* Kill off second chess program */
8004         if (second.isr != NULL)
8005           RemoveInputSource(second.isr);
8006         second.isr = NULL;
8007     
8008         if (second.pr != NoProc) {
8009             DoSleep( appData.delayBeforeQuit );
8010             SendToProgram("quit\n", &second);
8011             DoSleep( appData.delayAfterQuit );
8012             DestroyChildProcess(second.pr, second.useSigterm);
8013         }
8014         second.pr = NoProc;
8015     }
8016
8017     if (matchMode && gameMode == TwoMachinesPlay) {
8018         switch (result) {
8019         case WhiteWins:
8020           if (first.twoMachinesColor[0] == 'w') {
8021             first.matchWins++;
8022           } else {
8023             second.matchWins++;
8024           }
8025           break;
8026         case BlackWins:
8027           if (first.twoMachinesColor[0] == 'b') {
8028             first.matchWins++;
8029           } else {
8030             second.matchWins++;
8031           }
8032           break;
8033         default:
8034           break;
8035         }
8036         if (matchGame < appData.matchGames) {
8037             char *tmp;
8038             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8039                 tmp = first.twoMachinesColor;
8040                 first.twoMachinesColor = second.twoMachinesColor;
8041                 second.twoMachinesColor = tmp;
8042             }
8043             gameMode = nextGameMode;
8044             matchGame++;
8045             if(appData.matchPause>10000 || appData.matchPause<10)
8046                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8047             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8048             endingGame = 0; /* [HGM] crash */
8049             return;
8050         } else {
8051             char buf[MSG_SIZ];
8052             gameMode = nextGameMode;
8053             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8054                     first.tidy, second.tidy,
8055                     first.matchWins, second.matchWins,
8056                     appData.matchGames - (first.matchWins + second.matchWins));
8057             DisplayFatalError(buf, 0, 0);
8058         }
8059     }
8060     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8061         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8062       ExitAnalyzeMode();
8063     gameMode = nextGameMode;
8064     ModeHighlight();
8065     endingGame = 0;  /* [HGM] crash */
8066 }
8067
8068 /* Assumes program was just initialized (initString sent).
8069    Leaves program in force mode. */
8070 void
8071 FeedMovesToProgram(cps, upto) 
8072      ChessProgramState *cps;
8073      int upto;
8074 {
8075     int i;
8076     
8077     if (appData.debugMode)
8078       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8079               startedFromSetupPosition ? "position and " : "",
8080               backwardMostMove, upto, cps->which);
8081     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8082         // [HGM] variantswitch: make engine aware of new variant
8083         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8084                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8085         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8086         SendToProgram(buf, cps);
8087         currentlyInitializedVariant = gameInfo.variant;
8088     }
8089     SendToProgram("force\n", cps);
8090     if (startedFromSetupPosition) {
8091         SendBoard(cps, backwardMostMove);
8092     if (appData.debugMode) {
8093         fprintf(debugFP, "feedMoves\n");
8094     }
8095     }
8096     for (i = backwardMostMove; i < upto; i++) {
8097         SendMoveToProgram(i, cps);
8098     }
8099 }
8100
8101
8102 void
8103 ResurrectChessProgram()
8104 {
8105      /* The chess program may have exited.
8106         If so, restart it and feed it all the moves made so far. */
8107
8108     if (appData.noChessProgram || first.pr != NoProc) return;
8109     
8110     StartChessProgram(&first);
8111     InitChessProgram(&first, FALSE);
8112     FeedMovesToProgram(&first, currentMove);
8113
8114     if (!first.sendTime) {
8115         /* can't tell gnuchess what its clock should read,
8116            so we bow to its notion. */
8117         ResetClocks();
8118         timeRemaining[0][currentMove] = whiteTimeRemaining;
8119         timeRemaining[1][currentMove] = blackTimeRemaining;
8120     }
8121
8122     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8123                 appData.icsEngineAnalyze) && first.analysisSupport) {
8124       SendToProgram("analyze\n", &first);
8125       first.analyzing = TRUE;
8126     }
8127 }
8128
8129 /*
8130  * Button procedures
8131  */
8132 void
8133 Reset(redraw, init)
8134      int redraw, init;
8135 {
8136     int i;
8137
8138     if (appData.debugMode) {
8139         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8140                 redraw, init, gameMode);
8141     }
8142     pausing = pauseExamInvalid = FALSE;
8143     startedFromSetupPosition = blackPlaysFirst = FALSE;
8144     firstMove = TRUE;
8145     whiteFlag = blackFlag = FALSE;
8146     userOfferedDraw = FALSE;
8147     hintRequested = bookRequested = FALSE;
8148     first.maybeThinking = FALSE;
8149     second.maybeThinking = FALSE;
8150     first.bookSuspend = FALSE; // [HGM] book
8151     second.bookSuspend = FALSE;
8152     thinkOutput[0] = NULLCHAR;
8153     lastHint[0] = NULLCHAR;
8154     ClearGameInfo(&gameInfo);
8155     gameInfo.variant = StringToVariant(appData.variant);
8156     ics_user_moved = ics_clock_paused = FALSE;
8157     ics_getting_history = H_FALSE;
8158     ics_gamenum = -1;
8159     white_holding[0] = black_holding[0] = NULLCHAR;
8160     ClearProgramStats();
8161     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8162     
8163     ResetFrontEnd();
8164     ClearHighlights();
8165     flipView = appData.flipView;
8166     ClearPremoveHighlights();
8167     gotPremove = FALSE;
8168     alarmSounded = FALSE;
8169
8170     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8171     if(appData.serverMovesName != NULL) {
8172         /* [HGM] prepare to make moves file for broadcasting */
8173         clock_t t = clock();
8174         if(serverMoves != NULL) fclose(serverMoves);
8175         serverMoves = fopen(appData.serverMovesName, "r");
8176         if(serverMoves != NULL) {
8177             fclose(serverMoves);
8178             /* delay 15 sec before overwriting, so all clients can see end */
8179             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8180         }
8181         serverMoves = fopen(appData.serverMovesName, "w");
8182     }
8183
8184     ExitAnalyzeMode();
8185     gameMode = BeginningOfGame;
8186     ModeHighlight();
8187     if(appData.icsActive) gameInfo.variant = VariantNormal;
8188     InitPosition(redraw);
8189     for (i = 0; i < MAX_MOVES; i++) {
8190         if (commentList[i] != NULL) {
8191             free(commentList[i]);
8192             commentList[i] = NULL;
8193         }
8194     }
8195     ResetClocks();
8196     timeRemaining[0][0] = whiteTimeRemaining;
8197     timeRemaining[1][0] = blackTimeRemaining;
8198     if (first.pr == NULL) {
8199         StartChessProgram(&first);
8200     }
8201     if (init) {
8202             InitChessProgram(&first, startedFromSetupPosition);
8203     }
8204     DisplayTitle("");
8205     DisplayMessage("", "");
8206     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8207 }
8208
8209 void
8210 AutoPlayGameLoop()
8211 {
8212     for (;;) {
8213         if (!AutoPlayOneMove())
8214           return;
8215         if (matchMode || appData.timeDelay == 0)
8216           continue;
8217         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8218           return;
8219         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8220         break;
8221     }
8222 }
8223
8224
8225 int
8226 AutoPlayOneMove()
8227 {
8228     int fromX, fromY, toX, toY;
8229
8230     if (appData.debugMode) {
8231       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8232     }
8233
8234     if (gameMode != PlayFromGameFile)
8235       return FALSE;
8236
8237     if (currentMove >= forwardMostMove) {
8238       gameMode = EditGame;
8239       ModeHighlight();
8240
8241       /* [AS] Clear current move marker at the end of a game */
8242       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8243
8244       return FALSE;
8245     }
8246     
8247     toX = moveList[currentMove][2] - AAA;
8248     toY = moveList[currentMove][3] - ONE;
8249
8250     if (moveList[currentMove][1] == '@') {
8251         if (appData.highlightLastMove) {
8252             SetHighlights(-1, -1, toX, toY);
8253         }
8254     } else {
8255         fromX = moveList[currentMove][0] - AAA;
8256         fromY = moveList[currentMove][1] - ONE;
8257
8258         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8259
8260         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8261
8262         if (appData.highlightLastMove) {
8263             SetHighlights(fromX, fromY, toX, toY);
8264         }
8265     }
8266     DisplayMove(currentMove);
8267     SendMoveToProgram(currentMove++, &first);
8268     DisplayBothClocks();
8269     DrawPosition(FALSE, boards[currentMove]);
8270     // [HGM] PV info: always display, routine tests if empty
8271     DisplayComment(currentMove - 1, commentList[currentMove]);
8272     return TRUE;
8273 }
8274
8275
8276 int
8277 LoadGameOneMove(readAhead)
8278      ChessMove readAhead;
8279 {
8280     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8281     char promoChar = NULLCHAR;
8282     ChessMove moveType;
8283     char move[MSG_SIZ];
8284     char *p, *q;
8285     
8286     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8287         gameMode != AnalyzeMode && gameMode != Training) {
8288         gameFileFP = NULL;
8289         return FALSE;
8290     }
8291     
8292     yyboardindex = forwardMostMove;
8293     if (readAhead != (ChessMove)0) {
8294       moveType = readAhead;
8295     } else {
8296       if (gameFileFP == NULL)
8297           return FALSE;
8298       moveType = (ChessMove) yylex();
8299     }
8300     
8301     done = FALSE;
8302     switch (moveType) {
8303       case Comment:
8304         if (appData.debugMode) 
8305           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8306         p = yy_text;
8307         if (*p == '{' || *p == '[' || *p == '(') {
8308             p[strlen(p) - 1] = NULLCHAR;
8309             p++;
8310         }
8311
8312         /* append the comment but don't display it */
8313         while (*p == '\n') p++;
8314         AppendComment(currentMove, p);
8315         return TRUE;
8316
8317       case WhiteCapturesEnPassant:
8318       case BlackCapturesEnPassant:
8319       case WhitePromotionChancellor:
8320       case BlackPromotionChancellor:
8321       case WhitePromotionArchbishop:
8322       case BlackPromotionArchbishop:
8323       case WhitePromotionCentaur:
8324       case BlackPromotionCentaur:
8325       case WhitePromotionQueen:
8326       case BlackPromotionQueen:
8327       case WhitePromotionRook:
8328       case BlackPromotionRook:
8329       case WhitePromotionBishop:
8330       case BlackPromotionBishop:
8331       case WhitePromotionKnight:
8332       case BlackPromotionKnight:
8333       case WhitePromotionKing:
8334       case BlackPromotionKing:
8335       case NormalMove:
8336       case WhiteKingSideCastle:
8337       case WhiteQueenSideCastle:
8338       case BlackKingSideCastle:
8339       case BlackQueenSideCastle:
8340       case WhiteKingSideCastleWild:
8341       case WhiteQueenSideCastleWild:
8342       case BlackKingSideCastleWild:
8343       case BlackQueenSideCastleWild:
8344       /* PUSH Fabien */
8345       case WhiteHSideCastleFR:
8346       case WhiteASideCastleFR:
8347       case BlackHSideCastleFR:
8348       case BlackASideCastleFR:
8349       /* POP Fabien */
8350         if (appData.debugMode)
8351           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8352         fromX = currentMoveString[0] - AAA;
8353         fromY = currentMoveString[1] - ONE;
8354         toX = currentMoveString[2] - AAA;
8355         toY = currentMoveString[3] - ONE;
8356         promoChar = currentMoveString[4];
8357         break;
8358
8359       case WhiteDrop:
8360       case BlackDrop:
8361         if (appData.debugMode)
8362           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8363         fromX = moveType == WhiteDrop ?
8364           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8365         (int) CharToPiece(ToLower(currentMoveString[0]));
8366         fromY = DROP_RANK;
8367         toX = currentMoveString[2] - AAA;
8368         toY = currentMoveString[3] - ONE;
8369         break;
8370
8371       case WhiteWins:
8372       case BlackWins:
8373       case GameIsDrawn:
8374       case GameUnfinished:
8375         if (appData.debugMode)
8376           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8377         p = strchr(yy_text, '{');
8378         if (p == NULL) p = strchr(yy_text, '(');
8379         if (p == NULL) {
8380             p = yy_text;
8381             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8382         } else {
8383             q = strchr(p, *p == '{' ? '}' : ')');
8384             if (q != NULL) *q = NULLCHAR;
8385             p++;
8386         }
8387         GameEnds(moveType, p, GE_FILE);
8388         done = TRUE;
8389         if (cmailMsgLoaded) {
8390             ClearHighlights();
8391             flipView = WhiteOnMove(currentMove);
8392             if (moveType == GameUnfinished) flipView = !flipView;
8393             if (appData.debugMode)
8394               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8395         }
8396         break;
8397
8398       case (ChessMove) 0:       /* end of file */
8399         if (appData.debugMode)
8400           fprintf(debugFP, "Parser hit end of file\n");
8401         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8402                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8403           case MT_NONE:
8404           case MT_CHECK:
8405             break;
8406           case MT_CHECKMATE:
8407           case MT_STAINMATE:
8408             if (WhiteOnMove(currentMove)) {
8409                 GameEnds(BlackWins, "Black mates", GE_FILE);
8410             } else {
8411                 GameEnds(WhiteWins, "White mates", GE_FILE);
8412             }
8413             break;
8414           case MT_STALEMATE:
8415             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8416             break;
8417         }
8418         done = TRUE;
8419         break;
8420
8421       case MoveNumberOne:
8422         if (lastLoadGameStart == GNUChessGame) {
8423             /* GNUChessGames have numbers, but they aren't move numbers */
8424             if (appData.debugMode)
8425               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8426                       yy_text, (int) moveType);
8427             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8428         }
8429         /* else fall thru */
8430
8431       case XBoardGame:
8432       case GNUChessGame:
8433       case PGNTag:
8434         /* Reached start of next game in file */
8435         if (appData.debugMode)
8436           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8437         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8438                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8439           case MT_NONE:
8440           case MT_CHECK:
8441             break;
8442           case MT_CHECKMATE:
8443           case MT_STAINMATE:
8444             if (WhiteOnMove(currentMove)) {
8445                 GameEnds(BlackWins, "Black mates", GE_FILE);
8446             } else {
8447                 GameEnds(WhiteWins, "White mates", GE_FILE);
8448             }
8449             break;
8450           case MT_STALEMATE:
8451             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8452             break;
8453         }
8454         done = TRUE;
8455         break;
8456
8457       case PositionDiagram:     /* should not happen; ignore */
8458       case ElapsedTime:         /* ignore */
8459       case NAG:                 /* ignore */
8460         if (appData.debugMode)
8461           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8462                   yy_text, (int) moveType);
8463         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8464
8465       case IllegalMove:
8466         if (appData.testLegality) {
8467             if (appData.debugMode)
8468               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8469             sprintf(move, _("Illegal move: %d.%s%s"),
8470                     (forwardMostMove / 2) + 1,
8471                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8472             DisplayError(move, 0);
8473             done = TRUE;
8474         } else {
8475             if (appData.debugMode)
8476               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8477                       yy_text, currentMoveString);
8478             fromX = currentMoveString[0] - AAA;
8479             fromY = currentMoveString[1] - ONE;
8480             toX = currentMoveString[2] - AAA;
8481             toY = currentMoveString[3] - ONE;
8482             promoChar = currentMoveString[4];
8483         }
8484         break;
8485
8486       case AmbiguousMove:
8487         if (appData.debugMode)
8488           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8489         sprintf(move, _("Ambiguous move: %d.%s%s"),
8490                 (forwardMostMove / 2) + 1,
8491                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8492         DisplayError(move, 0);
8493         done = TRUE;
8494         break;
8495
8496       default:
8497       case ImpossibleMove:
8498         if (appData.debugMode)
8499           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8500         sprintf(move, _("Illegal move: %d.%s%s"),
8501                 (forwardMostMove / 2) + 1,
8502                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8503         DisplayError(move, 0);
8504         done = TRUE;
8505         break;
8506     }
8507
8508     if (done) {
8509         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8510             DrawPosition(FALSE, boards[currentMove]);
8511             DisplayBothClocks();
8512             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8513               DisplayComment(currentMove - 1, commentList[currentMove]);
8514         }
8515         (void) StopLoadGameTimer();
8516         gameFileFP = NULL;
8517         cmailOldMove = forwardMostMove;
8518         return FALSE;
8519     } else {
8520         /* currentMoveString is set as a side-effect of yylex */
8521         strcat(currentMoveString, "\n");
8522         strcpy(moveList[forwardMostMove], currentMoveString);
8523         
8524         thinkOutput[0] = NULLCHAR;
8525         MakeMove(fromX, fromY, toX, toY, promoChar);
8526         currentMove = forwardMostMove;
8527         return TRUE;
8528     }
8529 }
8530
8531 /* Load the nth game from the given file */
8532 int
8533 LoadGameFromFile(filename, n, title, useList)
8534      char *filename;
8535      int n;
8536      char *title;
8537      /*Boolean*/ int useList;
8538 {
8539     FILE *f;
8540     char buf[MSG_SIZ];
8541
8542     if (strcmp(filename, "-") == 0) {
8543         f = stdin;
8544         title = "stdin";
8545     } else {
8546         f = fopen(filename, "rb");
8547         if (f == NULL) {
8548           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8549             DisplayError(buf, errno);
8550             return FALSE;
8551         }
8552     }
8553     if (fseek(f, 0, 0) == -1) {
8554         /* f is not seekable; probably a pipe */
8555         useList = FALSE;
8556     }
8557     if (useList && n == 0) {
8558         int error = GameListBuild(f);
8559         if (error) {
8560             DisplayError(_("Cannot build game list"), error);
8561         } else if (!ListEmpty(&gameList) &&
8562                    ((ListGame *) gameList.tailPred)->number > 1) {
8563             GameListPopUp(f, title);
8564             return TRUE;
8565         }
8566         GameListDestroy();
8567         n = 1;
8568     }
8569     if (n == 0) n = 1;
8570     return LoadGame(f, n, title, FALSE);
8571 }
8572
8573
8574 void
8575 MakeRegisteredMove()
8576 {
8577     int fromX, fromY, toX, toY;
8578     char promoChar;
8579     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8580         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8581           case CMAIL_MOVE:
8582           case CMAIL_DRAW:
8583             if (appData.debugMode)
8584               fprintf(debugFP, "Restoring %s for game %d\n",
8585                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8586     
8587             thinkOutput[0] = NULLCHAR;
8588             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8589             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8590             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8591             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8592             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8593             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8594             MakeMove(fromX, fromY, toX, toY, promoChar);
8595             ShowMove(fromX, fromY, toX, toY);
8596               
8597             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8598                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8599               case MT_NONE:
8600               case MT_CHECK:
8601                 break;
8602                 
8603               case MT_CHECKMATE:
8604               case MT_STAINMATE:
8605                 if (WhiteOnMove(currentMove)) {
8606                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8607                 } else {
8608                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8609                 }
8610                 break;
8611                 
8612               case MT_STALEMATE:
8613                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8614                 break;
8615             }
8616
8617             break;
8618             
8619           case CMAIL_RESIGN:
8620             if (WhiteOnMove(currentMove)) {
8621                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8622             } else {
8623                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8624             }
8625             break;
8626             
8627           case CMAIL_ACCEPT:
8628             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8629             break;
8630               
8631           default:
8632             break;
8633         }
8634     }
8635
8636     return;
8637 }
8638
8639 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8640 int
8641 CmailLoadGame(f, gameNumber, title, useList)
8642      FILE *f;
8643      int gameNumber;
8644      char *title;
8645      int useList;
8646 {
8647     int retVal;
8648
8649     if (gameNumber > nCmailGames) {
8650         DisplayError(_("No more games in this message"), 0);
8651         return FALSE;
8652     }
8653     if (f == lastLoadGameFP) {
8654         int offset = gameNumber - lastLoadGameNumber;
8655         if (offset == 0) {
8656             cmailMsg[0] = NULLCHAR;
8657             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8658                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8659                 nCmailMovesRegistered--;
8660             }
8661             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8662             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8663                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8664             }
8665         } else {
8666             if (! RegisterMove()) return FALSE;
8667         }
8668     }
8669
8670     retVal = LoadGame(f, gameNumber, title, useList);
8671
8672     /* Make move registered during previous look at this game, if any */
8673     MakeRegisteredMove();
8674
8675     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8676         commentList[currentMove]
8677           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8678         DisplayComment(currentMove - 1, commentList[currentMove]);
8679     }
8680
8681     return retVal;
8682 }
8683
8684 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8685 int
8686 ReloadGame(offset)
8687      int offset;
8688 {
8689     int gameNumber = lastLoadGameNumber + offset;
8690     if (lastLoadGameFP == NULL) {
8691         DisplayError(_("No game has been loaded yet"), 0);
8692         return FALSE;
8693     }
8694     if (gameNumber <= 0) {
8695         DisplayError(_("Can't back up any further"), 0);
8696         return FALSE;
8697     }
8698     if (cmailMsgLoaded) {
8699         return CmailLoadGame(lastLoadGameFP, gameNumber,
8700                              lastLoadGameTitle, lastLoadGameUseList);
8701     } else {
8702         return LoadGame(lastLoadGameFP, gameNumber,
8703                         lastLoadGameTitle, lastLoadGameUseList);
8704     }
8705 }
8706
8707
8708
8709 /* Load the nth game from open file f */
8710 int
8711 LoadGame(f, gameNumber, title, useList)
8712      FILE *f;
8713      int gameNumber;
8714      char *title;
8715      int useList;
8716 {
8717     ChessMove cm;
8718     char buf[MSG_SIZ];
8719     int gn = gameNumber;
8720     ListGame *lg = NULL;
8721     int numPGNTags = 0;
8722     int err;
8723     GameMode oldGameMode;
8724     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8725
8726     if (appData.debugMode) 
8727         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8728
8729     if (gameMode == Training )
8730         SetTrainingModeOff();
8731
8732     oldGameMode = gameMode;
8733     if (gameMode != BeginningOfGame) {
8734       Reset(FALSE, TRUE);
8735     }
8736
8737     gameFileFP = f;
8738     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8739         fclose(lastLoadGameFP);
8740     }
8741
8742     if (useList) {
8743         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8744         
8745         if (lg) {
8746             fseek(f, lg->offset, 0);
8747             GameListHighlight(gameNumber);
8748             gn = 1;
8749         }
8750         else {
8751             DisplayError(_("Game number out of range"), 0);
8752             return FALSE;
8753         }
8754     } else {
8755         GameListDestroy();
8756         if (fseek(f, 0, 0) == -1) {
8757             if (f == lastLoadGameFP ?
8758                 gameNumber == lastLoadGameNumber + 1 :
8759                 gameNumber == 1) {
8760                 gn = 1;
8761             } else {
8762                 DisplayError(_("Can't seek on game file"), 0);
8763                 return FALSE;
8764             }
8765         }
8766     }
8767     lastLoadGameFP = f;
8768     lastLoadGameNumber = gameNumber;
8769     strcpy(lastLoadGameTitle, title);
8770     lastLoadGameUseList = useList;
8771
8772     yynewfile(f);
8773
8774     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8775       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8776                 lg->gameInfo.black);
8777             DisplayTitle(buf);
8778     } else if (*title != NULLCHAR) {
8779         if (gameNumber > 1) {
8780             sprintf(buf, "%s %d", title, gameNumber);
8781             DisplayTitle(buf);
8782         } else {
8783             DisplayTitle(title);
8784         }
8785     }
8786
8787     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8788         gameMode = PlayFromGameFile;
8789         ModeHighlight();
8790     }
8791
8792     currentMove = forwardMostMove = backwardMostMove = 0;
8793     CopyBoard(boards[0], initialPosition);
8794     StopClocks();
8795
8796     /*
8797      * Skip the first gn-1 games in the file.
8798      * Also skip over anything that precedes an identifiable 
8799      * start of game marker, to avoid being confused by 
8800      * garbage at the start of the file.  Currently 
8801      * recognized start of game markers are the move number "1",
8802      * the pattern "gnuchess .* game", the pattern
8803      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8804      * A game that starts with one of the latter two patterns
8805      * will also have a move number 1, possibly
8806      * following a position diagram.
8807      * 5-4-02: Let's try being more lenient and allowing a game to
8808      * start with an unnumbered move.  Does that break anything?
8809      */
8810     cm = lastLoadGameStart = (ChessMove) 0;
8811     while (gn > 0) {
8812         yyboardindex = forwardMostMove;
8813         cm = (ChessMove) yylex();
8814         switch (cm) {
8815           case (ChessMove) 0:
8816             if (cmailMsgLoaded) {
8817                 nCmailGames = CMAIL_MAX_GAMES - gn;
8818             } else {
8819                 Reset(TRUE, TRUE);
8820                 DisplayError(_("Game not found in file"), 0);
8821             }
8822             return FALSE;
8823
8824           case GNUChessGame:
8825           case XBoardGame:
8826             gn--;
8827             lastLoadGameStart = cm;
8828             break;
8829             
8830           case MoveNumberOne:
8831             switch (lastLoadGameStart) {
8832               case GNUChessGame:
8833               case XBoardGame:
8834               case PGNTag:
8835                 break;
8836               case MoveNumberOne:
8837               case (ChessMove) 0:
8838                 gn--;           /* count this game */
8839                 lastLoadGameStart = cm;
8840                 break;
8841               default:
8842                 /* impossible */
8843                 break;
8844             }
8845             break;
8846
8847           case PGNTag:
8848             switch (lastLoadGameStart) {
8849               case GNUChessGame:
8850               case PGNTag:
8851               case MoveNumberOne:
8852               case (ChessMove) 0:
8853                 gn--;           /* count this game */
8854                 lastLoadGameStart = cm;
8855                 break;
8856               case XBoardGame:
8857                 lastLoadGameStart = cm; /* game counted already */
8858                 break;
8859               default:
8860                 /* impossible */
8861                 break;
8862             }
8863             if (gn > 0) {
8864                 do {
8865                     yyboardindex = forwardMostMove;
8866                     cm = (ChessMove) yylex();
8867                 } while (cm == PGNTag || cm == Comment);
8868             }
8869             break;
8870
8871           case WhiteWins:
8872           case BlackWins:
8873           case GameIsDrawn:
8874             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8875                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8876                     != CMAIL_OLD_RESULT) {
8877                     nCmailResults ++ ;
8878                     cmailResult[  CMAIL_MAX_GAMES
8879                                 - gn - 1] = CMAIL_OLD_RESULT;
8880                 }
8881             }
8882             break;
8883
8884           case NormalMove:
8885             /* Only a NormalMove can be at the start of a game
8886              * without a position diagram. */
8887             if (lastLoadGameStart == (ChessMove) 0) {
8888               gn--;
8889               lastLoadGameStart = MoveNumberOne;
8890             }
8891             break;
8892
8893           default:
8894             break;
8895         }
8896     }
8897     
8898     if (appData.debugMode)
8899       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8900
8901     if (cm == XBoardGame) {
8902         /* Skip any header junk before position diagram and/or move 1 */
8903         for (;;) {
8904             yyboardindex = forwardMostMove;
8905             cm = (ChessMove) yylex();
8906
8907             if (cm == (ChessMove) 0 ||
8908                 cm == GNUChessGame || cm == XBoardGame) {
8909                 /* Empty game; pretend end-of-file and handle later */
8910                 cm = (ChessMove) 0;
8911                 break;
8912             }
8913
8914             if (cm == MoveNumberOne || cm == PositionDiagram ||
8915                 cm == PGNTag || cm == Comment)
8916               break;
8917         }
8918     } else if (cm == GNUChessGame) {
8919         if (gameInfo.event != NULL) {
8920             free(gameInfo.event);
8921         }
8922         gameInfo.event = StrSave(yy_text);
8923     }   
8924
8925     startedFromSetupPosition = FALSE;
8926     while (cm == PGNTag) {
8927         if (appData.debugMode) 
8928           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8929         err = ParsePGNTag(yy_text, &gameInfo);
8930         if (!err) numPGNTags++;
8931
8932         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8933         if(gameInfo.variant != oldVariant) {
8934             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8935             InitPosition(TRUE);
8936             oldVariant = gameInfo.variant;
8937             if (appData.debugMode) 
8938               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8939         }
8940
8941
8942         if (gameInfo.fen != NULL) {
8943           Board initial_position;
8944           startedFromSetupPosition = TRUE;
8945           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8946             Reset(TRUE, TRUE);
8947             DisplayError(_("Bad FEN position in file"), 0);
8948             return FALSE;
8949           }
8950           CopyBoard(boards[0], initial_position);
8951           if (blackPlaysFirst) {
8952             currentMove = forwardMostMove = backwardMostMove = 1;
8953             CopyBoard(boards[1], initial_position);
8954             strcpy(moveList[0], "");
8955             strcpy(parseList[0], "");
8956             timeRemaining[0][1] = whiteTimeRemaining;
8957             timeRemaining[1][1] = blackTimeRemaining;
8958             if (commentList[0] != NULL) {
8959               commentList[1] = commentList[0];
8960               commentList[0] = NULL;
8961             }
8962           } else {
8963             currentMove = forwardMostMove = backwardMostMove = 0;
8964           }
8965           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8966           {   int i;
8967               initialRulePlies = FENrulePlies;
8968               epStatus[forwardMostMove] = FENepStatus;
8969               for( i=0; i< nrCastlingRights; i++ )
8970                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8971           }
8972           yyboardindex = forwardMostMove;
8973           free(gameInfo.fen);
8974           gameInfo.fen = NULL;
8975         }
8976
8977         yyboardindex = forwardMostMove;
8978         cm = (ChessMove) yylex();
8979
8980         /* Handle comments interspersed among the tags */
8981         while (cm == Comment) {
8982             char *p;
8983             if (appData.debugMode) 
8984               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8985             p = yy_text;
8986             if (*p == '{' || *p == '[' || *p == '(') {
8987                 p[strlen(p) - 1] = NULLCHAR;
8988                 p++;
8989             }
8990             while (*p == '\n') p++;
8991             AppendComment(currentMove, p);
8992             yyboardindex = forwardMostMove;
8993             cm = (ChessMove) yylex();
8994         }
8995     }
8996
8997     /* don't rely on existence of Event tag since if game was
8998      * pasted from clipboard the Event tag may not exist
8999      */
9000     if (numPGNTags > 0){
9001         char *tags;
9002         if (gameInfo.variant == VariantNormal) {
9003           gameInfo.variant = StringToVariant(gameInfo.event);
9004         }
9005         if (!matchMode) {
9006           if( appData.autoDisplayTags ) {
9007             tags = PGNTags(&gameInfo);
9008             TagsPopUp(tags, CmailMsg());
9009             free(tags);
9010           }
9011         }
9012     } else {
9013         /* Make something up, but don't display it now */
9014         SetGameInfo();
9015         TagsPopDown();
9016     }
9017
9018     if (cm == PositionDiagram) {
9019         int i, j;
9020         char *p;
9021         Board initial_position;
9022
9023         if (appData.debugMode)
9024           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9025
9026         if (!startedFromSetupPosition) {
9027             p = yy_text;
9028             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9029               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9030                 switch (*p) {
9031                   case '[':
9032                   case '-':
9033                   case ' ':
9034                   case '\t':
9035                   case '\n':
9036                   case '\r':
9037                     break;
9038                   default:
9039                     initial_position[i][j++] = CharToPiece(*p);
9040                     break;
9041                 }
9042             while (*p == ' ' || *p == '\t' ||
9043                    *p == '\n' || *p == '\r') p++;
9044         
9045             if (strncmp(p, "black", strlen("black"))==0)
9046               blackPlaysFirst = TRUE;
9047             else
9048               blackPlaysFirst = FALSE;
9049             startedFromSetupPosition = TRUE;
9050         
9051             CopyBoard(boards[0], initial_position);
9052             if (blackPlaysFirst) {
9053                 currentMove = forwardMostMove = backwardMostMove = 1;
9054                 CopyBoard(boards[1], initial_position);
9055                 strcpy(moveList[0], "");
9056                 strcpy(parseList[0], "");
9057                 timeRemaining[0][1] = whiteTimeRemaining;
9058                 timeRemaining[1][1] = blackTimeRemaining;
9059                 if (commentList[0] != NULL) {
9060                     commentList[1] = commentList[0];
9061                     commentList[0] = NULL;
9062                 }
9063             } else {
9064                 currentMove = forwardMostMove = backwardMostMove = 0;
9065             }
9066         }
9067         yyboardindex = forwardMostMove;
9068         cm = (ChessMove) yylex();
9069     }
9070
9071     if (first.pr == NoProc) {
9072         StartChessProgram(&first);
9073     }
9074     InitChessProgram(&first, FALSE);
9075     SendToProgram("force\n", &first);
9076     if (startedFromSetupPosition) {
9077         SendBoard(&first, forwardMostMove);
9078     if (appData.debugMode) {
9079         fprintf(debugFP, "Load Game\n");
9080     }
9081         DisplayBothClocks();
9082     }      
9083
9084     /* [HGM] server: flag to write setup moves in broadcast file as one */
9085     loadFlag = appData.suppressLoadMoves;
9086
9087     while (cm == Comment) {
9088         char *p;
9089         if (appData.debugMode) 
9090           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9091         p = yy_text;
9092         if (*p == '{' || *p == '[' || *p == '(') {
9093             p[strlen(p) - 1] = NULLCHAR;
9094             p++;
9095         }
9096         while (*p == '\n') p++;
9097         AppendComment(currentMove, p);
9098         yyboardindex = forwardMostMove;
9099         cm = (ChessMove) yylex();
9100     }
9101
9102     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9103         cm == WhiteWins || cm == BlackWins ||
9104         cm == GameIsDrawn || cm == GameUnfinished) {
9105         DisplayMessage("", _("No moves in game"));
9106         if (cmailMsgLoaded) {
9107             if (appData.debugMode)
9108               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9109             ClearHighlights();
9110             flipView = FALSE;
9111         }
9112         DrawPosition(FALSE, boards[currentMove]);
9113         DisplayBothClocks();
9114         gameMode = EditGame;
9115         ModeHighlight();
9116         gameFileFP = NULL;
9117         cmailOldMove = 0;
9118         return TRUE;
9119     }
9120
9121     // [HGM] PV info: routine tests if comment empty
9122     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9123         DisplayComment(currentMove - 1, commentList[currentMove]);
9124     }
9125     if (!matchMode && appData.timeDelay != 0) 
9126       DrawPosition(FALSE, boards[currentMove]);
9127
9128     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9129       programStats.ok_to_send = 1;
9130     }
9131
9132     /* if the first token after the PGN tags is a move
9133      * and not move number 1, retrieve it from the parser 
9134      */
9135     if (cm != MoveNumberOne)
9136         LoadGameOneMove(cm);
9137
9138     /* load the remaining moves from the file */
9139     while (LoadGameOneMove((ChessMove)0)) {
9140       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9141       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9142     }
9143
9144     /* rewind to the start of the game */
9145     currentMove = backwardMostMove;
9146
9147     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9148
9149     if (oldGameMode == AnalyzeFile ||
9150         oldGameMode == AnalyzeMode) {
9151       AnalyzeFileEvent();
9152     }
9153
9154     if (matchMode || appData.timeDelay == 0) {
9155       ToEndEvent();
9156       gameMode = EditGame;
9157       ModeHighlight();
9158     } else if (appData.timeDelay > 0) {
9159       AutoPlayGameLoop();
9160     }
9161
9162     if (appData.debugMode) 
9163         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9164
9165     loadFlag = 0; /* [HGM] true game starts */
9166     return TRUE;
9167 }
9168
9169 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9170 int
9171 ReloadPosition(offset)
9172      int offset;
9173 {
9174     int positionNumber = lastLoadPositionNumber + offset;
9175     if (lastLoadPositionFP == NULL) {
9176         DisplayError(_("No position has been loaded yet"), 0);
9177         return FALSE;
9178     }
9179     if (positionNumber <= 0) {
9180         DisplayError(_("Can't back up any further"), 0);
9181         return FALSE;
9182     }
9183     return LoadPosition(lastLoadPositionFP, positionNumber,
9184                         lastLoadPositionTitle);
9185 }
9186
9187 /* Load the nth position from the given file */
9188 int
9189 LoadPositionFromFile(filename, n, title)
9190      char *filename;
9191      int n;
9192      char *title;
9193 {
9194     FILE *f;
9195     char buf[MSG_SIZ];
9196
9197     if (strcmp(filename, "-") == 0) {
9198         return LoadPosition(stdin, n, "stdin");
9199     } else {
9200         f = fopen(filename, "rb");
9201         if (f == NULL) {
9202             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9203             DisplayError(buf, errno);
9204             return FALSE;
9205         } else {
9206             return LoadPosition(f, n, title);
9207         }
9208     }
9209 }
9210
9211 /* Load the nth position from the given open file, and close it */
9212 int
9213 LoadPosition(f, positionNumber, title)
9214      FILE *f;
9215      int positionNumber;
9216      char *title;
9217 {
9218     char *p, line[MSG_SIZ];
9219     Board initial_position;
9220     int i, j, fenMode, pn;
9221     
9222     if (gameMode == Training )
9223         SetTrainingModeOff();
9224
9225     if (gameMode != BeginningOfGame) {
9226         Reset(FALSE, TRUE);
9227     }
9228     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9229         fclose(lastLoadPositionFP);
9230     }
9231     if (positionNumber == 0) positionNumber = 1;
9232     lastLoadPositionFP = f;
9233     lastLoadPositionNumber = positionNumber;
9234     strcpy(lastLoadPositionTitle, title);
9235     if (first.pr == NoProc) {
9236       StartChessProgram(&first);
9237       InitChessProgram(&first, FALSE);
9238     }    
9239     pn = positionNumber;
9240     if (positionNumber < 0) {
9241         /* Negative position number means to seek to that byte offset */
9242         if (fseek(f, -positionNumber, 0) == -1) {
9243             DisplayError(_("Can't seek on position file"), 0);
9244             return FALSE;
9245         };
9246         pn = 1;
9247     } else {
9248         if (fseek(f, 0, 0) == -1) {
9249             if (f == lastLoadPositionFP ?
9250                 positionNumber == lastLoadPositionNumber + 1 :
9251                 positionNumber == 1) {
9252                 pn = 1;
9253             } else {
9254                 DisplayError(_("Can't seek on position file"), 0);
9255                 return FALSE;
9256             }
9257         }
9258     }
9259     /* See if this file is FEN or old-style xboard */
9260     if (fgets(line, MSG_SIZ, f) == NULL) {
9261         DisplayError(_("Position not found in file"), 0);
9262         return FALSE;
9263     }
9264 #if 0
9265     switch (line[0]) {
9266       case '#':  case 'x':
9267       default:
9268         fenMode = FALSE;
9269         break;
9270       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9271       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9272       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9273       case '7':  case '8':  case '9':
9274       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9275       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9276       case 'C':  case 'W':             case 'c':  case 'w': 
9277         fenMode = TRUE;
9278         break;
9279     }
9280 #else
9281     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9282     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9283 #endif
9284
9285     if (pn >= 2) {
9286         if (fenMode || line[0] == '#') pn--;
9287         while (pn > 0) {
9288             /* skip positions before number pn */
9289             if (fgets(line, MSG_SIZ, f) == NULL) {
9290                 Reset(TRUE, TRUE);
9291                 DisplayError(_("Position not found in file"), 0);
9292                 return FALSE;
9293             }
9294             if (fenMode || line[0] == '#') pn--;
9295         }
9296     }
9297
9298     if (fenMode) {
9299         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9300             DisplayError(_("Bad FEN position in file"), 0);
9301             return FALSE;
9302         }
9303     } else {
9304         (void) fgets(line, MSG_SIZ, f);
9305         (void) fgets(line, MSG_SIZ, f);
9306     
9307         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9308             (void) fgets(line, MSG_SIZ, f);
9309             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9310                 if (*p == ' ')
9311                   continue;
9312                 initial_position[i][j++] = CharToPiece(*p);
9313             }
9314         }
9315     
9316         blackPlaysFirst = FALSE;
9317         if (!feof(f)) {
9318             (void) fgets(line, MSG_SIZ, f);
9319             if (strncmp(line, "black", strlen("black"))==0)
9320               blackPlaysFirst = TRUE;
9321         }
9322     }
9323     startedFromSetupPosition = TRUE;
9324     
9325     SendToProgram("force\n", &first);
9326     CopyBoard(boards[0], initial_position);
9327     if (blackPlaysFirst) {
9328         currentMove = forwardMostMove = backwardMostMove = 1;
9329         strcpy(moveList[0], "");
9330         strcpy(parseList[0], "");
9331         CopyBoard(boards[1], initial_position);
9332         DisplayMessage("", _("Black to play"));
9333     } else {
9334         currentMove = forwardMostMove = backwardMostMove = 0;
9335         DisplayMessage("", _("White to play"));
9336     }
9337           /* [HGM] copy FEN attributes as well */
9338           {   int i;
9339               initialRulePlies = FENrulePlies;
9340               epStatus[forwardMostMove] = FENepStatus;
9341               for( i=0; i< nrCastlingRights; i++ )
9342                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9343           }
9344     SendBoard(&first, forwardMostMove);
9345     if (appData.debugMode) {
9346 int i, j;
9347   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9348   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9349         fprintf(debugFP, "Load Position\n");
9350     }
9351
9352     if (positionNumber > 1) {
9353         sprintf(line, "%s %d", title, positionNumber);
9354         DisplayTitle(line);
9355     } else {
9356         DisplayTitle(title);
9357     }
9358     gameMode = EditGame;
9359     ModeHighlight();
9360     ResetClocks();
9361     timeRemaining[0][1] = whiteTimeRemaining;
9362     timeRemaining[1][1] = blackTimeRemaining;
9363     DrawPosition(FALSE, boards[currentMove]);
9364    
9365     return TRUE;
9366 }
9367
9368
9369 void
9370 CopyPlayerNameIntoFileName(dest, src)
9371      char **dest, *src;
9372 {
9373     while (*src != NULLCHAR && *src != ',') {
9374         if (*src == ' ') {
9375             *(*dest)++ = '_';
9376             src++;
9377         } else {
9378             *(*dest)++ = *src++;
9379         }
9380     }
9381 }
9382
9383 char *DefaultFileName(ext)
9384      char *ext;
9385 {
9386     static char def[MSG_SIZ];
9387     char *p;
9388
9389     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9390         p = def;
9391         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9392         *p++ = '-';
9393         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9394         *p++ = '.';
9395         strcpy(p, ext);
9396     } else {
9397         def[0] = NULLCHAR;
9398     }
9399     return def;
9400 }
9401
9402 /* Save the current game to the given file */
9403 int
9404 SaveGameToFile(filename, append)
9405      char *filename;
9406      int append;
9407 {
9408     FILE *f;
9409     char buf[MSG_SIZ];
9410
9411     if (strcmp(filename, "-") == 0) {
9412         return SaveGame(stdout, 0, NULL);
9413     } else {
9414         f = fopen(filename, append ? "a" : "w");
9415         if (f == NULL) {
9416             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9417             DisplayError(buf, errno);
9418             return FALSE;
9419         } else {
9420             return SaveGame(f, 0, NULL);
9421         }
9422     }
9423 }
9424
9425 char *
9426 SavePart(str)
9427      char *str;
9428 {
9429     static char buf[MSG_SIZ];
9430     char *p;
9431     
9432     p = strchr(str, ' ');
9433     if (p == NULL) return str;
9434     strncpy(buf, str, p - str);
9435     buf[p - str] = NULLCHAR;
9436     return buf;
9437 }
9438
9439 #define PGN_MAX_LINE 75
9440
9441 #define PGN_SIDE_WHITE  0
9442 #define PGN_SIDE_BLACK  1
9443
9444 /* [AS] */
9445 static int FindFirstMoveOutOfBook( int side )
9446 {
9447     int result = -1;
9448
9449     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9450         int index = backwardMostMove;
9451         int has_book_hit = 0;
9452
9453         if( (index % 2) != side ) {
9454             index++;
9455         }
9456
9457         while( index < forwardMostMove ) {
9458             /* Check to see if engine is in book */
9459             int depth = pvInfoList[index].depth;
9460             int score = pvInfoList[index].score;
9461             int in_book = 0;
9462
9463             if( depth <= 2 ) {
9464                 in_book = 1;
9465             }
9466             else if( score == 0 && depth == 63 ) {
9467                 in_book = 1; /* Zappa */
9468             }
9469             else if( score == 2 && depth == 99 ) {
9470                 in_book = 1; /* Abrok */
9471             }
9472
9473             has_book_hit += in_book;
9474
9475             if( ! in_book ) {
9476                 result = index;
9477
9478                 break;
9479             }
9480
9481             index += 2;
9482         }
9483     }
9484
9485     return result;
9486 }
9487
9488 /* [AS] */
9489 void GetOutOfBookInfo( char * buf )
9490 {
9491     int oob[2];
9492     int i;
9493     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9494
9495     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9496     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9497
9498     *buf = '\0';
9499
9500     if( oob[0] >= 0 || oob[1] >= 0 ) {
9501         for( i=0; i<2; i++ ) {
9502             int idx = oob[i];
9503
9504             if( idx >= 0 ) {
9505                 if( i > 0 && oob[0] >= 0 ) {
9506                     strcat( buf, "   " );
9507                 }
9508
9509                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9510                 sprintf( buf+strlen(buf), "%s%.2f", 
9511                     pvInfoList[idx].score >= 0 ? "+" : "",
9512                     pvInfoList[idx].score / 100.0 );
9513             }
9514         }
9515     }
9516 }
9517
9518 /* Save game in PGN style and close the file */
9519 int
9520 SaveGamePGN(f)
9521      FILE *f;
9522 {
9523     int i, offset, linelen, newblock;
9524     time_t tm;
9525 //    char *movetext;
9526     char numtext[32];
9527     int movelen, numlen, blank;
9528     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9529
9530     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9531     
9532     tm = time((time_t *) NULL);
9533     
9534     PrintPGNTags(f, &gameInfo);
9535     
9536     if (backwardMostMove > 0 || startedFromSetupPosition) {
9537         char *fen = PositionToFEN(backwardMostMove, NULL);
9538         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9539         fprintf(f, "\n{--------------\n");
9540         PrintPosition(f, backwardMostMove);
9541         fprintf(f, "--------------}\n");
9542         free(fen);
9543     }
9544     else {
9545         /* [AS] Out of book annotation */
9546         if( appData.saveOutOfBookInfo ) {
9547             char buf[64];
9548
9549             GetOutOfBookInfo( buf );
9550
9551             if( buf[0] != '\0' ) {
9552                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9553             }
9554         }
9555
9556         fprintf(f, "\n");
9557     }
9558
9559     i = backwardMostMove;
9560     linelen = 0;
9561     newblock = TRUE;
9562
9563     while (i < forwardMostMove) {
9564         /* Print comments preceding this move */
9565         if (commentList[i] != NULL) {
9566             if (linelen > 0) fprintf(f, "\n");
9567             fprintf(f, "{\n%s}\n", commentList[i]);
9568             linelen = 0;
9569             newblock = TRUE;
9570         }
9571
9572         /* Format move number */
9573         if ((i % 2) == 0) {
9574             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9575         } else {
9576             if (newblock) {
9577                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9578             } else {
9579                 numtext[0] = NULLCHAR;
9580             }
9581         }
9582         numlen = strlen(numtext);
9583         newblock = FALSE;
9584
9585         /* Print move number */
9586         blank = linelen > 0 && numlen > 0;
9587         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9588             fprintf(f, "\n");
9589             linelen = 0;
9590             blank = 0;
9591         }
9592         if (blank) {
9593             fprintf(f, " ");
9594             linelen++;
9595         }
9596         fprintf(f, numtext);
9597         linelen += numlen;
9598
9599         /* Get move */
9600         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9601         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9602         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9603                 int p = movelen - 1;
9604                 if(move_buffer[p] == ' ') p--;
9605                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9606                     while(p && move_buffer[--p] != '(');
9607                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9608                 }
9609         }
9610
9611         /* Print move */
9612         blank = linelen > 0 && movelen > 0;
9613         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9614             fprintf(f, "\n");
9615             linelen = 0;
9616             blank = 0;
9617         }
9618         if (blank) {
9619             fprintf(f, " ");
9620             linelen++;
9621         }
9622         fprintf(f, move_buffer);
9623         linelen += movelen;
9624
9625         /* [AS] Add PV info if present */
9626         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9627             /* [HGM] add time */
9628             char buf[MSG_SIZ]; int seconds = 0;
9629
9630 #if 1
9631             if(i >= backwardMostMove) {
9632                 if(WhiteOnMove(i))
9633                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9634                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9635                 else
9636                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9637                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9638             }
9639             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9640 #else
9641             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9642 #endif
9643
9644             if( seconds <= 0) buf[0] = 0; else
9645             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9646                 seconds = (seconds + 4)/10; // round to full seconds
9647                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9648                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9649             }
9650
9651             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9652                 pvInfoList[i].score >= 0 ? "+" : "",
9653                 pvInfoList[i].score / 100.0,
9654                 pvInfoList[i].depth,
9655                 buf );
9656
9657             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9658
9659             /* Print score/depth */
9660             blank = linelen > 0 && movelen > 0;
9661             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9662                 fprintf(f, "\n");
9663                 linelen = 0;
9664                 blank = 0;
9665             }
9666             if (blank) {
9667                 fprintf(f, " ");
9668                 linelen++;
9669             }
9670             fprintf(f, move_buffer);
9671             linelen += movelen;
9672         }
9673
9674         i++;
9675     }
9676     
9677     /* Start a new line */
9678     if (linelen > 0) fprintf(f, "\n");
9679
9680     /* Print comments after last move */
9681     if (commentList[i] != NULL) {
9682         fprintf(f, "{\n%s}\n", commentList[i]);
9683     }
9684
9685     /* Print result */
9686     if (gameInfo.resultDetails != NULL &&
9687         gameInfo.resultDetails[0] != NULLCHAR) {
9688         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9689                 PGNResult(gameInfo.result));
9690     } else {
9691         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9692     }
9693
9694     fclose(f);
9695     return TRUE;
9696 }
9697
9698 /* Save game in old style and close the file */
9699 int
9700 SaveGameOldStyle(f)
9701      FILE *f;
9702 {
9703     int i, offset;
9704     time_t tm;
9705     
9706     tm = time((time_t *) NULL);
9707     
9708     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9709     PrintOpponents(f);
9710     
9711     if (backwardMostMove > 0 || startedFromSetupPosition) {
9712         fprintf(f, "\n[--------------\n");
9713         PrintPosition(f, backwardMostMove);
9714         fprintf(f, "--------------]\n");
9715     } else {
9716         fprintf(f, "\n");
9717     }
9718
9719     i = backwardMostMove;
9720     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9721
9722     while (i < forwardMostMove) {
9723         if (commentList[i] != NULL) {
9724             fprintf(f, "[%s]\n", commentList[i]);
9725         }
9726
9727         if ((i % 2) == 1) {
9728             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9729             i++;
9730         } else {
9731             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9732             i++;
9733             if (commentList[i] != NULL) {
9734                 fprintf(f, "\n");
9735                 continue;
9736             }
9737             if (i >= forwardMostMove) {
9738                 fprintf(f, "\n");
9739                 break;
9740             }
9741             fprintf(f, "%s\n", parseList[i]);
9742             i++;
9743         }
9744     }
9745     
9746     if (commentList[i] != NULL) {
9747         fprintf(f, "[%s]\n", commentList[i]);
9748     }
9749
9750     /* This isn't really the old style, but it's close enough */
9751     if (gameInfo.resultDetails != NULL &&
9752         gameInfo.resultDetails[0] != NULLCHAR) {
9753         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9754                 gameInfo.resultDetails);
9755     } else {
9756         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9757     }
9758
9759     fclose(f);
9760     return TRUE;
9761 }
9762
9763 /* Save the current game to open file f and close the file */
9764 int
9765 SaveGame(f, dummy, dummy2)
9766      FILE *f;
9767      int dummy;
9768      char *dummy2;
9769 {
9770     if (gameMode == EditPosition) EditPositionDone();
9771     if (appData.oldSaveStyle)
9772       return SaveGameOldStyle(f);
9773     else
9774       return SaveGamePGN(f);
9775 }
9776
9777 /* Save the current position to the given file */
9778 int
9779 SavePositionToFile(filename)
9780      char *filename;
9781 {
9782     FILE *f;
9783     char buf[MSG_SIZ];
9784
9785     if (strcmp(filename, "-") == 0) {
9786         return SavePosition(stdout, 0, NULL);
9787     } else {
9788         f = fopen(filename, "a");
9789         if (f == NULL) {
9790             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9791             DisplayError(buf, errno);
9792             return FALSE;
9793         } else {
9794             SavePosition(f, 0, NULL);
9795             return TRUE;
9796         }
9797     }
9798 }
9799
9800 /* Save the current position to the given open file and close the file */
9801 int
9802 SavePosition(f, dummy, dummy2)
9803      FILE *f;
9804      int dummy;
9805      char *dummy2;
9806 {
9807     time_t tm;
9808     char *fen;
9809     
9810     if (appData.oldSaveStyle) {
9811         tm = time((time_t *) NULL);
9812     
9813         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9814         PrintOpponents(f);
9815         fprintf(f, "[--------------\n");
9816         PrintPosition(f, currentMove);
9817         fprintf(f, "--------------]\n");
9818     } else {
9819         fen = PositionToFEN(currentMove, NULL);
9820         fprintf(f, "%s\n", fen);
9821         free(fen);
9822     }
9823     fclose(f);
9824     return TRUE;
9825 }
9826
9827 void
9828 ReloadCmailMsgEvent(unregister)
9829      int unregister;
9830 {
9831 #if !WIN32
9832     static char *inFilename = NULL;
9833     static char *outFilename;
9834     int i;
9835     struct stat inbuf, outbuf;
9836     int status;
9837     
9838     /* Any registered moves are unregistered if unregister is set, */
9839     /* i.e. invoked by the signal handler */
9840     if (unregister) {
9841         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9842             cmailMoveRegistered[i] = FALSE;
9843             if (cmailCommentList[i] != NULL) {
9844                 free(cmailCommentList[i]);
9845                 cmailCommentList[i] = NULL;
9846             }
9847         }
9848         nCmailMovesRegistered = 0;
9849     }
9850
9851     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9852         cmailResult[i] = CMAIL_NOT_RESULT;
9853     }
9854     nCmailResults = 0;
9855
9856     if (inFilename == NULL) {
9857         /* Because the filenames are static they only get malloced once  */
9858         /* and they never get freed                                      */
9859         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9860         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9861
9862         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9863         sprintf(outFilename, "%s.out", appData.cmailGameName);
9864     }
9865     
9866     status = stat(outFilename, &outbuf);
9867     if (status < 0) {
9868         cmailMailedMove = FALSE;
9869     } else {
9870         status = stat(inFilename, &inbuf);
9871         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9872     }
9873     
9874     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9875        counts the games, notes how each one terminated, etc.
9876        
9877        It would be nice to remove this kludge and instead gather all
9878        the information while building the game list.  (And to keep it
9879        in the game list nodes instead of having a bunch of fixed-size
9880        parallel arrays.)  Note this will require getting each game's
9881        termination from the PGN tags, as the game list builder does
9882        not process the game moves.  --mann
9883        */
9884     cmailMsgLoaded = TRUE;
9885     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9886     
9887     /* Load first game in the file or popup game menu */
9888     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9889
9890 #endif /* !WIN32 */
9891     return;
9892 }
9893
9894 int
9895 RegisterMove()
9896 {
9897     FILE *f;
9898     char string[MSG_SIZ];
9899
9900     if (   cmailMailedMove
9901         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9902         return TRUE;            /* Allow free viewing  */
9903     }
9904
9905     /* Unregister move to ensure that we don't leave RegisterMove        */
9906     /* with the move registered when the conditions for registering no   */
9907     /* longer hold                                                       */
9908     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9909         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9910         nCmailMovesRegistered --;
9911
9912         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9913           {
9914               free(cmailCommentList[lastLoadGameNumber - 1]);
9915               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9916           }
9917     }
9918
9919     if (cmailOldMove == -1) {
9920         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9921         return FALSE;
9922     }
9923
9924     if (currentMove > cmailOldMove + 1) {
9925         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9926         return FALSE;
9927     }
9928
9929     if (currentMove < cmailOldMove) {
9930         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9931         return FALSE;
9932     }
9933
9934     if (forwardMostMove > currentMove) {
9935         /* Silently truncate extra moves */
9936         TruncateGame();
9937     }
9938
9939     if (   (currentMove == cmailOldMove + 1)
9940         || (   (currentMove == cmailOldMove)
9941             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9942                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9943         if (gameInfo.result != GameUnfinished) {
9944             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9945         }
9946
9947         if (commentList[currentMove] != NULL) {
9948             cmailCommentList[lastLoadGameNumber - 1]
9949               = StrSave(commentList[currentMove]);
9950         }
9951         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9952
9953         if (appData.debugMode)
9954           fprintf(debugFP, "Saving %s for game %d\n",
9955                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9956
9957         sprintf(string,
9958                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9959         
9960         f = fopen(string, "w");
9961         if (appData.oldSaveStyle) {
9962             SaveGameOldStyle(f); /* also closes the file */
9963             
9964             sprintf(string, "%s.pos.out", appData.cmailGameName);
9965             f = fopen(string, "w");
9966             SavePosition(f, 0, NULL); /* also closes the file */
9967         } else {
9968             fprintf(f, "{--------------\n");
9969             PrintPosition(f, currentMove);
9970             fprintf(f, "--------------}\n\n");
9971             
9972             SaveGame(f, 0, NULL); /* also closes the file*/
9973         }
9974         
9975         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9976         nCmailMovesRegistered ++;
9977     } else if (nCmailGames == 1) {
9978         DisplayError(_("You have not made a move yet"), 0);
9979         return FALSE;
9980     }
9981
9982     return TRUE;
9983 }
9984
9985 void
9986 MailMoveEvent()
9987 {
9988 #if !WIN32
9989     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9990     FILE *commandOutput;
9991     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9992     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9993     int nBuffers;
9994     int i;
9995     int archived;
9996     char *arcDir;
9997
9998     if (! cmailMsgLoaded) {
9999         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10000         return;
10001     }
10002
10003     if (nCmailGames == nCmailResults) {
10004         DisplayError(_("No unfinished games"), 0);
10005         return;
10006     }
10007
10008 #if CMAIL_PROHIBIT_REMAIL
10009     if (cmailMailedMove) {
10010         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);
10011         DisplayError(msg, 0);
10012         return;
10013     }
10014 #endif
10015
10016     if (! (cmailMailedMove || RegisterMove())) return;
10017     
10018     if (   cmailMailedMove
10019         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10020         sprintf(string, partCommandString,
10021                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10022         commandOutput = popen(string, "r");
10023
10024         if (commandOutput == NULL) {
10025             DisplayError(_("Failed to invoke cmail"), 0);
10026         } else {
10027             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10028                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10029             }
10030             if (nBuffers > 1) {
10031                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10032                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10033                 nBytes = MSG_SIZ - 1;
10034             } else {
10035                 (void) memcpy(msg, buffer, nBytes);
10036             }
10037             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10038
10039             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10040                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10041
10042                 archived = TRUE;
10043                 for (i = 0; i < nCmailGames; i ++) {
10044                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10045                         archived = FALSE;
10046                     }
10047                 }
10048                 if (   archived
10049                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10050                         != NULL)) {
10051                     sprintf(buffer, "%s/%s.%s.archive",
10052                             arcDir,
10053                             appData.cmailGameName,
10054                             gameInfo.date);
10055                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10056                     cmailMsgLoaded = FALSE;
10057                 }
10058             }
10059
10060             DisplayInformation(msg);
10061             pclose(commandOutput);
10062         }
10063     } else {
10064         if ((*cmailMsg) != '\0') {
10065             DisplayInformation(cmailMsg);
10066         }
10067     }
10068
10069     return;
10070 #endif /* !WIN32 */
10071 }
10072
10073 char *
10074 CmailMsg()
10075 {
10076 #if WIN32
10077     return NULL;
10078 #else
10079     int  prependComma = 0;
10080     char number[5];
10081     char string[MSG_SIZ];       /* Space for game-list */
10082     int  i;
10083     
10084     if (!cmailMsgLoaded) return "";
10085
10086     if (cmailMailedMove) {
10087         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10088     } else {
10089         /* Create a list of games left */
10090         sprintf(string, "[");
10091         for (i = 0; i < nCmailGames; i ++) {
10092             if (! (   cmailMoveRegistered[i]
10093                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10094                 if (prependComma) {
10095                     sprintf(number, ",%d", i + 1);
10096                 } else {
10097                     sprintf(number, "%d", i + 1);
10098                     prependComma = 1;
10099                 }
10100                 
10101                 strcat(string, number);
10102             }
10103         }
10104         strcat(string, "]");
10105
10106         if (nCmailMovesRegistered + nCmailResults == 0) {
10107             switch (nCmailGames) {
10108               case 1:
10109                 sprintf(cmailMsg,
10110                         _("Still need to make move for game\n"));
10111                 break;
10112                 
10113               case 2:
10114                 sprintf(cmailMsg,
10115                         _("Still need to make moves for both games\n"));
10116                 break;
10117                 
10118               default:
10119                 sprintf(cmailMsg,
10120                         _("Still need to make moves for all %d games\n"),
10121                         nCmailGames);
10122                 break;
10123             }
10124         } else {
10125             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10126               case 1:
10127                 sprintf(cmailMsg,
10128                         _("Still need to make a move for game %s\n"),
10129                         string);
10130                 break;
10131                 
10132               case 0:
10133                 if (nCmailResults == nCmailGames) {
10134                     sprintf(cmailMsg, _("No unfinished games\n"));
10135                 } else {
10136                     sprintf(cmailMsg, _("Ready to send mail\n"));
10137                 }
10138                 break;
10139                 
10140               default:
10141                 sprintf(cmailMsg,
10142                         _("Still need to make moves for games %s\n"),
10143                         string);
10144             }
10145         }
10146     }
10147     return cmailMsg;
10148 #endif /* WIN32 */
10149 }
10150
10151 void
10152 ResetGameEvent()
10153 {
10154     if (gameMode == Training)
10155       SetTrainingModeOff();
10156
10157     Reset(TRUE, TRUE);
10158     cmailMsgLoaded = FALSE;
10159     if (appData.icsActive) {
10160       SendToICS(ics_prefix);
10161       SendToICS("refresh\n");
10162     }
10163 }
10164
10165 void
10166 ExitEvent(status)
10167      int status;
10168 {
10169     exiting++;
10170     if (exiting > 2) {
10171       /* Give up on clean exit */
10172       exit(status);
10173     }
10174     if (exiting > 1) {
10175       /* Keep trying for clean exit */
10176       return;
10177     }
10178
10179     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10180
10181     if (telnetISR != NULL) {
10182       RemoveInputSource(telnetISR);
10183     }
10184     if (icsPR != NoProc) {
10185       DestroyChildProcess(icsPR, TRUE);
10186     }
10187 #if 0
10188     /* Save game if resource set and not already saved by GameEnds() */
10189     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10190                              && forwardMostMove > 0) {
10191       if (*appData.saveGameFile != NULLCHAR) {
10192         SaveGameToFile(appData.saveGameFile, TRUE);
10193       } else if (appData.autoSaveGames) {
10194         AutoSaveGame();
10195       }
10196       if (*appData.savePositionFile != NULLCHAR) {
10197         SavePositionToFile(appData.savePositionFile);
10198       }
10199     }
10200     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10201 #else
10202     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10203     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10204 #endif
10205     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10206     /* make sure this other one finishes before killing it!                  */
10207     if(endingGame) { int count = 0;
10208         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10209         while(endingGame && count++ < 10) DoSleep(1);
10210         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10211     }
10212
10213     /* Kill off chess programs */
10214     if (first.pr != NoProc) {
10215         ExitAnalyzeMode();
10216         
10217         DoSleep( appData.delayBeforeQuit );
10218         SendToProgram("quit\n", &first);
10219         DoSleep( appData.delayAfterQuit );
10220         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10221     }
10222     if (second.pr != NoProc) {
10223         DoSleep( appData.delayBeforeQuit );
10224         SendToProgram("quit\n", &second);
10225         DoSleep( appData.delayAfterQuit );
10226         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10227     }
10228     if (first.isr != NULL) {
10229         RemoveInputSource(first.isr);
10230     }
10231     if (second.isr != NULL) {
10232         RemoveInputSource(second.isr);
10233     }
10234
10235     ShutDownFrontEnd();
10236     exit(status);
10237 }
10238
10239 void
10240 PauseEvent()
10241 {
10242     if (appData.debugMode)
10243         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10244     if (pausing) {
10245         pausing = FALSE;
10246         ModeHighlight();
10247         if (gameMode == MachinePlaysWhite ||
10248             gameMode == MachinePlaysBlack) {
10249             StartClocks();
10250         } else {
10251             DisplayBothClocks();
10252         }
10253         if (gameMode == PlayFromGameFile) {
10254             if (appData.timeDelay >= 0) 
10255                 AutoPlayGameLoop();
10256         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10257             Reset(FALSE, TRUE);
10258             SendToICS(ics_prefix);
10259             SendToICS("refresh\n");
10260         } else if (currentMove < forwardMostMove) {
10261             ForwardInner(forwardMostMove);
10262         }
10263         pauseExamInvalid = FALSE;
10264     } else {
10265         switch (gameMode) {
10266           default:
10267             return;
10268           case IcsExamining:
10269             pauseExamForwardMostMove = forwardMostMove;
10270             pauseExamInvalid = FALSE;
10271             /* fall through */
10272           case IcsObserving:
10273           case IcsPlayingWhite:
10274           case IcsPlayingBlack:
10275             pausing = TRUE;
10276             ModeHighlight();
10277             return;
10278           case PlayFromGameFile:
10279             (void) StopLoadGameTimer();
10280             pausing = TRUE;
10281             ModeHighlight();
10282             break;
10283           case BeginningOfGame:
10284             if (appData.icsActive) return;
10285             /* else fall through */
10286           case MachinePlaysWhite:
10287           case MachinePlaysBlack:
10288           case TwoMachinesPlay:
10289             if (forwardMostMove == 0)
10290               return;           /* don't pause if no one has moved */
10291             if ((gameMode == MachinePlaysWhite &&
10292                  !WhiteOnMove(forwardMostMove)) ||
10293                 (gameMode == MachinePlaysBlack &&
10294                  WhiteOnMove(forwardMostMove))) {
10295                 StopClocks();
10296             }
10297             pausing = TRUE;
10298             ModeHighlight();
10299             break;
10300         }
10301     }
10302 }
10303
10304 void
10305 EditCommentEvent()
10306 {
10307     char title[MSG_SIZ];
10308
10309     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10310         strcpy(title, _("Edit comment"));
10311     } else {
10312         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10313                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10314                 parseList[currentMove - 1]);
10315     }
10316
10317     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10318 }
10319
10320
10321 void
10322 EditTagsEvent()
10323 {
10324     char *tags = PGNTags(&gameInfo);
10325     EditTagsPopUp(tags);
10326     free(tags);
10327 }
10328
10329 void
10330 AnalyzeModeEvent()
10331 {
10332     if (appData.noChessProgram || gameMode == AnalyzeMode)
10333       return;
10334
10335     if (gameMode != AnalyzeFile) {
10336         if (!appData.icsEngineAnalyze) {
10337                EditGameEvent();
10338                if (gameMode != EditGame) return;
10339         }
10340         ResurrectChessProgram();
10341         SendToProgram("analyze\n", &first);
10342         first.analyzing = TRUE;
10343         /*first.maybeThinking = TRUE;*/
10344         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10345         AnalysisPopUp(_("Analysis"),
10346                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10347     }
10348     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10349     pausing = FALSE;
10350     ModeHighlight();
10351     SetGameInfo();
10352
10353     StartAnalysisClock();
10354     GetTimeMark(&lastNodeCountTime);
10355     lastNodeCount = 0;
10356 }
10357
10358 void
10359 AnalyzeFileEvent()
10360 {
10361     if (appData.noChessProgram || gameMode == AnalyzeFile)
10362       return;
10363
10364     if (gameMode != AnalyzeMode) {
10365         EditGameEvent();
10366         if (gameMode != EditGame) return;
10367         ResurrectChessProgram();
10368         SendToProgram("analyze\n", &first);
10369         first.analyzing = TRUE;
10370         /*first.maybeThinking = TRUE;*/
10371         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10372         AnalysisPopUp(_("Analysis"),
10373                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10374     }
10375     gameMode = AnalyzeFile;
10376     pausing = FALSE;
10377     ModeHighlight();
10378     SetGameInfo();
10379
10380     StartAnalysisClock();
10381     GetTimeMark(&lastNodeCountTime);
10382     lastNodeCount = 0;
10383 }
10384
10385 void
10386 MachineWhiteEvent()
10387 {
10388     char buf[MSG_SIZ];
10389     char *bookHit = NULL;
10390
10391     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10392       return;
10393
10394
10395     if (gameMode == PlayFromGameFile || 
10396         gameMode == TwoMachinesPlay  || 
10397         gameMode == Training         || 
10398         gameMode == AnalyzeMode      || 
10399         gameMode == EndOfGame)
10400         EditGameEvent();
10401
10402     if (gameMode == EditPosition) 
10403         EditPositionDone();
10404
10405     if (!WhiteOnMove(currentMove)) {
10406         DisplayError(_("It is not White's turn"), 0);
10407         return;
10408     }
10409   
10410     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10411       ExitAnalyzeMode();
10412
10413     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10414         gameMode == AnalyzeFile)
10415         TruncateGame();
10416
10417     ResurrectChessProgram();    /* in case it isn't running */
10418     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10419         gameMode = MachinePlaysWhite;
10420         ResetClocks();
10421     } else
10422     gameMode = MachinePlaysWhite;
10423     pausing = FALSE;
10424     ModeHighlight();
10425     SetGameInfo();
10426     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10427     DisplayTitle(buf);
10428     if (first.sendName) {
10429       sprintf(buf, "name %s\n", gameInfo.black);
10430       SendToProgram(buf, &first);
10431     }
10432     if (first.sendTime) {
10433       if (first.useColors) {
10434         SendToProgram("black\n", &first); /*gnu kludge*/
10435       }
10436       SendTimeRemaining(&first, TRUE);
10437     }
10438     if (first.useColors) {
10439       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10440     }
10441     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10442     SetMachineThinkingEnables();
10443     first.maybeThinking = TRUE;
10444     StartClocks();
10445
10446     if (appData.autoFlipView && !flipView) {
10447       flipView = !flipView;
10448       DrawPosition(FALSE, NULL);
10449       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10450     }
10451
10452     if(bookHit) { // [HGM] book: simulate book reply
10453         static char bookMove[MSG_SIZ]; // a bit generous?
10454
10455         programStats.nodes = programStats.depth = programStats.time = 
10456         programStats.score = programStats.got_only_move = 0;
10457         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10458
10459         strcpy(bookMove, "move ");
10460         strcat(bookMove, bookHit);
10461         HandleMachineMove(bookMove, &first);
10462     }
10463 }
10464
10465 void
10466 MachineBlackEvent()
10467 {
10468     char buf[MSG_SIZ];
10469    char *bookHit = NULL;
10470
10471     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10472         return;
10473
10474
10475     if (gameMode == PlayFromGameFile || 
10476         gameMode == TwoMachinesPlay  || 
10477         gameMode == Training         || 
10478         gameMode == AnalyzeMode      || 
10479         gameMode == EndOfGame)
10480         EditGameEvent();
10481
10482     if (gameMode == EditPosition) 
10483         EditPositionDone();
10484
10485     if (WhiteOnMove(currentMove)) {
10486         DisplayError(_("It is not Black's turn"), 0);
10487         return;
10488     }
10489     
10490     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10491       ExitAnalyzeMode();
10492
10493     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10494         gameMode == AnalyzeFile)
10495         TruncateGame();
10496
10497     ResurrectChessProgram();    /* in case it isn't running */
10498     gameMode = MachinePlaysBlack;
10499     pausing = FALSE;
10500     ModeHighlight();
10501     SetGameInfo();
10502     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10503     DisplayTitle(buf);
10504     if (first.sendName) {
10505       sprintf(buf, "name %s\n", gameInfo.white);
10506       SendToProgram(buf, &first);
10507     }
10508     if (first.sendTime) {
10509       if (first.useColors) {
10510         SendToProgram("white\n", &first); /*gnu kludge*/
10511       }
10512       SendTimeRemaining(&first, FALSE);
10513     }
10514     if (first.useColors) {
10515       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10516     }
10517     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10518     SetMachineThinkingEnables();
10519     first.maybeThinking = TRUE;
10520     StartClocks();
10521
10522     if (appData.autoFlipView && flipView) {
10523       flipView = !flipView;
10524       DrawPosition(FALSE, NULL);
10525       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10526     }
10527     if(bookHit) { // [HGM] book: simulate book reply
10528         static char bookMove[MSG_SIZ]; // a bit generous?
10529
10530         programStats.nodes = programStats.depth = programStats.time = 
10531         programStats.score = programStats.got_only_move = 0;
10532         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10533
10534         strcpy(bookMove, "move ");
10535         strcat(bookMove, bookHit);
10536         HandleMachineMove(bookMove, &first);
10537     }
10538 }
10539
10540
10541 void
10542 DisplayTwoMachinesTitle()
10543 {
10544     char buf[MSG_SIZ];
10545     if (appData.matchGames > 0) {
10546         if (first.twoMachinesColor[0] == 'w') {
10547             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10548                     gameInfo.white, gameInfo.black,
10549                     first.matchWins, second.matchWins,
10550                     matchGame - 1 - (first.matchWins + second.matchWins));
10551         } else {
10552             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10553                     gameInfo.white, gameInfo.black,
10554                     second.matchWins, first.matchWins,
10555                     matchGame - 1 - (first.matchWins + second.matchWins));
10556         }
10557     } else {
10558         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10559     }
10560     DisplayTitle(buf);
10561 }
10562
10563 void
10564 TwoMachinesEvent P((void))
10565 {
10566     int i;
10567     char buf[MSG_SIZ];
10568     ChessProgramState *onmove;
10569     char *bookHit = NULL;
10570     
10571     if (appData.noChessProgram) return;
10572
10573     switch (gameMode) {
10574       case TwoMachinesPlay:
10575         return;
10576       case MachinePlaysWhite:
10577       case MachinePlaysBlack:
10578         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10579             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10580             return;
10581         }
10582         /* fall through */
10583       case BeginningOfGame:
10584       case PlayFromGameFile:
10585       case EndOfGame:
10586         EditGameEvent();
10587         if (gameMode != EditGame) return;
10588         break;
10589       case EditPosition:
10590         EditPositionDone();
10591         break;
10592       case AnalyzeMode:
10593       case AnalyzeFile:
10594         ExitAnalyzeMode();
10595         break;
10596       case EditGame:
10597       default:
10598         break;
10599     }
10600
10601     forwardMostMove = currentMove;
10602     ResurrectChessProgram();    /* in case first program isn't running */
10603
10604     if (second.pr == NULL) {
10605         StartChessProgram(&second);
10606         if (second.protocolVersion == 1) {
10607           TwoMachinesEventIfReady();
10608         } else {
10609           /* kludge: allow timeout for initial "feature" command */
10610           FreezeUI();
10611           DisplayMessage("", _("Starting second chess program"));
10612           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10613         }
10614         return;
10615     }
10616     DisplayMessage("", "");
10617     InitChessProgram(&second, FALSE);
10618     SendToProgram("force\n", &second);
10619     if (startedFromSetupPosition) {
10620         SendBoard(&second, backwardMostMove);
10621     if (appData.debugMode) {
10622         fprintf(debugFP, "Two Machines\n");
10623     }
10624     }
10625     for (i = backwardMostMove; i < forwardMostMove; i++) {
10626         SendMoveToProgram(i, &second);
10627     }
10628
10629     gameMode = TwoMachinesPlay;
10630     pausing = FALSE;
10631     ModeHighlight();
10632     SetGameInfo();
10633     DisplayTwoMachinesTitle();
10634     firstMove = TRUE;
10635     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10636         onmove = &first;
10637     } else {
10638         onmove = &second;
10639     }
10640
10641     SendToProgram(first.computerString, &first);
10642     if (first.sendName) {
10643       sprintf(buf, "name %s\n", second.tidy);
10644       SendToProgram(buf, &first);
10645     }
10646     SendToProgram(second.computerString, &second);
10647     if (second.sendName) {
10648       sprintf(buf, "name %s\n", first.tidy);
10649       SendToProgram(buf, &second);
10650     }
10651
10652     ResetClocks();
10653     if (!first.sendTime || !second.sendTime) {
10654         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10655         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10656     }
10657     if (onmove->sendTime) {
10658       if (onmove->useColors) {
10659         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10660       }
10661       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10662     }
10663     if (onmove->useColors) {
10664       SendToProgram(onmove->twoMachinesColor, onmove);
10665     }
10666     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10667 //    SendToProgram("go\n", onmove);
10668     onmove->maybeThinking = TRUE;
10669     SetMachineThinkingEnables();
10670
10671     StartClocks();
10672
10673     if(bookHit) { // [HGM] book: simulate book reply
10674         static char bookMove[MSG_SIZ]; // a bit generous?
10675
10676         programStats.nodes = programStats.depth = programStats.time = 
10677         programStats.score = programStats.got_only_move = 0;
10678         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10679
10680         strcpy(bookMove, "move ");
10681         strcat(bookMove, bookHit);
10682         HandleMachineMove(bookMove, &first);
10683     }
10684 }
10685
10686 void
10687 TrainingEvent()
10688 {
10689     if (gameMode == Training) {
10690       SetTrainingModeOff();
10691       gameMode = PlayFromGameFile;
10692       DisplayMessage("", _("Training mode off"));
10693     } else {
10694       gameMode = Training;
10695       animateTraining = appData.animate;
10696
10697       /* make sure we are not already at the end of the game */
10698       if (currentMove < forwardMostMove) {
10699         SetTrainingModeOn();
10700         DisplayMessage("", _("Training mode on"));
10701       } else {
10702         gameMode = PlayFromGameFile;
10703         DisplayError(_("Already at end of game"), 0);
10704       }
10705     }
10706     ModeHighlight();
10707 }
10708
10709 void
10710 IcsClientEvent()
10711 {
10712     if (!appData.icsActive) return;
10713     switch (gameMode) {
10714       case IcsPlayingWhite:
10715       case IcsPlayingBlack:
10716       case IcsObserving:
10717       case IcsIdle:
10718       case BeginningOfGame:
10719       case IcsExamining:
10720         return;
10721
10722       case EditGame:
10723         break;
10724
10725       case EditPosition:
10726         EditPositionDone();
10727         break;
10728
10729       case AnalyzeMode:
10730       case AnalyzeFile:
10731         ExitAnalyzeMode();
10732         break;
10733         
10734       default:
10735         EditGameEvent();
10736         break;
10737     }
10738
10739     gameMode = IcsIdle;
10740     ModeHighlight();
10741     return;
10742 }
10743
10744
10745 void
10746 EditGameEvent()
10747 {
10748     int i;
10749
10750     switch (gameMode) {
10751       case Training:
10752         SetTrainingModeOff();
10753         break;
10754       case MachinePlaysWhite:
10755       case MachinePlaysBlack:
10756       case BeginningOfGame:
10757         SendToProgram("force\n", &first);
10758         SetUserThinkingEnables();
10759         break;
10760       case PlayFromGameFile:
10761         (void) StopLoadGameTimer();
10762         if (gameFileFP != NULL) {
10763             gameFileFP = NULL;
10764         }
10765         break;
10766       case EditPosition:
10767         EditPositionDone();
10768         break;
10769       case AnalyzeMode:
10770       case AnalyzeFile:
10771         ExitAnalyzeMode();
10772         SendToProgram("force\n", &first);
10773         break;
10774       case TwoMachinesPlay:
10775         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10776         ResurrectChessProgram();
10777         SetUserThinkingEnables();
10778         break;
10779       case EndOfGame:
10780         ResurrectChessProgram();
10781         break;
10782       case IcsPlayingBlack:
10783       case IcsPlayingWhite:
10784         DisplayError(_("Warning: You are still playing a game"), 0);
10785         break;
10786       case IcsObserving:
10787         DisplayError(_("Warning: You are still observing a game"), 0);
10788         break;
10789       case IcsExamining:
10790         DisplayError(_("Warning: You are still examining a game"), 0);
10791         break;
10792       case IcsIdle:
10793         break;
10794       case EditGame:
10795       default:
10796         return;
10797     }
10798     
10799     pausing = FALSE;
10800     StopClocks();
10801     first.offeredDraw = second.offeredDraw = 0;
10802
10803     if (gameMode == PlayFromGameFile) {
10804         whiteTimeRemaining = timeRemaining[0][currentMove];
10805         blackTimeRemaining = timeRemaining[1][currentMove];
10806         DisplayTitle("");
10807     }
10808
10809     if (gameMode == MachinePlaysWhite ||
10810         gameMode == MachinePlaysBlack ||
10811         gameMode == TwoMachinesPlay ||
10812         gameMode == EndOfGame) {
10813         i = forwardMostMove;
10814         while (i > currentMove) {
10815             SendToProgram("undo\n", &first);
10816             i--;
10817         }
10818         whiteTimeRemaining = timeRemaining[0][currentMove];
10819         blackTimeRemaining = timeRemaining[1][currentMove];
10820         DisplayBothClocks();
10821         if (whiteFlag || blackFlag) {
10822             whiteFlag = blackFlag = 0;
10823         }
10824         DisplayTitle("");
10825     }           
10826     
10827     gameMode = EditGame;
10828     ModeHighlight();
10829     SetGameInfo();
10830 }
10831
10832
10833 void
10834 EditPositionEvent()
10835 {
10836     if (gameMode == EditPosition) {
10837         EditGameEvent();
10838         return;
10839     }
10840     
10841     EditGameEvent();
10842     if (gameMode != EditGame) return;
10843     
10844     gameMode = EditPosition;
10845     ModeHighlight();
10846     SetGameInfo();
10847     if (currentMove > 0)
10848       CopyBoard(boards[0], boards[currentMove]);
10849     
10850     blackPlaysFirst = !WhiteOnMove(currentMove);
10851     ResetClocks();
10852     currentMove = forwardMostMove = backwardMostMove = 0;
10853     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10854     DisplayMove(-1);
10855 }
10856
10857 void
10858 ExitAnalyzeMode()
10859 {
10860     /* [DM] icsEngineAnalyze - possible call from other functions */
10861     if (appData.icsEngineAnalyze) {
10862         appData.icsEngineAnalyze = FALSE;
10863
10864         DisplayMessage("",_("Close ICS engine analyze..."));
10865     }
10866     if (first.analysisSupport && first.analyzing) {
10867       SendToProgram("exit\n", &first);
10868       first.analyzing = FALSE;
10869     }
10870     AnalysisPopDown();
10871     thinkOutput[0] = NULLCHAR;
10872 }
10873
10874 void
10875 EditPositionDone()
10876 {
10877     startedFromSetupPosition = TRUE;
10878     InitChessProgram(&first, FALSE);
10879     SendToProgram("force\n", &first);
10880     if (blackPlaysFirst) {
10881         strcpy(moveList[0], "");
10882         strcpy(parseList[0], "");
10883         currentMove = forwardMostMove = backwardMostMove = 1;
10884         CopyBoard(boards[1], boards[0]);
10885         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10886         { int i;
10887           epStatus[1] = epStatus[0];
10888           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10889         }
10890     } else {
10891         currentMove = forwardMostMove = backwardMostMove = 0;
10892     }
10893     SendBoard(&first, forwardMostMove);
10894     if (appData.debugMode) {
10895         fprintf(debugFP, "EditPosDone\n");
10896     }
10897     DisplayTitle("");
10898     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10899     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10900     gameMode = EditGame;
10901     ModeHighlight();
10902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10903     ClearHighlights(); /* [AS] */
10904 }
10905
10906 /* Pause for `ms' milliseconds */
10907 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10908 void
10909 TimeDelay(ms)
10910      long ms;
10911 {
10912     TimeMark m1, m2;
10913
10914     GetTimeMark(&m1);
10915     do {
10916         GetTimeMark(&m2);
10917     } while (SubtractTimeMarks(&m2, &m1) < ms);
10918 }
10919
10920 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10921 void
10922 SendMultiLineToICS(buf)
10923      char *buf;
10924 {
10925     char temp[MSG_SIZ+1], *p;
10926     int len;
10927
10928     len = strlen(buf);
10929     if (len > MSG_SIZ)
10930       len = MSG_SIZ;
10931   
10932     strncpy(temp, buf, len);
10933     temp[len] = 0;
10934
10935     p = temp;
10936     while (*p) {
10937         if (*p == '\n' || *p == '\r')
10938           *p = ' ';
10939         ++p;
10940     }
10941
10942     strcat(temp, "\n");
10943     SendToICS(temp);
10944     SendToPlayer(temp, strlen(temp));
10945 }
10946
10947 void
10948 SetWhiteToPlayEvent()
10949 {
10950     if (gameMode == EditPosition) {
10951         blackPlaysFirst = FALSE;
10952         DisplayBothClocks();    /* works because currentMove is 0 */
10953     } else if (gameMode == IcsExamining) {
10954         SendToICS(ics_prefix);
10955         SendToICS("tomove white\n");
10956     }
10957 }
10958
10959 void
10960 SetBlackToPlayEvent()
10961 {
10962     if (gameMode == EditPosition) {
10963         blackPlaysFirst = TRUE;
10964         currentMove = 1;        /* kludge */
10965         DisplayBothClocks();
10966         currentMove = 0;
10967     } else if (gameMode == IcsExamining) {
10968         SendToICS(ics_prefix);
10969         SendToICS("tomove black\n");
10970     }
10971 }
10972
10973 void
10974 EditPositionMenuEvent(selection, x, y)
10975      ChessSquare selection;
10976      int x, y;
10977 {
10978     char buf[MSG_SIZ];
10979     ChessSquare piece = boards[0][y][x];
10980
10981     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10982
10983     switch (selection) {
10984       case ClearBoard:
10985         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10986             SendToICS(ics_prefix);
10987             SendToICS("bsetup clear\n");
10988         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10989             SendToICS(ics_prefix);
10990             SendToICS("clearboard\n");
10991         } else {
10992             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10993                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10994                 for (y = 0; y < BOARD_HEIGHT; y++) {
10995                     if (gameMode == IcsExamining) {
10996                         if (boards[currentMove][y][x] != EmptySquare) {
10997                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10998                                     AAA + x, ONE + y);
10999                             SendToICS(buf);
11000                         }
11001                     } else {
11002                         boards[0][y][x] = p;
11003                     }
11004                 }
11005             }
11006         }
11007         if (gameMode == EditPosition) {
11008             DrawPosition(FALSE, boards[0]);
11009         }
11010         break;
11011
11012       case WhitePlay:
11013         SetWhiteToPlayEvent();
11014         break;
11015
11016       case BlackPlay:
11017         SetBlackToPlayEvent();
11018         break;
11019
11020       case EmptySquare:
11021         if (gameMode == IcsExamining) {
11022             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11023             SendToICS(buf);
11024         } else {
11025             boards[0][y][x] = EmptySquare;
11026             DrawPosition(FALSE, boards[0]);
11027         }
11028         break;
11029
11030       case PromotePiece:
11031         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11032            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11033             selection = (ChessSquare) (PROMOTED piece);
11034         } else if(piece == EmptySquare) selection = WhiteSilver;
11035         else selection = (ChessSquare)((int)piece - 1);
11036         goto defaultlabel;
11037
11038       case DemotePiece:
11039         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11040            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11041             selection = (ChessSquare) (DEMOTED piece);
11042         } else if(piece == EmptySquare) selection = BlackSilver;
11043         else selection = (ChessSquare)((int)piece + 1);       
11044         goto defaultlabel;
11045
11046       case WhiteQueen:
11047       case BlackQueen:
11048         if(gameInfo.variant == VariantShatranj ||
11049            gameInfo.variant == VariantXiangqi  ||
11050            gameInfo.variant == VariantCourier    )
11051             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11052         goto defaultlabel;
11053
11054       case WhiteKing:
11055       case BlackKing:
11056         if(gameInfo.variant == VariantXiangqi)
11057             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11058         if(gameInfo.variant == VariantKnightmate)
11059             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11060       default:
11061         defaultlabel:
11062         if (gameMode == IcsExamining) {
11063             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11064                     PieceToChar(selection), AAA + x, ONE + y);
11065             SendToICS(buf);
11066         } else {
11067             boards[0][y][x] = selection;
11068             DrawPosition(FALSE, boards[0]);
11069         }
11070         break;
11071     }
11072 }
11073
11074
11075 void
11076 DropMenuEvent(selection, x, y)
11077      ChessSquare selection;
11078      int x, y;
11079 {
11080     ChessMove moveType;
11081
11082     switch (gameMode) {
11083       case IcsPlayingWhite:
11084       case MachinePlaysBlack:
11085         if (!WhiteOnMove(currentMove)) {
11086             DisplayMoveError(_("It is Black's turn"));
11087             return;
11088         }
11089         moveType = WhiteDrop;
11090         break;
11091       case IcsPlayingBlack:
11092       case MachinePlaysWhite:
11093         if (WhiteOnMove(currentMove)) {
11094             DisplayMoveError(_("It is White's turn"));
11095             return;
11096         }
11097         moveType = BlackDrop;
11098         break;
11099       case EditGame:
11100         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11101         break;
11102       default:
11103         return;
11104     }
11105
11106     if (moveType == BlackDrop && selection < BlackPawn) {
11107       selection = (ChessSquare) ((int) selection
11108                                  + (int) BlackPawn - (int) WhitePawn);
11109     }
11110     if (boards[currentMove][y][x] != EmptySquare) {
11111         DisplayMoveError(_("That square is occupied"));
11112         return;
11113     }
11114
11115     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11116 }
11117
11118 void
11119 AcceptEvent()
11120 {
11121     /* Accept a pending offer of any kind from opponent */
11122     
11123     if (appData.icsActive) {
11124         SendToICS(ics_prefix);
11125         SendToICS("accept\n");
11126     } else if (cmailMsgLoaded) {
11127         if (currentMove == cmailOldMove &&
11128             commentList[cmailOldMove] != NULL &&
11129             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11130                    "Black offers a draw" : "White offers a draw")) {
11131             TruncateGame();
11132             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11133             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11134         } else {
11135             DisplayError(_("There is no pending offer on this move"), 0);
11136             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11137         }
11138     } else {
11139         /* Not used for offers from chess program */
11140     }
11141 }
11142
11143 void
11144 DeclineEvent()
11145 {
11146     /* Decline a pending offer of any kind from opponent */
11147     
11148     if (appData.icsActive) {
11149         SendToICS(ics_prefix);
11150         SendToICS("decline\n");
11151     } else if (cmailMsgLoaded) {
11152         if (currentMove == cmailOldMove &&
11153             commentList[cmailOldMove] != NULL &&
11154             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11155                    "Black offers a draw" : "White offers a draw")) {
11156 #ifdef NOTDEF
11157             AppendComment(cmailOldMove, "Draw declined");
11158             DisplayComment(cmailOldMove - 1, "Draw declined");
11159 #endif /*NOTDEF*/
11160         } else {
11161             DisplayError(_("There is no pending offer on this move"), 0);
11162         }
11163     } else {
11164         /* Not used for offers from chess program */
11165     }
11166 }
11167
11168 void
11169 RematchEvent()
11170 {
11171     /* Issue ICS rematch command */
11172     if (appData.icsActive) {
11173         SendToICS(ics_prefix);
11174         SendToICS("rematch\n");
11175     }
11176 }
11177
11178 void
11179 CallFlagEvent()
11180 {
11181     /* Call your opponent's flag (claim a win on time) */
11182     if (appData.icsActive) {
11183         SendToICS(ics_prefix);
11184         SendToICS("flag\n");
11185     } else {
11186         switch (gameMode) {
11187           default:
11188             return;
11189           case MachinePlaysWhite:
11190             if (whiteFlag) {
11191                 if (blackFlag)
11192                   GameEnds(GameIsDrawn, "Both players ran out of time",
11193                            GE_PLAYER);
11194                 else
11195                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11196             } else {
11197                 DisplayError(_("Your opponent is not out of time"), 0);
11198             }
11199             break;
11200           case MachinePlaysBlack:
11201             if (blackFlag) {
11202                 if (whiteFlag)
11203                   GameEnds(GameIsDrawn, "Both players ran out of time",
11204                            GE_PLAYER);
11205                 else
11206                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11207             } else {
11208                 DisplayError(_("Your opponent is not out of time"), 0);
11209             }
11210             break;
11211         }
11212     }
11213 }
11214
11215 void
11216 DrawEvent()
11217 {
11218     /* Offer draw or accept pending draw offer from opponent */
11219     
11220     if (appData.icsActive) {
11221         /* Note: tournament rules require draw offers to be
11222            made after you make your move but before you punch
11223            your clock.  Currently ICS doesn't let you do that;
11224            instead, you immediately punch your clock after making
11225            a move, but you can offer a draw at any time. */
11226         
11227         SendToICS(ics_prefix);
11228         SendToICS("draw\n");
11229     } else if (cmailMsgLoaded) {
11230         if (currentMove == cmailOldMove &&
11231             commentList[cmailOldMove] != NULL &&
11232             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11233                    "Black offers a draw" : "White offers a draw")) {
11234             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11236         } else if (currentMove == cmailOldMove + 1) {
11237             char *offer = WhiteOnMove(cmailOldMove) ?
11238               "White offers a draw" : "Black offers a draw";
11239             AppendComment(currentMove, offer);
11240             DisplayComment(currentMove - 1, offer);
11241             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11242         } else {
11243             DisplayError(_("You must make your move before offering a draw"), 0);
11244             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11245         }
11246     } else if (first.offeredDraw) {
11247         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11248     } else {
11249         if (first.sendDrawOffers) {
11250             SendToProgram("draw\n", &first);
11251             userOfferedDraw = TRUE;
11252         }
11253     }
11254 }
11255
11256 void
11257 AdjournEvent()
11258 {
11259     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11260     
11261     if (appData.icsActive) {
11262         SendToICS(ics_prefix);
11263         SendToICS("adjourn\n");
11264     } else {
11265         /* Currently GNU Chess doesn't offer or accept Adjourns */
11266     }
11267 }
11268
11269
11270 void
11271 AbortEvent()
11272 {
11273     /* Offer Abort or accept pending Abort offer from opponent */
11274     
11275     if (appData.icsActive) {
11276         SendToICS(ics_prefix);
11277         SendToICS("abort\n");
11278     } else {
11279         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11280     }
11281 }
11282
11283 void
11284 ResignEvent()
11285 {
11286     /* Resign.  You can do this even if it's not your turn. */
11287     
11288     if (appData.icsActive) {
11289         SendToICS(ics_prefix);
11290         SendToICS("resign\n");
11291     } else {
11292         switch (gameMode) {
11293           case MachinePlaysWhite:
11294             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11295             break;
11296           case MachinePlaysBlack:
11297             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11298             break;
11299           case EditGame:
11300             if (cmailMsgLoaded) {
11301                 TruncateGame();
11302                 if (WhiteOnMove(cmailOldMove)) {
11303                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11304                 } else {
11305                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11306                 }
11307                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11308             }
11309             break;
11310           default:
11311             break;
11312         }
11313     }
11314 }
11315
11316
11317 void
11318 StopObservingEvent()
11319 {
11320     /* Stop observing current games */
11321     SendToICS(ics_prefix);
11322     SendToICS("unobserve\n");
11323 }
11324
11325 void
11326 StopExaminingEvent()
11327 {
11328     /* Stop observing current game */
11329     SendToICS(ics_prefix);
11330     SendToICS("unexamine\n");
11331 }
11332
11333 void
11334 ForwardInner(target)
11335      int target;
11336 {
11337     int limit;
11338
11339     if (appData.debugMode)
11340         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11341                 target, currentMove, forwardMostMove);
11342
11343     if (gameMode == EditPosition)
11344       return;
11345
11346     if (gameMode == PlayFromGameFile && !pausing)
11347       PauseEvent();
11348     
11349     if (gameMode == IcsExamining && pausing)
11350       limit = pauseExamForwardMostMove;
11351     else
11352       limit = forwardMostMove;
11353     
11354     if (target > limit) target = limit;
11355
11356     if (target > 0 && moveList[target - 1][0]) {
11357         int fromX, fromY, toX, toY;
11358         toX = moveList[target - 1][2] - AAA;
11359         toY = moveList[target - 1][3] - ONE;
11360         if (moveList[target - 1][1] == '@') {
11361             if (appData.highlightLastMove) {
11362                 SetHighlights(-1, -1, toX, toY);
11363             }
11364         } else {
11365             fromX = moveList[target - 1][0] - AAA;
11366             fromY = moveList[target - 1][1] - ONE;
11367             if (target == currentMove + 1) {
11368                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11369             }
11370             if (appData.highlightLastMove) {
11371                 SetHighlights(fromX, fromY, toX, toY);
11372             }
11373         }
11374     }
11375     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11376         gameMode == Training || gameMode == PlayFromGameFile || 
11377         gameMode == AnalyzeFile) {
11378         while (currentMove < target) {
11379             SendMoveToProgram(currentMove++, &first);
11380         }
11381     } else {
11382         currentMove = target;
11383     }
11384     
11385     if (gameMode == EditGame || gameMode == EndOfGame) {
11386         whiteTimeRemaining = timeRemaining[0][currentMove];
11387         blackTimeRemaining = timeRemaining[1][currentMove];
11388     }
11389     DisplayBothClocks();
11390     DisplayMove(currentMove - 1);
11391     DrawPosition(FALSE, boards[currentMove]);
11392     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11393     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11394         DisplayComment(currentMove - 1, commentList[currentMove]);
11395     }
11396 }
11397
11398
11399 void
11400 ForwardEvent()
11401 {
11402     if (gameMode == IcsExamining && !pausing) {
11403         SendToICS(ics_prefix);
11404         SendToICS("forward\n");
11405     } else {
11406         ForwardInner(currentMove + 1);
11407     }
11408 }
11409
11410 void
11411 ToEndEvent()
11412 {
11413     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11414         /* to optimze, we temporarily turn off analysis mode while we feed
11415          * the remaining moves to the engine. Otherwise we get analysis output
11416          * after each move.
11417          */ 
11418         if (first.analysisSupport) {
11419           SendToProgram("exit\nforce\n", &first);
11420           first.analyzing = FALSE;
11421         }
11422     }
11423         
11424     if (gameMode == IcsExamining && !pausing) {
11425         SendToICS(ics_prefix);
11426         SendToICS("forward 999999\n");
11427     } else {
11428         ForwardInner(forwardMostMove);
11429     }
11430
11431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11432         /* we have fed all the moves, so reactivate analysis mode */
11433         SendToProgram("analyze\n", &first);
11434         first.analyzing = TRUE;
11435         /*first.maybeThinking = TRUE;*/
11436         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11437     }
11438 }
11439
11440 void
11441 BackwardInner(target)
11442      int target;
11443 {
11444     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11445
11446     if (appData.debugMode)
11447         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11448                 target, currentMove, forwardMostMove);
11449
11450     if (gameMode == EditPosition) return;
11451     if (currentMove <= backwardMostMove) {
11452         ClearHighlights();
11453         DrawPosition(full_redraw, boards[currentMove]);
11454         return;
11455     }
11456     if (gameMode == PlayFromGameFile && !pausing)
11457       PauseEvent();
11458     
11459     if (moveList[target][0]) {
11460         int fromX, fromY, toX, toY;
11461         toX = moveList[target][2] - AAA;
11462         toY = moveList[target][3] - ONE;
11463         if (moveList[target][1] == '@') {
11464             if (appData.highlightLastMove) {
11465                 SetHighlights(-1, -1, toX, toY);
11466             }
11467         } else {
11468             fromX = moveList[target][0] - AAA;
11469             fromY = moveList[target][1] - ONE;
11470             if (target == currentMove - 1) {
11471                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11472             }
11473             if (appData.highlightLastMove) {
11474                 SetHighlights(fromX, fromY, toX, toY);
11475             }
11476         }
11477     }
11478     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11479         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11480         while (currentMove > target) {
11481             SendToProgram("undo\n", &first);
11482             currentMove--;
11483         }
11484     } else {
11485         currentMove = target;
11486     }
11487     
11488     if (gameMode == EditGame || gameMode == EndOfGame) {
11489         whiteTimeRemaining = timeRemaining[0][currentMove];
11490         blackTimeRemaining = timeRemaining[1][currentMove];
11491     }
11492     DisplayBothClocks();
11493     DisplayMove(currentMove - 1);
11494     DrawPosition(full_redraw, boards[currentMove]);
11495     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11496     // [HGM] PV info: routine tests if comment empty
11497     DisplayComment(currentMove - 1, commentList[currentMove]);
11498 }
11499
11500 void
11501 BackwardEvent()
11502 {
11503     if (gameMode == IcsExamining && !pausing) {
11504         SendToICS(ics_prefix);
11505         SendToICS("backward\n");
11506     } else {
11507         BackwardInner(currentMove - 1);
11508     }
11509 }
11510
11511 void
11512 ToStartEvent()
11513 {
11514     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11515         /* to optimze, we temporarily turn off analysis mode while we undo
11516          * all the moves. Otherwise we get analysis output after each undo.
11517          */ 
11518         if (first.analysisSupport) {
11519           SendToProgram("exit\nforce\n", &first);
11520           first.analyzing = FALSE;
11521         }
11522     }
11523
11524     if (gameMode == IcsExamining && !pausing) {
11525         SendToICS(ics_prefix);
11526         SendToICS("backward 999999\n");
11527     } else {
11528         BackwardInner(backwardMostMove);
11529     }
11530
11531     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11532         /* we have fed all the moves, so reactivate analysis mode */
11533         SendToProgram("analyze\n", &first);
11534         first.analyzing = TRUE;
11535         /*first.maybeThinking = TRUE;*/
11536         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11537     }
11538 }
11539
11540 void
11541 ToNrEvent(int to)
11542 {
11543   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11544   if (to >= forwardMostMove) to = forwardMostMove;
11545   if (to <= backwardMostMove) to = backwardMostMove;
11546   if (to < currentMove) {
11547     BackwardInner(to);
11548   } else {
11549     ForwardInner(to);
11550   }
11551 }
11552
11553 void
11554 RevertEvent()
11555 {
11556     if (gameMode != IcsExamining) {
11557         DisplayError(_("You are not examining a game"), 0);
11558         return;
11559     }
11560     if (pausing) {
11561         DisplayError(_("You can't revert while pausing"), 0);
11562         return;
11563     }
11564     SendToICS(ics_prefix);
11565     SendToICS("revert\n");
11566 }
11567
11568 void
11569 RetractMoveEvent()
11570 {
11571     switch (gameMode) {
11572       case MachinePlaysWhite:
11573       case MachinePlaysBlack:
11574         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11575             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11576             return;
11577         }
11578         if (forwardMostMove < 2) return;
11579         currentMove = forwardMostMove = forwardMostMove - 2;
11580         whiteTimeRemaining = timeRemaining[0][currentMove];
11581         blackTimeRemaining = timeRemaining[1][currentMove];
11582         DisplayBothClocks();
11583         DisplayMove(currentMove - 1);
11584         ClearHighlights();/*!! could figure this out*/
11585         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11586         SendToProgram("remove\n", &first);
11587         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11588         break;
11589
11590       case BeginningOfGame:
11591       default:
11592         break;
11593
11594       case IcsPlayingWhite:
11595       case IcsPlayingBlack:
11596         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11597             SendToICS(ics_prefix);
11598             SendToICS("takeback 2\n");
11599         } else {
11600             SendToICS(ics_prefix);
11601             SendToICS("takeback 1\n");
11602         }
11603         break;
11604     }
11605 }
11606
11607 void
11608 MoveNowEvent()
11609 {
11610     ChessProgramState *cps;
11611
11612     switch (gameMode) {
11613       case MachinePlaysWhite:
11614         if (!WhiteOnMove(forwardMostMove)) {
11615             DisplayError(_("It is your turn"), 0);
11616             return;
11617         }
11618         cps = &first;
11619         break;
11620       case MachinePlaysBlack:
11621         if (WhiteOnMove(forwardMostMove)) {
11622             DisplayError(_("It is your turn"), 0);
11623             return;
11624         }
11625         cps = &first;
11626         break;
11627       case TwoMachinesPlay:
11628         if (WhiteOnMove(forwardMostMove) ==
11629             (first.twoMachinesColor[0] == 'w')) {
11630             cps = &first;
11631         } else {
11632             cps = &second;
11633         }
11634         break;
11635       case BeginningOfGame:
11636       default:
11637         return;
11638     }
11639     SendToProgram("?\n", cps);
11640 }
11641
11642 void
11643 TruncateGameEvent()
11644 {
11645     EditGameEvent();
11646     if (gameMode != EditGame) return;
11647     TruncateGame();
11648 }
11649
11650 void
11651 TruncateGame()
11652 {
11653     if (forwardMostMove > currentMove) {
11654         if (gameInfo.resultDetails != NULL) {
11655             free(gameInfo.resultDetails);
11656             gameInfo.resultDetails = NULL;
11657             gameInfo.result = GameUnfinished;
11658         }
11659         forwardMostMove = currentMove;
11660         HistorySet(parseList, backwardMostMove, forwardMostMove,
11661                    currentMove-1);
11662     }
11663 }
11664
11665 void
11666 HintEvent()
11667 {
11668     if (appData.noChessProgram) return;
11669     switch (gameMode) {
11670       case MachinePlaysWhite:
11671         if (WhiteOnMove(forwardMostMove)) {
11672             DisplayError(_("Wait until your turn"), 0);
11673             return;
11674         }
11675         break;
11676       case BeginningOfGame:
11677       case MachinePlaysBlack:
11678         if (!WhiteOnMove(forwardMostMove)) {
11679             DisplayError(_("Wait until your turn"), 0);
11680             return;
11681         }
11682         break;
11683       default:
11684         DisplayError(_("No hint available"), 0);
11685         return;
11686     }
11687     SendToProgram("hint\n", &first);
11688     hintRequested = TRUE;
11689 }
11690
11691 void
11692 BookEvent()
11693 {
11694     if (appData.noChessProgram) return;
11695     switch (gameMode) {
11696       case MachinePlaysWhite:
11697         if (WhiteOnMove(forwardMostMove)) {
11698             DisplayError(_("Wait until your turn"), 0);
11699             return;
11700         }
11701         break;
11702       case BeginningOfGame:
11703       case MachinePlaysBlack:
11704         if (!WhiteOnMove(forwardMostMove)) {
11705             DisplayError(_("Wait until your turn"), 0);
11706             return;
11707         }
11708         break;
11709       case EditPosition:
11710         EditPositionDone();
11711         break;
11712       case TwoMachinesPlay:
11713         return;
11714       default:
11715         break;
11716     }
11717     SendToProgram("bk\n", &first);
11718     bookOutput[0] = NULLCHAR;
11719     bookRequested = TRUE;
11720 }
11721
11722 void
11723 AboutGameEvent()
11724 {
11725     char *tags = PGNTags(&gameInfo);
11726     TagsPopUp(tags, CmailMsg());
11727     free(tags);
11728 }
11729
11730 /* end button procedures */
11731
11732 void
11733 PrintPosition(fp, move)
11734      FILE *fp;
11735      int move;
11736 {
11737     int i, j;
11738     
11739     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11740         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11741             char c = PieceToChar(boards[move][i][j]);
11742             fputc(c == 'x' ? '.' : c, fp);
11743             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11744         }
11745     }
11746     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11747       fprintf(fp, "white to play\n");
11748     else
11749       fprintf(fp, "black to play\n");
11750 }
11751
11752 void
11753 PrintOpponents(fp)
11754      FILE *fp;
11755 {
11756     if (gameInfo.white != NULL) {
11757         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11758     } else {
11759         fprintf(fp, "\n");
11760     }
11761 }
11762
11763 /* Find last component of program's own name, using some heuristics */
11764 void
11765 TidyProgramName(prog, host, buf)
11766      char *prog, *host, buf[MSG_SIZ];
11767 {
11768     char *p, *q;
11769     int local = (strcmp(host, "localhost") == 0);
11770     while (!local && (p = strchr(prog, ';')) != NULL) {
11771         p++;
11772         while (*p == ' ') p++;
11773         prog = p;
11774     }
11775     if (*prog == '"' || *prog == '\'') {
11776         q = strchr(prog + 1, *prog);
11777     } else {
11778         q = strchr(prog, ' ');
11779     }
11780     if (q == NULL) q = prog + strlen(prog);
11781     p = q;
11782     while (p >= prog && *p != '/' && *p != '\\') p--;
11783     p++;
11784     if(p == prog && *p == '"') p++;
11785     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11786     memcpy(buf, p, q - p);
11787     buf[q - p] = NULLCHAR;
11788     if (!local) {
11789         strcat(buf, "@");
11790         strcat(buf, host);
11791     }
11792 }
11793
11794 char *
11795 TimeControlTagValue()
11796 {
11797     char buf[MSG_SIZ];
11798     if (!appData.clockMode) {
11799         strcpy(buf, "-");
11800     } else if (movesPerSession > 0) {
11801         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11802     } else if (timeIncrement == 0) {
11803         sprintf(buf, "%ld", timeControl/1000);
11804     } else {
11805         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11806     }
11807     return StrSave(buf);
11808 }
11809
11810 void
11811 SetGameInfo()
11812 {
11813     /* This routine is used only for certain modes */
11814     VariantClass v = gameInfo.variant;
11815     ClearGameInfo(&gameInfo);
11816     gameInfo.variant = v;
11817
11818     switch (gameMode) {
11819       case MachinePlaysWhite:
11820         gameInfo.event = StrSave( appData.pgnEventHeader );
11821         gameInfo.site = StrSave(HostName());
11822         gameInfo.date = PGNDate();
11823         gameInfo.round = StrSave("-");
11824         gameInfo.white = StrSave(first.tidy);
11825         gameInfo.black = StrSave(UserName());
11826         gameInfo.timeControl = TimeControlTagValue();
11827         break;
11828
11829       case MachinePlaysBlack:
11830         gameInfo.event = StrSave( appData.pgnEventHeader );
11831         gameInfo.site = StrSave(HostName());
11832         gameInfo.date = PGNDate();
11833         gameInfo.round = StrSave("-");
11834         gameInfo.white = StrSave(UserName());
11835         gameInfo.black = StrSave(first.tidy);
11836         gameInfo.timeControl = TimeControlTagValue();
11837         break;
11838
11839       case TwoMachinesPlay:
11840         gameInfo.event = StrSave( appData.pgnEventHeader );
11841         gameInfo.site = StrSave(HostName());
11842         gameInfo.date = PGNDate();
11843         if (matchGame > 0) {
11844             char buf[MSG_SIZ];
11845             sprintf(buf, "%d", matchGame);
11846             gameInfo.round = StrSave(buf);
11847         } else {
11848             gameInfo.round = StrSave("-");
11849         }
11850         if (first.twoMachinesColor[0] == 'w') {
11851             gameInfo.white = StrSave(first.tidy);
11852             gameInfo.black = StrSave(second.tidy);
11853         } else {
11854             gameInfo.white = StrSave(second.tidy);
11855             gameInfo.black = StrSave(first.tidy);
11856         }
11857         gameInfo.timeControl = TimeControlTagValue();
11858         break;
11859
11860       case EditGame:
11861         gameInfo.event = StrSave("Edited game");
11862         gameInfo.site = StrSave(HostName());
11863         gameInfo.date = PGNDate();
11864         gameInfo.round = StrSave("-");
11865         gameInfo.white = StrSave("-");
11866         gameInfo.black = StrSave("-");
11867         break;
11868
11869       case EditPosition:
11870         gameInfo.event = StrSave("Edited position");
11871         gameInfo.site = StrSave(HostName());
11872         gameInfo.date = PGNDate();
11873         gameInfo.round = StrSave("-");
11874         gameInfo.white = StrSave("-");
11875         gameInfo.black = StrSave("-");
11876         break;
11877
11878       case IcsPlayingWhite:
11879       case IcsPlayingBlack:
11880       case IcsObserving:
11881       case IcsExamining:
11882         break;
11883
11884       case PlayFromGameFile:
11885         gameInfo.event = StrSave("Game from non-PGN file");
11886         gameInfo.site = StrSave(HostName());
11887         gameInfo.date = PGNDate();
11888         gameInfo.round = StrSave("-");
11889         gameInfo.white = StrSave("?");
11890         gameInfo.black = StrSave("?");
11891         break;
11892
11893       default:
11894         break;
11895     }
11896 }
11897
11898 void
11899 ReplaceComment(index, text)
11900      int index;
11901      char *text;
11902 {
11903     int len;
11904
11905     while (*text == '\n') text++;
11906     len = strlen(text);
11907     while (len > 0 && text[len - 1] == '\n') len--;
11908
11909     if (commentList[index] != NULL)
11910       free(commentList[index]);
11911
11912     if (len == 0) {
11913         commentList[index] = NULL;
11914         return;
11915     }
11916     commentList[index] = (char *) malloc(len + 2);
11917     strncpy(commentList[index], text, len);
11918     commentList[index][len] = '\n';
11919     commentList[index][len + 1] = NULLCHAR;
11920 }
11921
11922 void
11923 CrushCRs(text)
11924      char *text;
11925 {
11926   char *p = text;
11927   char *q = text;
11928   char ch;
11929
11930   do {
11931     ch = *p++;
11932     if (ch == '\r') continue;
11933     *q++ = ch;
11934   } while (ch != '\0');
11935 }
11936
11937 void
11938 AppendComment(index, text)
11939      int index;
11940      char *text;
11941 {
11942     int oldlen, len;
11943     char *old;
11944
11945     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11946
11947     CrushCRs(text);
11948     while (*text == '\n') text++;
11949     len = strlen(text);
11950     while (len > 0 && text[len - 1] == '\n') len--;
11951
11952     if (len == 0) return;
11953
11954     if (commentList[index] != NULL) {
11955         old = commentList[index];
11956         oldlen = strlen(old);
11957         commentList[index] = (char *) malloc(oldlen + len + 2);
11958         strcpy(commentList[index], old);
11959         free(old);
11960         strncpy(&commentList[index][oldlen], text, len);
11961         commentList[index][oldlen + len] = '\n';
11962         commentList[index][oldlen + len + 1] = NULLCHAR;
11963     } else {
11964         commentList[index] = (char *) malloc(len + 2);
11965         strncpy(commentList[index], text, len);
11966         commentList[index][len] = '\n';
11967         commentList[index][len + 1] = NULLCHAR;
11968     }
11969 }
11970
11971 static char * FindStr( char * text, char * sub_text )
11972 {
11973     char * result = strstr( text, sub_text );
11974
11975     if( result != NULL ) {
11976         result += strlen( sub_text );
11977     }
11978
11979     return result;
11980 }
11981
11982 /* [AS] Try to extract PV info from PGN comment */
11983 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11984 char *GetInfoFromComment( int index, char * text )
11985 {
11986     char * sep = text;
11987
11988     if( text != NULL && index > 0 ) {
11989         int score = 0;
11990         int depth = 0;
11991         int time = -1, sec = 0, deci;
11992         char * s_eval = FindStr( text, "[%eval " );
11993         char * s_emt = FindStr( text, "[%emt " );
11994
11995         if( s_eval != NULL || s_emt != NULL ) {
11996             /* New style */
11997             char delim;
11998
11999             if( s_eval != NULL ) {
12000                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12001                     return text;
12002                 }
12003
12004                 if( delim != ']' ) {
12005                     return text;
12006                 }
12007             }
12008
12009             if( s_emt != NULL ) {
12010             }
12011         }
12012         else {
12013             /* We expect something like: [+|-]nnn.nn/dd */
12014             int score_lo = 0;
12015
12016             sep = strchr( text, '/' );
12017             if( sep == NULL || sep < (text+4) ) {
12018                 return text;
12019             }
12020
12021             time = -1; sec = -1; deci = -1;
12022             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12023                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12024                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12025                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12026                 return text;
12027             }
12028
12029             if( score_lo < 0 || score_lo >= 100 ) {
12030                 return text;
12031             }
12032
12033             if(sec >= 0) time = 600*time + 10*sec; else
12034             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12035
12036             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12037
12038             /* [HGM] PV time: now locate end of PV info */
12039             while( *++sep >= '0' && *sep <= '9'); // strip depth
12040             if(time >= 0)
12041             while( *++sep >= '0' && *sep <= '9'); // strip time
12042             if(sec >= 0)
12043             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12044             if(deci >= 0)
12045             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12046             while(*sep == ' ') sep++;
12047         }
12048
12049         if( depth <= 0 ) {
12050             return text;
12051         }
12052
12053         if( time < 0 ) {
12054             time = -1;
12055         }
12056
12057         pvInfoList[index-1].depth = depth;
12058         pvInfoList[index-1].score = score;
12059         pvInfoList[index-1].time  = 10*time; // centi-sec
12060     }
12061     return sep;
12062 }
12063
12064 void
12065 SendToProgram(message, cps)
12066      char *message;
12067      ChessProgramState *cps;
12068 {
12069     int count, outCount, error;
12070     char buf[MSG_SIZ];
12071
12072     if (cps->pr == NULL) return;
12073     Attention(cps);
12074     
12075     if (appData.debugMode) {
12076         TimeMark now;
12077         GetTimeMark(&now);
12078         fprintf(debugFP, "%ld >%-6s: %s", 
12079                 SubtractTimeMarks(&now, &programStartTime),
12080                 cps->which, message);
12081     }
12082     
12083     count = strlen(message);
12084     outCount = OutputToProcess(cps->pr, message, count, &error);
12085     if (outCount < count && !exiting 
12086                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12087         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12088         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12089             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12090                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12091                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12092             } else {
12093                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12094             }
12095             gameInfo.resultDetails = buf;
12096         }
12097         DisplayFatalError(buf, error, 1);
12098     }
12099 }
12100
12101 void
12102 ReceiveFromProgram(isr, closure, message, count, error)
12103      InputSourceRef isr;
12104      VOIDSTAR closure;
12105      char *message;
12106      int count;
12107      int error;
12108 {
12109     char *end_str;
12110     char buf[MSG_SIZ];
12111     ChessProgramState *cps = (ChessProgramState *)closure;
12112
12113     if (isr != cps->isr) return; /* Killed intentionally */
12114     if (count <= 0) {
12115         if (count == 0) {
12116             sprintf(buf,
12117                     _("Error: %s chess program (%s) exited unexpectedly"),
12118                     cps->which, cps->program);
12119         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12120                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12121                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12122                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12123                 } else {
12124                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12125                 }
12126                 gameInfo.resultDetails = buf;
12127             }
12128             RemoveInputSource(cps->isr);
12129             DisplayFatalError(buf, 0, 1);
12130         } else {
12131             sprintf(buf,
12132                     _("Error reading from %s chess program (%s)"),
12133                     cps->which, cps->program);
12134             RemoveInputSource(cps->isr);
12135
12136             /* [AS] Program is misbehaving badly... kill it */
12137             if( count == -2 ) {
12138                 DestroyChildProcess( cps->pr, 9 );
12139                 cps->pr = NoProc;
12140             }
12141
12142             DisplayFatalError(buf, error, 1);
12143         }
12144         return;
12145     }
12146     
12147     if ((end_str = strchr(message, '\r')) != NULL)
12148       *end_str = NULLCHAR;
12149     if ((end_str = strchr(message, '\n')) != NULL)
12150       *end_str = NULLCHAR;
12151     
12152     if (appData.debugMode) {
12153         TimeMark now; int print = 1;
12154         char *quote = ""; char c; int i;
12155
12156         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12157                 char start = message[0];
12158                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12159                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12160                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12161                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12162                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12163                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12164                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12165                         { quote = "# "; print = (appData.engineComments == 2); }
12166                 message[0] = start; // restore original message
12167         }
12168         if(print) {
12169                 GetTimeMark(&now);
12170                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12171                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12172                         quote,
12173                         message);
12174         }
12175     }
12176
12177     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12178     if (appData.icsEngineAnalyze) {
12179         if (strstr(message, "whisper") != NULL ||
12180              strstr(message, "kibitz") != NULL || 
12181             strstr(message, "tellics") != NULL) return;
12182     }
12183
12184     HandleMachineMove(message, cps);
12185 }
12186
12187
12188 void
12189 SendTimeControl(cps, mps, tc, inc, sd, st)
12190      ChessProgramState *cps;
12191      int mps, inc, sd, st;
12192      long tc;
12193 {
12194     char buf[MSG_SIZ];
12195     int seconds;
12196
12197     if( timeControl_2 > 0 ) {
12198         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12199             tc = timeControl_2;
12200         }
12201     }
12202     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12203     inc /= cps->timeOdds;
12204     st  /= cps->timeOdds;
12205
12206     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12207
12208     if (st > 0) {
12209       /* Set exact time per move, normally using st command */
12210       if (cps->stKludge) {
12211         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12212         seconds = st % 60;
12213         if (seconds == 0) {
12214           sprintf(buf, "level 1 %d\n", st/60);
12215         } else {
12216           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12217         }
12218       } else {
12219         sprintf(buf, "st %d\n", st);
12220       }
12221     } else {
12222       /* Set conventional or incremental time control, using level command */
12223       if (seconds == 0) {
12224         /* Note old gnuchess bug -- minutes:seconds used to not work.
12225            Fixed in later versions, but still avoid :seconds
12226            when seconds is 0. */
12227         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12228       } else {
12229         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12230                 seconds, inc/1000);
12231       }
12232     }
12233     SendToProgram(buf, cps);
12234
12235     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12236     /* Orthogonally, limit search to given depth */
12237     if (sd > 0) {
12238       if (cps->sdKludge) {
12239         sprintf(buf, "depth\n%d\n", sd);
12240       } else {
12241         sprintf(buf, "sd %d\n", sd);
12242       }
12243       SendToProgram(buf, cps);
12244     }
12245
12246     if(cps->nps > 0) { /* [HGM] nps */
12247         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12248         else {
12249                 sprintf(buf, "nps %d\n", cps->nps);
12250               SendToProgram(buf, cps);
12251         }
12252     }
12253 }
12254
12255 ChessProgramState *WhitePlayer()
12256 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12257 {
12258     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12259        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12260         return &second;
12261     return &first;
12262 }
12263
12264 void
12265 SendTimeRemaining(cps, machineWhite)
12266      ChessProgramState *cps;
12267      int /*boolean*/ machineWhite;
12268 {
12269     char message[MSG_SIZ];
12270     long time, otime;
12271
12272     /* Note: this routine must be called when the clocks are stopped
12273        or when they have *just* been set or switched; otherwise
12274        it will be off by the time since the current tick started.
12275     */
12276     if (machineWhite) {
12277         time = whiteTimeRemaining / 10;
12278         otime = blackTimeRemaining / 10;
12279     } else {
12280         time = blackTimeRemaining / 10;
12281         otime = whiteTimeRemaining / 10;
12282     }
12283     /* [HGM] translate opponent's time by time-odds factor */
12284     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12285     if (appData.debugMode) {
12286         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12287     }
12288
12289     if (time <= 0) time = 1;
12290     if (otime <= 0) otime = 1;
12291     
12292     sprintf(message, "time %ld\n", time);
12293     SendToProgram(message, cps);
12294
12295     sprintf(message, "otim %ld\n", otime);
12296     SendToProgram(message, cps);
12297 }
12298
12299 int
12300 BoolFeature(p, name, loc, cps)
12301      char **p;
12302      char *name;
12303      int *loc;
12304      ChessProgramState *cps;
12305 {
12306   char buf[MSG_SIZ];
12307   int len = strlen(name);
12308   int val;
12309   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12310     (*p) += len + 1;
12311     sscanf(*p, "%d", &val);
12312     *loc = (val != 0);
12313     while (**p && **p != ' ') (*p)++;
12314     sprintf(buf, "accepted %s\n", name);
12315     SendToProgram(buf, cps);
12316     return TRUE;
12317   }
12318   return FALSE;
12319 }
12320
12321 int
12322 IntFeature(p, name, loc, cps)
12323      char **p;
12324      char *name;
12325      int *loc;
12326      ChessProgramState *cps;
12327 {
12328   char buf[MSG_SIZ];
12329   int len = strlen(name);
12330   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12331     (*p) += len + 1;
12332     sscanf(*p, "%d", loc);
12333     while (**p && **p != ' ') (*p)++;
12334     sprintf(buf, "accepted %s\n", name);
12335     SendToProgram(buf, cps);
12336     return TRUE;
12337   }
12338   return FALSE;
12339 }
12340
12341 int
12342 StringFeature(p, name, loc, cps)
12343      char **p;
12344      char *name;
12345      char loc[];
12346      ChessProgramState *cps;
12347 {
12348   char buf[MSG_SIZ];
12349   int len = strlen(name);
12350   if (strncmp((*p), name, len) == 0
12351       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12352     (*p) += len + 2;
12353     sscanf(*p, "%[^\"]", loc);
12354     while (**p && **p != '\"') (*p)++;
12355     if (**p == '\"') (*p)++;
12356     sprintf(buf, "accepted %s\n", name);
12357     SendToProgram(buf, cps);
12358     return TRUE;
12359   }
12360   return FALSE;
12361 }
12362
12363 int 
12364 ParseOption(Option *opt, ChessProgramState *cps)
12365 // [HGM] options: process the string that defines an engine option, and determine
12366 // name, type, default value, and allowed value range
12367 {
12368         char *p, *q, buf[MSG_SIZ];
12369         int n, min = (-1)<<31, max = 1<<31, def;
12370
12371         if(p = strstr(opt->name, " -spin ")) {
12372             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12373             if(max < min) max = min; // enforce consistency
12374             if(def < min) def = min;
12375             if(def > max) def = max;
12376             opt->value = def;
12377             opt->min = min;
12378             opt->max = max;
12379             opt->type = Spin;
12380         } else if(p = strstr(opt->name, " -string ")) {
12381             opt->textValue = p+9;
12382             opt->type = TextBox;
12383         } else if(p = strstr(opt->name, " -check ")) {
12384             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12385             opt->value = (def != 0);
12386             opt->type = CheckBox;
12387         } else if(p = strstr(opt->name, " -combo ")) {
12388             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12389             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12390             opt->value = n = 0;
12391             while(q = StrStr(q, " /// ")) {
12392                 n++; *q = 0;    // count choices, and null-terminate each of them
12393                 q += 5;
12394                 if(*q == '*') { // remember default, which is marked with * prefix
12395                     q++;
12396                     opt->value = n;
12397                 }
12398                 cps->comboList[cps->comboCnt++] = q;
12399             }
12400             cps->comboList[cps->comboCnt++] = NULL;
12401             opt->max = n + 1;
12402             opt->type = ComboBox;
12403         } else if(p = strstr(opt->name, " -button")) {
12404             opt->type = Button;
12405         } else if(p = strstr(opt->name, " -save")) {
12406             opt->type = SaveButton;
12407         } else return FALSE;
12408         *p = 0; // terminate option name
12409         // now look if the command-line options define a setting for this engine option.
12410         if(cps->optionSettings && cps->optionSettings[0])
12411             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12412         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12413                 sprintf(buf, "option %s", p);
12414                 if(p = strstr(buf, ",")) *p = 0;
12415                 strcat(buf, "\n");
12416                 SendToProgram(buf, cps);
12417         }
12418         return TRUE;
12419 }
12420
12421 void
12422 FeatureDone(cps, val)
12423      ChessProgramState* cps;
12424      int val;
12425 {
12426   DelayedEventCallback cb = GetDelayedEvent();
12427   if ((cb == InitBackEnd3 && cps == &first) ||
12428       (cb == TwoMachinesEventIfReady && cps == &second)) {
12429     CancelDelayedEvent();
12430     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12431   }
12432   cps->initDone = val;
12433 }
12434
12435 /* Parse feature command from engine */
12436 void
12437 ParseFeatures(args, cps)
12438      char* args;
12439      ChessProgramState *cps;  
12440 {
12441   char *p = args;
12442   char *q;
12443   int val;
12444   char buf[MSG_SIZ];
12445
12446   for (;;) {
12447     while (*p == ' ') p++;
12448     if (*p == NULLCHAR) return;
12449
12450     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12451     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12452     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12453     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12454     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12455     if (BoolFeature(&p, "reuse", &val, cps)) {
12456       /* Engine can disable reuse, but can't enable it if user said no */
12457       if (!val) cps->reuse = FALSE;
12458       continue;
12459     }
12460     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12461     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12462       if (gameMode == TwoMachinesPlay) {
12463         DisplayTwoMachinesTitle();
12464       } else {
12465         DisplayTitle("");
12466       }
12467       continue;
12468     }
12469     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12470     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12471     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12472     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12473     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12474     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12475     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12476     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12477     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12478     if (IntFeature(&p, "done", &val, cps)) {
12479       FeatureDone(cps, val);
12480       continue;
12481     }
12482     /* Added by Tord: */
12483     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12484     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12485     /* End of additions by Tord */
12486
12487     /* [HGM] added features: */
12488     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12489     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12490     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12491     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12492     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12493     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12494     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12495         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12496         if(cps->nrOptions >= MAX_OPTIONS) {
12497             cps->nrOptions--;
12498             sprintf(buf, "%s engine has too many options\n", cps->which);
12499             DisplayError(buf, 0);
12500         }
12501         continue;
12502     }
12503     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12504     /* End of additions by HGM */
12505
12506     /* unknown feature: complain and skip */
12507     q = p;
12508     while (*q && *q != '=') q++;
12509     sprintf(buf, "rejected %.*s\n", q-p, p);
12510     SendToProgram(buf, cps);
12511     p = q;
12512     if (*p == '=') {
12513       p++;
12514       if (*p == '\"') {
12515         p++;
12516         while (*p && *p != '\"') p++;
12517         if (*p == '\"') p++;
12518       } else {
12519         while (*p && *p != ' ') p++;
12520       }
12521     }
12522   }
12523
12524 }
12525
12526 void
12527 PeriodicUpdatesEvent(newState)
12528      int newState;
12529 {
12530     if (newState == appData.periodicUpdates)
12531       return;
12532
12533     appData.periodicUpdates=newState;
12534
12535     /* Display type changes, so update it now */
12536     DisplayAnalysis();
12537
12538     /* Get the ball rolling again... */
12539     if (newState) {
12540         AnalysisPeriodicEvent(1);
12541         StartAnalysisClock();
12542     }
12543 }
12544
12545 void
12546 PonderNextMoveEvent(newState)
12547      int newState;
12548 {
12549     if (newState == appData.ponderNextMove) return;
12550     if (gameMode == EditPosition) EditPositionDone();
12551     if (newState) {
12552         SendToProgram("hard\n", &first);
12553         if (gameMode == TwoMachinesPlay) {
12554             SendToProgram("hard\n", &second);
12555         }
12556     } else {
12557         SendToProgram("easy\n", &first);
12558         thinkOutput[0] = NULLCHAR;
12559         if (gameMode == TwoMachinesPlay) {
12560             SendToProgram("easy\n", &second);
12561         }
12562     }
12563     appData.ponderNextMove = newState;
12564 }
12565
12566 void
12567 NewSettingEvent(option, command, value)
12568      char *command;
12569      int option, value;
12570 {
12571     char buf[MSG_SIZ];
12572
12573     if (gameMode == EditPosition) EditPositionDone();
12574     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12575     SendToProgram(buf, &first);
12576     if (gameMode == TwoMachinesPlay) {
12577         SendToProgram(buf, &second);
12578     }
12579 }
12580
12581 void
12582 ShowThinkingEvent()
12583 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12584 {
12585     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12586     int newState = appData.showThinking
12587         // [HGM] thinking: other features now need thinking output as well
12588         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12589     
12590     if (oldState == newState) return;
12591     oldState = newState;
12592     if (gameMode == EditPosition) EditPositionDone();
12593     if (oldState) {
12594         SendToProgram("post\n", &first);
12595         if (gameMode == TwoMachinesPlay) {
12596             SendToProgram("post\n", &second);
12597         }
12598     } else {
12599         SendToProgram("nopost\n", &first);
12600         thinkOutput[0] = NULLCHAR;
12601         if (gameMode == TwoMachinesPlay) {
12602             SendToProgram("nopost\n", &second);
12603         }
12604     }
12605 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12606 }
12607
12608 void
12609 AskQuestionEvent(title, question, replyPrefix, which)
12610      char *title; char *question; char *replyPrefix; char *which;
12611 {
12612   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12613   if (pr == NoProc) return;
12614   AskQuestion(title, question, replyPrefix, pr);
12615 }
12616
12617 void
12618 DisplayMove(moveNumber)
12619      int moveNumber;
12620 {
12621     char message[MSG_SIZ];
12622     char res[MSG_SIZ];
12623     char cpThinkOutput[MSG_SIZ];
12624
12625     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12626     
12627     if (moveNumber == forwardMostMove - 1 || 
12628         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12629
12630         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12631
12632         if (strchr(cpThinkOutput, '\n')) {
12633             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12634         }
12635     } else {
12636         *cpThinkOutput = NULLCHAR;
12637     }
12638
12639     /* [AS] Hide thinking from human user */
12640     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12641         *cpThinkOutput = NULLCHAR;
12642         if( thinkOutput[0] != NULLCHAR ) {
12643             int i;
12644
12645             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12646                 cpThinkOutput[i] = '.';
12647             }
12648             cpThinkOutput[i] = NULLCHAR;
12649             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12650         }
12651     }
12652
12653     if (moveNumber == forwardMostMove - 1 &&
12654         gameInfo.resultDetails != NULL) {
12655         if (gameInfo.resultDetails[0] == NULLCHAR) {
12656             sprintf(res, " %s", PGNResult(gameInfo.result));
12657         } else {
12658             sprintf(res, " {%s} %s",
12659                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12660         }
12661     } else {
12662         res[0] = NULLCHAR;
12663     }
12664
12665     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12666         DisplayMessage(res, cpThinkOutput);
12667     } else {
12668         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12669                 WhiteOnMove(moveNumber) ? " " : ".. ",
12670                 parseList[moveNumber], res);
12671         DisplayMessage(message, cpThinkOutput);
12672     }
12673 }
12674
12675 void
12676 DisplayAnalysisText(text)
12677      char *text;
12678 {
12679     char buf[MSG_SIZ];
12680
12681     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12682                || appData.icsEngineAnalyze) {
12683         sprintf(buf, "Analysis (%s)", first.tidy);
12684         AnalysisPopUp(buf, text);
12685     }
12686 }
12687
12688 static int
12689 only_one_move(str)
12690      char *str;
12691 {
12692     while (*str && isspace(*str)) ++str;
12693     while (*str && !isspace(*str)) ++str;
12694     if (!*str) return 1;
12695     while (*str && isspace(*str)) ++str;
12696     if (!*str) return 1;
12697     return 0;
12698 }
12699
12700 void
12701 DisplayAnalysis()
12702 {
12703     char buf[MSG_SIZ];
12704     char lst[MSG_SIZ / 2];
12705     double nps;
12706     static char *xtra[] = { "", " (--)", " (++)" };
12707     int h, m, s, cs;
12708   
12709     if (programStats.time == 0) {
12710         programStats.time = 1;
12711     }
12712   
12713     if (programStats.got_only_move) {
12714         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12715     } else {
12716         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12717
12718         nps = (u64ToDouble(programStats.nodes) /
12719              ((double)programStats.time /100.0));
12720
12721         cs = programStats.time % 100;
12722         s = programStats.time / 100;
12723         h = (s / (60*60));
12724         s = s - h*60*60;
12725         m = (s/60);
12726         s = s - m*60;
12727
12728         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12729           if (programStats.move_name[0] != NULLCHAR) {
12730             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12731                     programStats.depth,
12732                     programStats.nr_moves-programStats.moves_left,
12733                     programStats.nr_moves, programStats.move_name,
12734                     ((float)programStats.score)/100.0, lst,
12735                     only_one_move(lst)?
12736                     xtra[programStats.got_fail] : "",
12737                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12738           } else {
12739             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12740                     programStats.depth,
12741                     programStats.nr_moves-programStats.moves_left,
12742                     programStats.nr_moves, ((float)programStats.score)/100.0,
12743                     lst,
12744                     only_one_move(lst)?
12745                     xtra[programStats.got_fail] : "",
12746                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12747           }
12748         } else {
12749             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12750                     programStats.depth,
12751                     ((float)programStats.score)/100.0,
12752                     lst,
12753                     only_one_move(lst)?
12754                     xtra[programStats.got_fail] : "",
12755                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12756         }
12757     }
12758     DisplayAnalysisText(buf);
12759 }
12760
12761 void
12762 DisplayComment(moveNumber, text)
12763      int moveNumber;
12764      char *text;
12765 {
12766     char title[MSG_SIZ];
12767     char buf[8000]; // comment can be long!
12768     int score, depth;
12769
12770     if( appData.autoDisplayComment ) {
12771         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12772             strcpy(title, "Comment");
12773         } else {
12774             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12775                     WhiteOnMove(moveNumber) ? " " : ".. ",
12776                     parseList[moveNumber]);
12777         }
12778         // [HGM] PV info: display PV info together with (or as) comment
12779         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12780             if(text == NULL) text = "";                                           
12781             score = pvInfoList[moveNumber].score;
12782             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12783                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12784             text = buf;
12785         }
12786     } else title[0] = 0;
12787
12788     if (text != NULL)
12789         CommentPopUp(title, text);
12790 }
12791
12792 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12793  * might be busy thinking or pondering.  It can be omitted if your
12794  * gnuchess is configured to stop thinking immediately on any user
12795  * input.  However, that gnuchess feature depends on the FIONREAD
12796  * ioctl, which does not work properly on some flavors of Unix.
12797  */
12798 void
12799 Attention(cps)
12800      ChessProgramState *cps;
12801 {
12802 #if ATTENTION
12803     if (!cps->useSigint) return;
12804     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12805     switch (gameMode) {
12806       case MachinePlaysWhite:
12807       case MachinePlaysBlack:
12808       case TwoMachinesPlay:
12809       case IcsPlayingWhite:
12810       case IcsPlayingBlack:
12811       case AnalyzeMode:
12812       case AnalyzeFile:
12813         /* Skip if we know it isn't thinking */
12814         if (!cps->maybeThinking) return;
12815         if (appData.debugMode)
12816           fprintf(debugFP, "Interrupting %s\n", cps->which);
12817         InterruptChildProcess(cps->pr);
12818         cps->maybeThinking = FALSE;
12819         break;
12820       default:
12821         break;
12822     }
12823 #endif /*ATTENTION*/
12824 }
12825
12826 int
12827 CheckFlags()
12828 {
12829     if (whiteTimeRemaining <= 0) {
12830         if (!whiteFlag) {
12831             whiteFlag = TRUE;
12832             if (appData.icsActive) {
12833                 if (appData.autoCallFlag &&
12834                     gameMode == IcsPlayingBlack && !blackFlag) {
12835                   SendToICS(ics_prefix);
12836                   SendToICS("flag\n");
12837                 }
12838             } else {
12839                 if (blackFlag) {
12840                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12841                 } else {
12842                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12843                     if (appData.autoCallFlag) {
12844                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12845                         return TRUE;
12846                     }
12847                 }
12848             }
12849         }
12850     }
12851     if (blackTimeRemaining <= 0) {
12852         if (!blackFlag) {
12853             blackFlag = TRUE;
12854             if (appData.icsActive) {
12855                 if (appData.autoCallFlag &&
12856                     gameMode == IcsPlayingWhite && !whiteFlag) {
12857                   SendToICS(ics_prefix);
12858                   SendToICS("flag\n");
12859                 }
12860             } else {
12861                 if (whiteFlag) {
12862                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12863                 } else {
12864                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12865                     if (appData.autoCallFlag) {
12866                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12867                         return TRUE;
12868                     }
12869                 }
12870             }
12871         }
12872     }
12873     return FALSE;
12874 }
12875
12876 void
12877 CheckTimeControl()
12878 {
12879     if (!appData.clockMode || appData.icsActive ||
12880         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12881
12882     /*
12883      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12884      */
12885     if ( !WhiteOnMove(forwardMostMove) )
12886         /* White made time control */
12887         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12888         /* [HGM] time odds: correct new time quota for time odds! */
12889                                             / WhitePlayer()->timeOdds;
12890       else
12891         /* Black made time control */
12892         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12893                                             / WhitePlayer()->other->timeOdds;
12894 }
12895
12896 void
12897 DisplayBothClocks()
12898 {
12899     int wom = gameMode == EditPosition ?
12900       !blackPlaysFirst : WhiteOnMove(currentMove);
12901     DisplayWhiteClock(whiteTimeRemaining, wom);
12902     DisplayBlackClock(blackTimeRemaining, !wom);
12903 }
12904
12905
12906 /* Timekeeping seems to be a portability nightmare.  I think everyone
12907    has ftime(), but I'm really not sure, so I'm including some ifdefs
12908    to use other calls if you don't.  Clocks will be less accurate if
12909    you have neither ftime nor gettimeofday.
12910 */
12911
12912 /* VS 2008 requires the #include outside of the function */
12913 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12914 #include <sys/timeb.h>
12915 #endif
12916
12917 /* Get the current time as a TimeMark */
12918 void
12919 GetTimeMark(tm)
12920      TimeMark *tm;
12921 {
12922 #if HAVE_GETTIMEOFDAY
12923
12924     struct timeval timeVal;
12925     struct timezone timeZone;
12926
12927     gettimeofday(&timeVal, &timeZone);
12928     tm->sec = (long) timeVal.tv_sec; 
12929     tm->ms = (int) (timeVal.tv_usec / 1000L);
12930
12931 #else /*!HAVE_GETTIMEOFDAY*/
12932 #if HAVE_FTIME
12933
12934 // include <sys/timeb.h> / moved to just above start of function
12935     struct timeb timeB;
12936
12937     ftime(&timeB);
12938     tm->sec = (long) timeB.time;
12939     tm->ms = (int) timeB.millitm;
12940
12941 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12942     tm->sec = (long) time(NULL);
12943     tm->ms = 0;
12944 #endif
12945 #endif
12946 }
12947
12948 /* Return the difference in milliseconds between two
12949    time marks.  We assume the difference will fit in a long!
12950 */
12951 long
12952 SubtractTimeMarks(tm2, tm1)
12953      TimeMark *tm2, *tm1;
12954 {
12955     return 1000L*(tm2->sec - tm1->sec) +
12956            (long) (tm2->ms - tm1->ms);
12957 }
12958
12959
12960 /*
12961  * Code to manage the game clocks.
12962  *
12963  * In tournament play, black starts the clock and then white makes a move.
12964  * We give the human user a slight advantage if he is playing white---the
12965  * clocks don't run until he makes his first move, so it takes zero time.
12966  * Also, we don't account for network lag, so we could get out of sync
12967  * with GNU Chess's clock -- but then, referees are always right.  
12968  */
12969
12970 static TimeMark tickStartTM;
12971 static long intendedTickLength;
12972
12973 long
12974 NextTickLength(timeRemaining)
12975      long timeRemaining;
12976 {
12977     long nominalTickLength, nextTickLength;
12978
12979     if (timeRemaining > 0L && timeRemaining <= 10000L)
12980       nominalTickLength = 100L;
12981     else
12982       nominalTickLength = 1000L;
12983     nextTickLength = timeRemaining % nominalTickLength;
12984     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12985
12986     return nextTickLength;
12987 }
12988
12989 /* Adjust clock one minute up or down */
12990 void
12991 AdjustClock(Boolean which, int dir)
12992 {
12993     if(which) blackTimeRemaining += 60000*dir;
12994     else      whiteTimeRemaining += 60000*dir;
12995     DisplayBothClocks();
12996 }
12997
12998 /* Stop clocks and reset to a fresh time control */
12999 void
13000 ResetClocks() 
13001 {
13002     (void) StopClockTimer();
13003     if (appData.icsActive) {
13004         whiteTimeRemaining = blackTimeRemaining = 0;
13005     } else { /* [HGM] correct new time quote for time odds */
13006         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13007         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13008     }
13009     if (whiteFlag || blackFlag) {
13010         DisplayTitle("");
13011         whiteFlag = blackFlag = FALSE;
13012     }
13013     DisplayBothClocks();
13014 }
13015
13016 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13017
13018 /* Decrement running clock by amount of time that has passed */
13019 void
13020 DecrementClocks()
13021 {
13022     long timeRemaining;
13023     long lastTickLength, fudge;
13024     TimeMark now;
13025
13026     if (!appData.clockMode) return;
13027     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13028         
13029     GetTimeMark(&now);
13030
13031     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13032
13033     /* Fudge if we woke up a little too soon */
13034     fudge = intendedTickLength - lastTickLength;
13035     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13036
13037     if (WhiteOnMove(forwardMostMove)) {
13038         if(whiteNPS >= 0) lastTickLength = 0;
13039         timeRemaining = whiteTimeRemaining -= lastTickLength;
13040         DisplayWhiteClock(whiteTimeRemaining - fudge,
13041                           WhiteOnMove(currentMove));
13042     } else {
13043         if(blackNPS >= 0) lastTickLength = 0;
13044         timeRemaining = blackTimeRemaining -= lastTickLength;
13045         DisplayBlackClock(blackTimeRemaining - fudge,
13046                           !WhiteOnMove(currentMove));
13047     }
13048
13049     if (CheckFlags()) return;
13050         
13051     tickStartTM = now;
13052     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13053     StartClockTimer(intendedTickLength);
13054
13055     /* if the time remaining has fallen below the alarm threshold, sound the
13056      * alarm. if the alarm has sounded and (due to a takeback or time control
13057      * with increment) the time remaining has increased to a level above the
13058      * threshold, reset the alarm so it can sound again. 
13059      */
13060     
13061     if (appData.icsActive && appData.icsAlarm) {
13062
13063         /* make sure we are dealing with the user's clock */
13064         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13065                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13066            )) return;
13067
13068         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13069             alarmSounded = FALSE;
13070         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13071             PlayAlarmSound();
13072             alarmSounded = TRUE;
13073         }
13074     }
13075 }
13076
13077
13078 /* A player has just moved, so stop the previously running
13079    clock and (if in clock mode) start the other one.
13080    We redisplay both clocks in case we're in ICS mode, because
13081    ICS gives us an update to both clocks after every move.
13082    Note that this routine is called *after* forwardMostMove
13083    is updated, so the last fractional tick must be subtracted
13084    from the color that is *not* on move now.
13085 */
13086 void
13087 SwitchClocks()
13088 {
13089     long lastTickLength;
13090     TimeMark now;
13091     int flagged = FALSE;
13092
13093     GetTimeMark(&now);
13094
13095     if (StopClockTimer() && appData.clockMode) {
13096         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13097         if (WhiteOnMove(forwardMostMove)) {
13098             if(blackNPS >= 0) lastTickLength = 0;
13099             blackTimeRemaining -= lastTickLength;
13100            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13101 //         if(pvInfoList[forwardMostMove-1].time == -1)
13102                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13103                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13104         } else {
13105            if(whiteNPS >= 0) lastTickLength = 0;
13106            whiteTimeRemaining -= lastTickLength;
13107            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13108 //         if(pvInfoList[forwardMostMove-1].time == -1)
13109                  pvInfoList[forwardMostMove-1].time = 
13110                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13111         }
13112         flagged = CheckFlags();
13113     }
13114     CheckTimeControl();
13115
13116     if (flagged || !appData.clockMode) return;
13117
13118     switch (gameMode) {
13119       case MachinePlaysBlack:
13120       case MachinePlaysWhite:
13121       case BeginningOfGame:
13122         if (pausing) return;
13123         break;
13124
13125       case EditGame:
13126       case PlayFromGameFile:
13127       case IcsExamining:
13128         return;
13129
13130       default:
13131         break;
13132     }
13133
13134     tickStartTM = now;
13135     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13136       whiteTimeRemaining : blackTimeRemaining);
13137     StartClockTimer(intendedTickLength);
13138 }
13139         
13140
13141 /* Stop both clocks */
13142 void
13143 StopClocks()
13144 {       
13145     long lastTickLength;
13146     TimeMark now;
13147
13148     if (!StopClockTimer()) return;
13149     if (!appData.clockMode) return;
13150
13151     GetTimeMark(&now);
13152
13153     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13154     if (WhiteOnMove(forwardMostMove)) {
13155         if(whiteNPS >= 0) lastTickLength = 0;
13156         whiteTimeRemaining -= lastTickLength;
13157         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13158     } else {
13159         if(blackNPS >= 0) lastTickLength = 0;
13160         blackTimeRemaining -= lastTickLength;
13161         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13162     }
13163     CheckFlags();
13164 }
13165         
13166 /* Start clock of player on move.  Time may have been reset, so
13167    if clock is already running, stop and restart it. */
13168 void
13169 StartClocks()
13170 {
13171     (void) StopClockTimer(); /* in case it was running already */
13172     DisplayBothClocks();
13173     if (CheckFlags()) return;
13174
13175     if (!appData.clockMode) return;
13176     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13177
13178     GetTimeMark(&tickStartTM);
13179     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13180       whiteTimeRemaining : blackTimeRemaining);
13181
13182    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13183     whiteNPS = blackNPS = -1; 
13184     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13185        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13186         whiteNPS = first.nps;
13187     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13188        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13189         blackNPS = first.nps;
13190     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13191         whiteNPS = second.nps;
13192     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13193         blackNPS = second.nps;
13194     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13195
13196     StartClockTimer(intendedTickLength);
13197 }
13198
13199 char *
13200 TimeString(ms)
13201      long ms;
13202 {
13203     long second, minute, hour, day;
13204     char *sign = "";
13205     static char buf[32];
13206     
13207     if (ms > 0 && ms <= 9900) {
13208       /* convert milliseconds to tenths, rounding up */
13209       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13210
13211       sprintf(buf, " %03.1f ", tenths/10.0);
13212       return buf;
13213     }
13214
13215     /* convert milliseconds to seconds, rounding up */
13216     /* use floating point to avoid strangeness of integer division
13217        with negative dividends on many machines */
13218     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13219
13220     if (second < 0) {
13221         sign = "-";
13222         second = -second;
13223     }
13224     
13225     day = second / (60 * 60 * 24);
13226     second = second % (60 * 60 * 24);
13227     hour = second / (60 * 60);
13228     second = second % (60 * 60);
13229     minute = second / 60;
13230     second = second % 60;
13231     
13232     if (day > 0)
13233       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13234               sign, day, hour, minute, second);
13235     else if (hour > 0)
13236       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13237     else
13238       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13239     
13240     return buf;
13241 }
13242
13243
13244 /*
13245  * This is necessary because some C libraries aren't ANSI C compliant yet.
13246  */
13247 char *
13248 StrStr(string, match)
13249      char *string, *match;
13250 {
13251     int i, length;
13252     
13253     length = strlen(match);
13254     
13255     for (i = strlen(string) - length; i >= 0; i--, string++)
13256       if (!strncmp(match, string, length))
13257         return string;
13258     
13259     return NULL;
13260 }
13261
13262 char *
13263 StrCaseStr(string, match)
13264      char *string, *match;
13265 {
13266     int i, j, length;
13267     
13268     length = strlen(match);
13269     
13270     for (i = strlen(string) - length; i >= 0; i--, string++) {
13271         for (j = 0; j < length; j++) {
13272             if (ToLower(match[j]) != ToLower(string[j]))
13273               break;
13274         }
13275         if (j == length) return string;
13276     }
13277
13278     return NULL;
13279 }
13280
13281 #ifndef _amigados
13282 int
13283 StrCaseCmp(s1, s2)
13284      char *s1, *s2;
13285 {
13286     char c1, c2;
13287     
13288     for (;;) {
13289         c1 = ToLower(*s1++);
13290         c2 = ToLower(*s2++);
13291         if (c1 > c2) return 1;
13292         if (c1 < c2) return -1;
13293         if (c1 == NULLCHAR) return 0;
13294     }
13295 }
13296
13297
13298 int
13299 ToLower(c)
13300      int c;
13301 {
13302     return isupper(c) ? tolower(c) : c;
13303 }
13304
13305
13306 int
13307 ToUpper(c)
13308      int c;
13309 {
13310     return islower(c) ? toupper(c) : c;
13311 }
13312 #endif /* !_amigados    */
13313
13314 char *
13315 StrSave(s)
13316      char *s;
13317 {
13318     char *ret;
13319
13320     if ((ret = (char *) malloc(strlen(s) + 1))) {
13321         strcpy(ret, s);
13322     }
13323     return ret;
13324 }
13325
13326 char *
13327 StrSavePtr(s, savePtr)
13328      char *s, **savePtr;
13329 {
13330     if (*savePtr) {
13331         free(*savePtr);
13332     }
13333     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13334         strcpy(*savePtr, s);
13335     }
13336     return(*savePtr);
13337 }
13338
13339 char *
13340 PGNDate()
13341 {
13342     time_t clock;
13343     struct tm *tm;
13344     char buf[MSG_SIZ];
13345
13346     clock = time((time_t *)NULL);
13347     tm = localtime(&clock);
13348     sprintf(buf, "%04d.%02d.%02d",
13349             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13350     return StrSave(buf);
13351 }
13352
13353
13354 char *
13355 PositionToFEN(move, overrideCastling)
13356      int move;
13357      char *overrideCastling;
13358 {
13359     int i, j, fromX, fromY, toX, toY;
13360     int whiteToPlay;
13361     char buf[128];
13362     char *p, *q;
13363     int emptycount;
13364     ChessSquare piece;
13365
13366     whiteToPlay = (gameMode == EditPosition) ?
13367       !blackPlaysFirst : (move % 2 == 0);
13368     p = buf;
13369
13370     /* Piece placement data */
13371     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13372         emptycount = 0;
13373         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13374             if (boards[move][i][j] == EmptySquare) {
13375                 emptycount++;
13376             } else { ChessSquare piece = boards[move][i][j];
13377                 if (emptycount > 0) {
13378                     if(emptycount<10) /* [HGM] can be >= 10 */
13379                         *p++ = '0' + emptycount;
13380                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13381                     emptycount = 0;
13382                 }
13383                 if(PieceToChar(piece) == '+') {
13384                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13385                     *p++ = '+';
13386                     piece = (ChessSquare)(DEMOTED piece);
13387                 } 
13388                 *p++ = PieceToChar(piece);
13389                 if(p[-1] == '~') {
13390                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13391                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13392                     *p++ = '~';
13393                 }
13394             }
13395         }
13396         if (emptycount > 0) {
13397             if(emptycount<10) /* [HGM] can be >= 10 */
13398                 *p++ = '0' + emptycount;
13399             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13400             emptycount = 0;
13401         }
13402         *p++ = '/';
13403     }
13404     *(p - 1) = ' ';
13405
13406     /* [HGM] print Crazyhouse or Shogi holdings */
13407     if( gameInfo.holdingsWidth ) {
13408         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13409         q = p;
13410         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13411             piece = boards[move][i][BOARD_WIDTH-1];
13412             if( piece != EmptySquare )
13413               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13414                   *p++ = PieceToChar(piece);
13415         }
13416         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13417             piece = boards[move][BOARD_HEIGHT-i-1][0];
13418             if( piece != EmptySquare )
13419               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13420                   *p++ = PieceToChar(piece);
13421         }
13422
13423         if( q == p ) *p++ = '-';
13424         *p++ = ']';
13425         *p++ = ' ';
13426     }
13427
13428     /* Active color */
13429     *p++ = whiteToPlay ? 'w' : 'b';
13430     *p++ = ' ';
13431
13432   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13433     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13434   } else {
13435   if(nrCastlingRights) {
13436      q = p;
13437      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13438        /* [HGM] write directly from rights */
13439            if(castlingRights[move][2] >= 0 &&
13440               castlingRights[move][0] >= 0   )
13441                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13442            if(castlingRights[move][2] >= 0 &&
13443               castlingRights[move][1] >= 0   )
13444                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13445            if(castlingRights[move][5] >= 0 &&
13446               castlingRights[move][3] >= 0   )
13447                 *p++ = castlingRights[move][3] + AAA;
13448            if(castlingRights[move][5] >= 0 &&
13449               castlingRights[move][4] >= 0   )
13450                 *p++ = castlingRights[move][4] + AAA;
13451      } else {
13452
13453         /* [HGM] write true castling rights */
13454         if( nrCastlingRights == 6 ) {
13455             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13456                castlingRights[move][2] >= 0  ) *p++ = 'K';
13457             if(castlingRights[move][1] == BOARD_LEFT &&
13458                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13459             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13460                castlingRights[move][5] >= 0  ) *p++ = 'k';
13461             if(castlingRights[move][4] == BOARD_LEFT &&
13462                castlingRights[move][5] >= 0  ) *p++ = 'q';
13463         }
13464      }
13465      if (q == p) *p++ = '-'; /* No castling rights */
13466      *p++ = ' ';
13467   }
13468
13469   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13470      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13471     /* En passant target square */
13472     if (move > backwardMostMove) {
13473         fromX = moveList[move - 1][0] - AAA;
13474         fromY = moveList[move - 1][1] - ONE;
13475         toX = moveList[move - 1][2] - AAA;
13476         toY = moveList[move - 1][3] - ONE;
13477         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13478             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13479             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13480             fromX == toX) {
13481             /* 2-square pawn move just happened */
13482             *p++ = toX + AAA;
13483             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13484         } else {
13485             *p++ = '-';
13486         }
13487     } else {
13488         *p++ = '-';
13489     }
13490     *p++ = ' ';
13491   }
13492   }
13493
13494     /* [HGM] find reversible plies */
13495     {   int i = 0, j=move;
13496
13497         if (appData.debugMode) { int k;
13498             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13499             for(k=backwardMostMove; k<=forwardMostMove; k++)
13500                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13501
13502         }
13503
13504         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13505         if( j == backwardMostMove ) i += initialRulePlies;
13506         sprintf(p, "%d ", i);
13507         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13508     }
13509     /* Fullmove number */
13510     sprintf(p, "%d", (move / 2) + 1);
13511     
13512     return StrSave(buf);
13513 }
13514
13515 Boolean
13516 ParseFEN(board, blackPlaysFirst, fen)
13517     Board board;
13518      int *blackPlaysFirst;
13519      char *fen;
13520 {
13521     int i, j;
13522     char *p;
13523     int emptycount;
13524     ChessSquare piece;
13525
13526     p = fen;
13527
13528     /* [HGM] by default clear Crazyhouse holdings, if present */
13529     if(gameInfo.holdingsWidth) {
13530        for(i=0; i<BOARD_HEIGHT; i++) {
13531            board[i][0]             = EmptySquare; /* black holdings */
13532            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13533            board[i][1]             = (ChessSquare) 0; /* black counts */
13534            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13535        }
13536     }
13537
13538     /* Piece placement data */
13539     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13540         j = 0;
13541         for (;;) {
13542             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13543                 if (*p == '/') p++;
13544                 emptycount = gameInfo.boardWidth - j;
13545                 while (emptycount--)
13546                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13547                 break;
13548 #if(BOARD_SIZE >= 10)
13549             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13550                 p++; emptycount=10;
13551                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13552                 while (emptycount--)
13553                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13554 #endif
13555             } else if (isdigit(*p)) {
13556                 emptycount = *p++ - '0';
13557                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13558                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13559                 while (emptycount--)
13560                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13561             } else if (*p == '+' || isalpha(*p)) {
13562                 if (j >= gameInfo.boardWidth) return FALSE;
13563                 if(*p=='+') {
13564                     piece = CharToPiece(*++p);
13565                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13566                     piece = (ChessSquare) (PROMOTED piece ); p++;
13567                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13568                 } else piece = CharToPiece(*p++);
13569
13570                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13571                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13572                     piece = (ChessSquare) (PROMOTED piece);
13573                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13574                     p++;
13575                 }
13576                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13577             } else {
13578                 return FALSE;
13579             }
13580         }
13581     }
13582     while (*p == '/' || *p == ' ') p++;
13583
13584     /* [HGM] look for Crazyhouse holdings here */
13585     while(*p==' ') p++;
13586     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13587         if(*p == '[') p++;
13588         if(*p == '-' ) *p++; /* empty holdings */ else {
13589             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13590             /* if we would allow FEN reading to set board size, we would   */
13591             /* have to add holdings and shift the board read so far here   */
13592             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13593                 *p++;
13594                 if((int) piece >= (int) BlackPawn ) {
13595                     i = (int)piece - (int)BlackPawn;
13596                     i = PieceToNumber((ChessSquare)i);
13597                     if( i >= gameInfo.holdingsSize ) return FALSE;
13598                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13599                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13600                 } else {
13601                     i = (int)piece - (int)WhitePawn;
13602                     i = PieceToNumber((ChessSquare)i);
13603                     if( i >= gameInfo.holdingsSize ) return FALSE;
13604                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13605                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13606                 }
13607             }
13608         }
13609         if(*p == ']') *p++;
13610     }
13611
13612     while(*p == ' ') p++;
13613
13614     /* Active color */
13615     switch (*p++) {
13616       case 'w':
13617         *blackPlaysFirst = FALSE;
13618         break;
13619       case 'b': 
13620         *blackPlaysFirst = TRUE;
13621         break;
13622       default:
13623         return FALSE;
13624     }
13625
13626     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13627     /* return the extra info in global variiables             */
13628
13629     /* set defaults in case FEN is incomplete */
13630     FENepStatus = EP_UNKNOWN;
13631     for(i=0; i<nrCastlingRights; i++ ) {
13632         FENcastlingRights[i] =
13633             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13634     }   /* assume possible unless obviously impossible */
13635     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13636     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13637     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13638     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13639     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13640     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13641     FENrulePlies = 0;
13642
13643     while(*p==' ') p++;
13644     if(nrCastlingRights) {
13645       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13646           /* castling indicator present, so default becomes no castlings */
13647           for(i=0; i<nrCastlingRights; i++ ) {
13648                  FENcastlingRights[i] = -1;
13649           }
13650       }
13651       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13652              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13653              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13654              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13655         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13656
13657         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13658             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13659             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13660         }
13661         switch(c) {
13662           case'K':
13663               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13664               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13665               FENcastlingRights[2] = whiteKingFile;
13666               break;
13667           case'Q':
13668               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13669               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13670               FENcastlingRights[2] = whiteKingFile;
13671               break;
13672           case'k':
13673               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13674               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13675               FENcastlingRights[5] = blackKingFile;
13676               break;
13677           case'q':
13678               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13679               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13680               FENcastlingRights[5] = blackKingFile;
13681           case '-':
13682               break;
13683           default: /* FRC castlings */
13684               if(c >= 'a') { /* black rights */
13685                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13686                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13687                   if(i == BOARD_RGHT) break;
13688                   FENcastlingRights[5] = i;
13689                   c -= AAA;
13690                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13691                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13692                   if(c > i)
13693                       FENcastlingRights[3] = c;
13694                   else
13695                       FENcastlingRights[4] = c;
13696               } else { /* white rights */
13697                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13698                     if(board[0][i] == WhiteKing) break;
13699                   if(i == BOARD_RGHT) break;
13700                   FENcastlingRights[2] = i;
13701                   c -= AAA - 'a' + 'A';
13702                   if(board[0][c] >= WhiteKing) break;
13703                   if(c > i)
13704                       FENcastlingRights[0] = c;
13705                   else
13706                       FENcastlingRights[1] = c;
13707               }
13708         }
13709       }
13710     if (appData.debugMode) {
13711         fprintf(debugFP, "FEN castling rights:");
13712         for(i=0; i<nrCastlingRights; i++)
13713         fprintf(debugFP, " %d", FENcastlingRights[i]);
13714         fprintf(debugFP, "\n");
13715     }
13716
13717       while(*p==' ') p++;
13718     }
13719
13720     /* read e.p. field in games that know e.p. capture */
13721     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13722        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13723       if(*p=='-') {
13724         p++; FENepStatus = EP_NONE;
13725       } else {
13726          char c = *p++ - AAA;
13727
13728          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13729          if(*p >= '0' && *p <='9') *p++;
13730          FENepStatus = c;
13731       }
13732     }
13733
13734
13735     if(sscanf(p, "%d", &i) == 1) {
13736         FENrulePlies = i; /* 50-move ply counter */
13737         /* (The move number is still ignored)    */
13738     }
13739
13740     return TRUE;
13741 }
13742       
13743 void
13744 EditPositionPasteFEN(char *fen)
13745 {
13746   if (fen != NULL) {
13747     Board initial_position;
13748
13749     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13750       DisplayError(_("Bad FEN position in clipboard"), 0);
13751       return ;
13752     } else {
13753       int savedBlackPlaysFirst = blackPlaysFirst;
13754       EditPositionEvent();
13755       blackPlaysFirst = savedBlackPlaysFirst;
13756       CopyBoard(boards[0], initial_position);
13757           /* [HGM] copy FEN attributes as well */
13758           {   int i;
13759               initialRulePlies = FENrulePlies;
13760               epStatus[0] = FENepStatus;
13761               for( i=0; i<nrCastlingRights; i++ )
13762                   castlingRights[0][i] = FENcastlingRights[i];
13763           }
13764       EditPositionDone();
13765       DisplayBothClocks();
13766       DrawPosition(FALSE, boards[currentMove]);
13767     }
13768   }
13769 }