removed all my printf's for debugging
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
234
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
240 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245
246 /* States for ics_getting_history */
247 #define H_FALSE 0
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
253
254 /* whosays values for GameEnds */
255 #define GE_ICS 0
256 #define GE_ENGINE 1
257 #define GE_PLAYER 2
258 #define GE_FILE 3
259 #define GE_XBOARD 4
260 #define GE_ENGINE1 5
261 #define GE_ENGINE2 6
262
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
265
266 /* Different types of move when calling RegisterMove */
267 #define CMAIL_MOVE   0
268 #define CMAIL_RESIGN 1
269 #define CMAIL_DRAW   2
270 #define CMAIL_ACCEPT 3
271
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
276
277 /* Telnet protocol constants */
278 #define TN_WILL 0373
279 #define TN_WONT 0374
280 #define TN_DO   0375
281 #define TN_DONT 0376
282 #define TN_IAC  0377
283 #define TN_ECHO 0001
284 #define TN_SGA  0003
285 #define TN_PORT 23
286
287 /* [AS] */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
289 {
290     assert( dst != NULL );
291     assert( src != NULL );
292     assert( count > 0 );
293
294     strncpy( dst, src, count );
295     dst[ count-1 ] = '\0';
296     return dst;
297 }
298
299 #if 0
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
302 {
303     size_t  dst_len;
304
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     dst_len = strlen(dst);
310
311     assert( count > dst_len ); /* Buffer size must be greater than current length */
312
313     safeStrCpy( dst + dst_len, src, count - dst_len );
314
315     return dst;
316 }
317 #endif
318
319 /* Some compiler can't cast u64 to double
320  * This function do the job for us:
321
322  * We use the highest bit for cast, this only
323  * works if the highest bit is not
324  * in use (This should not happen)
325  *
326  * We used this for all compiler
327  */
328 double
329 u64ToDouble(u64 value)
330 {
331   double r;
332   u64 tmp = value & u64Const(0x7fffffffffffffff);
333   r = (double)(s64)tmp;
334   if (value & u64Const(0x8000000000000000))
335        r +=  9.2233720368547758080e18; /* 2^63 */
336  return r;
337 }
338
339 /* Fake up flags for now, as we aren't keeping track of castling
340    availability yet. [HGM] Change of logic: the flag now only
341    indicates the type of castlings allowed by the rule of the game.
342    The actual rights themselves are maintained in the array
343    castlingRights, as part of the game history, and are not probed
344    by this function.
345  */
346 int
347 PosFlags(index)
348 {
349   int flags = F_ALL_CASTLE_OK;
350   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351   switch (gameInfo.variant) {
352   case VariantSuicide:
353     flags &= ~F_ALL_CASTLE_OK;
354   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355     flags |= F_IGNORE_CHECK;
356   case VariantLosers:
357     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358     break;
359   case VariantAtomic:
360     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361     break;
362   case VariantKriegspiel:
363     flags |= F_KRIEGSPIEL_CAPTURE;
364     break;
365   case VariantCapaRandom: 
366   case VariantFischeRandom:
367     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368   case VariantNoCastle:
369   case VariantShatranj:
370   case VariantCourier:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /* 
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char  epStatus[MAX_MOVES];
460 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int   initialRulePlies, FENrulePlies;
465 char  FENepStatus;
466 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 int loadFlag = 0; 
468 int shuffleOpenings;
469
470 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_SIZE>=10)
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
570
571 #if (BOARD_SIZE>=12)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<MAX_MOVES; i++ ) {
662             pvInfoList[i].depth = -1;
663             epStatus[i]=EP_NONE;
664             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { int norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815     
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825     
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830 #if 0
831         char *p, *q;
832         q = first.program;
833         while (*q != ' ' && *q != NULLCHAR) q++;
834         p = q;
835         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837         sprintf(programVersion, "%s + ", PACKAGE_STRING);
838         strncat(programVersion, p, q - p);
839 #else
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 #endif
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantBerolina:   /* might work if TestLegality is off */
902       case VariantCapaRandom: /* should work */
903       case VariantJanus:      /* should work */
904       case VariantSuper:      /* experimental */
905       case VariantGreat:      /* experimental, requires legality testing to be off */
906         break;
907       }
908     }
909
910     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
911     InitEngineUCI( installDir, &second );
912 }
913
914 int NextIntegerFromString( char ** str, long * value )
915 {
916     int result = -1;
917     char * s = *str;
918
919     while( *s == ' ' || *s == '\t' ) {
920         s++;
921     }
922
923     *value = 0;
924
925     if( *s >= '0' && *s <= '9' ) {
926         while( *s >= '0' && *s <= '9' ) {
927             *value = *value * 10 + (*s - '0');
928             s++;
929         }
930
931         result = 0;
932     }
933
934     *str = s;
935
936     return result;
937 }
938
939 int NextTimeControlFromString( char ** str, long * value )
940 {
941     long temp;
942     int result = NextIntegerFromString( str, &temp );
943
944     if( result == 0 ) {
945         *value = temp * 60; /* Minutes */
946         if( **str == ':' ) {
947             (*str)++;
948             result = NextIntegerFromString( str, &temp );
949             *value += temp; /* Seconds */
950         }
951     }
952
953     return result;
954 }
955
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
958     int result = -1; long temp, temp2;
959
960     if(**str != '+') return -1; // old params remain in force!
961     (*str)++;
962     if( NextTimeControlFromString( str, &temp ) ) return -1;
963
964     if(**str != '/') {
965         /* time only: incremental or sudden-death time control */
966         if(**str == '+') { /* increment follows; read it */
967             (*str)++;
968             if(result = NextIntegerFromString( str, &temp2)) return -1;
969             *inc = temp2 * 1000;
970         } else *inc = 0;
971         *moves = 0; *tc = temp * 1000; 
972         return 0;
973     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
974
975     (*str)++; /* classical time control */
976     result = NextTimeControlFromString( str, &temp2);
977     if(result == 0) {
978         *moves = temp/60;
979         *tc    = temp2 * 1000;
980         *inc   = 0;
981     }
982     return result;
983 }
984
985 int GetTimeQuota(int movenr)
986 {   /* [HGM] get time to add from the multi-session time-control string */
987     int moves=1; /* kludge to force reading of first session */
988     long time, increment;
989     char *s = fullTimeControlString;
990
991     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992     do {
993         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995         if(movenr == -1) return time;    /* last move before new session     */
996         if(!moves) return increment;     /* current session is incremental   */
997         if(movenr >= 0) movenr -= moves; /* we already finished this session */
998     } while(movenr >= -1);               /* try again for next session       */
999
1000     return 0; // no new time quota on this move
1001 }
1002
1003 int
1004 ParseTimeControl(tc, ti, mps)
1005      char *tc;
1006      int ti;
1007      int mps;
1008 {
1009 #if 0
1010     int matched, min, sec;
1011
1012     matched = sscanf(tc, "%d:%d", &min, &sec);
1013     if (matched == 1) {
1014         timeControl = min * 60 * 1000;
1015     } else if (matched == 2) {
1016         timeControl = (min * 60 + sec) * 1000;
1017     } else {
1018         return FALSE;
1019     }
1020 #else
1021     long tc1;
1022     long tc2;
1023     char buf[MSG_SIZ];
1024
1025     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1026     if(ti > 0) {
1027         if(mps)
1028              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029         else sprintf(buf, "+%s+%d", tc, ti);
1030     } else {
1031         if(mps)
1032              sprintf(buf, "+%d/%s", mps, tc);
1033         else sprintf(buf, "+%s", tc);
1034     }
1035     fullTimeControlString = StrSave(buf);
1036
1037     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038         return FALSE;
1039     }
1040
1041     if( *tc == '/' ) {
1042         /* Parse second time control */
1043         tc++;
1044
1045         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1046             return FALSE;
1047         }
1048
1049         if( tc2 == 0 ) {
1050             return FALSE;
1051         }
1052
1053         timeControl_2 = tc2 * 1000;
1054     }
1055     else {
1056         timeControl_2 = 0;
1057     }
1058
1059     if( tc1 == 0 ) {
1060         return FALSE;
1061     }
1062
1063     timeControl = tc1 * 1000;
1064 #endif
1065
1066     if (ti >= 0) {
1067         timeIncrement = ti * 1000;  /* convert to ms */
1068         movesPerSession = 0;
1069     } else {
1070         timeIncrement = 0;
1071         movesPerSession = mps;
1072     }
1073     return TRUE;
1074 }
1075
1076 void
1077 InitBackEnd2()
1078 {
1079   if (appData.debugMode) {
1080     fprintf(debugFP, "%s\n", programVersion);
1081   }
1082   
1083   if (appData.matchGames > 0) {
1084     appData.matchMode = TRUE;
1085   } else if (appData.matchMode) {
1086     appData.matchGames = 1;
1087   }
1088   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089     appData.matchGames = appData.sameColorGames;
1090   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1093   }
1094   Reset(TRUE, FALSE);
1095   if (appData.noChessProgram || first.protocolVersion == 1) {
1096     InitBackEnd3();
1097   } else {
1098     /* kludge: allow timeout for initial "feature" commands */
1099     FreezeUI();
1100     DisplayMessage("", _("Starting chess program"));
1101     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102   }
1103 }
1104
1105 void
1106 InitBackEnd3 P((void))
1107 {
1108     GameMode initialMode;
1109     char buf[MSG_SIZ];
1110     int err;
1111
1112     InitChessProgram(&first, startedFromSetupPosition);
1113
1114
1115     if (appData.icsActive) {
1116 #ifdef WIN32
1117         /* [DM] Make a console window if needed [HGM] merged ifs */
1118         ConsoleCreate(); 
1119 #endif
1120         err = establish();
1121         if (err != 0) {
1122             if (*appData.icsCommPort != NULLCHAR) {
1123                 sprintf(buf, _("Could not open comm port %s"),  
1124                         appData.icsCommPort);
1125             } else {
1126                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1127                         appData.icsHost, appData.icsPort);
1128             }
1129             DisplayFatalError(buf, err, 1);
1130             return;
1131         }
1132         SetICSMode();
1133         telnetISR =
1134           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1135         fromUserISR =
1136           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137     } else if (appData.noChessProgram) {
1138         SetNCPMode();
1139     } else {
1140         SetGNUMode();
1141     }
1142
1143     if (*appData.cmailGameName != NULLCHAR) {
1144         SetCmailMode();
1145         OpenLoopback(&cmailPR);
1146         cmailISR =
1147           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149     
1150     ThawUI();
1151     DisplayMessage("", "");
1152     if (StrCaseCmp(appData.initialMode, "") == 0) {
1153       initialMode = BeginningOfGame;
1154     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155       initialMode = TwoMachinesPlay;
1156     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157       initialMode = AnalyzeFile; 
1158     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159       initialMode = AnalyzeMode;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161       initialMode = MachinePlaysWhite;
1162     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163       initialMode = MachinePlaysBlack;
1164     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165       initialMode = EditGame;
1166     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167       initialMode = EditPosition;
1168     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169       initialMode = Training;
1170     } else {
1171       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172       DisplayFatalError(buf, 0, 2);
1173       return;
1174     }
1175
1176     if (appData.matchMode) {
1177         /* Set up machine vs. machine match */
1178         if (appData.noChessProgram) {
1179             DisplayFatalError(_("Can't have a match with no chess programs"),
1180                               0, 2);
1181             return;
1182         }
1183         matchMode = TRUE;
1184         matchGame = 1;
1185         if (*appData.loadGameFile != NULLCHAR) {
1186             int index = appData.loadGameIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadGameFromFile(appData.loadGameFile,
1189                                   index,
1190                                   appData.loadGameFile, FALSE)) {
1191                 DisplayFatalError(_("Bad game file"), 0, 1);
1192                 return;
1193             }
1194         } else if (*appData.loadPositionFile != NULLCHAR) {
1195             int index = appData.loadPositionIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadPositionFromFile(appData.loadPositionFile,
1198                                       index,
1199                                       appData.loadPositionFile)) {
1200                 DisplayFatalError(_("Bad position file"), 0, 1);
1201                 return;
1202             }
1203         }
1204         TwoMachinesEvent();
1205     } else if (*appData.cmailGameName != NULLCHAR) {
1206         /* Set up cmail mode */
1207         ReloadCmailMsgEvent(TRUE);
1208     } else {
1209         /* Set up other modes */
1210         if (initialMode == AnalyzeFile) {
1211           if (*appData.loadGameFile == NULLCHAR) {
1212             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213             return;
1214           }
1215         }
1216         if (*appData.loadGameFile != NULLCHAR) {
1217             (void) LoadGameFromFile(appData.loadGameFile,
1218                                     appData.loadGameIndex,
1219                                     appData.loadGameFile, TRUE);
1220         } else if (*appData.loadPositionFile != NULLCHAR) {
1221             (void) LoadPositionFromFile(appData.loadPositionFile,
1222                                         appData.loadPositionIndex,
1223                                         appData.loadPositionFile);
1224             /* [HGM] try to make self-starting even after FEN load */
1225             /* to allow automatic setup of fairy variants with wtm */
1226             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227                 gameMode = BeginningOfGame;
1228                 setboardSpoiledMachineBlack = 1;
1229             }
1230             /* [HGM] loadPos: make that every new game uses the setup */
1231             /* from file as long as we do not switch variant          */
1232             if(!blackPlaysFirst) { int i;
1233                 startedFromPositionFile = TRUE;
1234                 CopyBoard(filePosition, boards[0]);
1235                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1236             }
1237         }
1238         if (initialMode == AnalyzeMode) {
1239           if (appData.noChessProgram) {
1240             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245             return;
1246           }
1247           AnalyzeModeEvent();
1248         } else if (initialMode == AnalyzeFile) {
1249           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250           ShowThinkingEvent();
1251           AnalyzeFileEvent();
1252           AnalysisPeriodicEvent(1);
1253         } else if (initialMode == MachinePlaysWhite) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           MachineWhiteEvent();
1265         } else if (initialMode == MachinePlaysBlack) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineBlackEvent();
1277         } else if (initialMode == TwoMachinesPlay) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           TwoMachinesEvent();
1289         } else if (initialMode == EditGame) {
1290           EditGameEvent();
1291         } else if (initialMode == EditPosition) {
1292           EditPositionEvent();
1293         } else if (initialMode == Training) {
1294           if (*appData.loadGameFile == NULLCHAR) {
1295             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1296             return;
1297           }
1298           TrainingEvent();
1299         }
1300     }
1301 }
1302
1303 /*
1304  * Establish will establish a contact to a remote host.port.
1305  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306  *  used to talk to the host.
1307  * Returns 0 if okay, error code if not.
1308  */
1309 int
1310 establish()
1311 {
1312     char buf[MSG_SIZ];
1313
1314     if (*appData.icsCommPort != NULLCHAR) {
1315         /* Talk to the host through a serial comm port */
1316         return OpenCommPort(appData.icsCommPort, &icsPR);
1317
1318     } else if (*appData.gateway != NULLCHAR) {
1319         if (*appData.remoteShell == NULLCHAR) {
1320             /* Use the rcmd protocol to run telnet program on a gateway host */
1321             snprintf(buf, sizeof(buf), "%s %s %s",
1322                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1323             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1324
1325         } else {
1326             /* Use the rsh program to run telnet program on a gateway host */
1327             if (*appData.remoteUser == NULLCHAR) {
1328                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329                         appData.gateway, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             } else {
1332                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333                         appData.remoteShell, appData.gateway, 
1334                         appData.remoteUser, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             }
1337             return StartChildProcess(buf, "", &icsPR);
1338
1339         }
1340     } else if (appData.useTelnet) {
1341         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1342
1343     } else {
1344         /* TCP socket interface differs somewhat between
1345            Unix and NT; handle details in the front end.
1346            */
1347         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1348     }
1349 }
1350
1351 void
1352 show_bytes(fp, buf, count)
1353      FILE *fp;
1354      char *buf;
1355      int count;
1356 {
1357     while (count--) {
1358         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359             fprintf(fp, "\\%03o", *buf & 0xff);
1360         } else {
1361             putc(*buf, fp);
1362         }
1363         buf++;
1364     }
1365     fflush(fp);
1366 }
1367
1368 /* Returns an errno value */
1369 int
1370 OutputMaybeTelnet(pr, message, count, outError)
1371      ProcRef pr;
1372      char *message;
1373      int count;
1374      int *outError;
1375 {
1376     char buf[8192], *p, *q, *buflim;
1377     int left, newcount, outcount;
1378
1379     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380         *appData.gateway != NULLCHAR) {
1381         if (appData.debugMode) {
1382             fprintf(debugFP, ">ICS: ");
1383             show_bytes(debugFP, message, count);
1384             fprintf(debugFP, "\n");
1385         }
1386         return OutputToProcess(pr, message, count, outError);
1387     }
1388
1389     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1390     p = message;
1391     q = buf;
1392     left = count;
1393     newcount = 0;
1394     while (left) {
1395         if (q >= buflim) {
1396             if (appData.debugMode) {
1397                 fprintf(debugFP, ">ICS: ");
1398                 show_bytes(debugFP, buf, newcount);
1399                 fprintf(debugFP, "\n");
1400             }
1401             outcount = OutputToProcess(pr, buf, newcount, outError);
1402             if (outcount < newcount) return -1; /* to be sure */
1403             q = buf;
1404             newcount = 0;
1405         }
1406         if (*p == '\n') {
1407             *q++ = '\r';
1408             newcount++;
1409         } else if (((unsigned char) *p) == TN_IAC) {
1410             *q++ = (char) TN_IAC;
1411             newcount ++;
1412         }
1413         *q++ = *p++;
1414         newcount++;
1415         left--;
1416     }
1417     if (appData.debugMode) {
1418         fprintf(debugFP, ">ICS: ");
1419         show_bytes(debugFP, buf, newcount);
1420         fprintf(debugFP, "\n");
1421     }
1422     outcount = OutputToProcess(pr, buf, newcount, outError);
1423     if (outcount < newcount) return -1; /* to be sure */
1424     return count;
1425 }
1426
1427 void
1428 read_from_player(isr, closure, message, count, error)
1429      InputSourceRef isr;
1430      VOIDSTAR closure;
1431      char *message;
1432      int count;
1433      int error;
1434 {
1435     int outError, outCount;
1436     static int gotEof = 0;
1437
1438     /* Pass data read from player on to ICS */
1439     if (count > 0) {
1440         gotEof = 0;
1441         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442         if (outCount < count) {
1443             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1444         }
1445     } else if (count < 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448     } else if (gotEof++ > 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451     }
1452 }
1453
1454 void
1455 SendToICS(s)
1456      char *s;
1457 {
1458     int count, outCount, outError;
1459
1460     if (icsPR == NULL) return;
1461
1462     count = strlen(s);
1463     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464     if (outCount < count) {
1465         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466     }
1467 }
1468
1469 /* This is used for sending logon scripts to the ICS. Sending
1470    without a delay causes problems when using timestamp on ICC
1471    (at least on my machine). */
1472 void
1473 SendToICSDelayed(s,msdelay)
1474      char *s;
1475      long msdelay;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     if (appData.debugMode) {
1483         fprintf(debugFP, ">ICS: ");
1484         show_bytes(debugFP, s, count);
1485         fprintf(debugFP, "\n");
1486     }
1487     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1488                                       msdelay);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494
1495 /* Remove all highlighting escape sequences in s
1496    Also deletes any suffix starting with '(' 
1497    */
1498 char *
1499 StripHighlightAndTitle(s)
1500      char *s;
1501 {
1502     static char retbuf[MSG_SIZ];
1503     char *p = retbuf;
1504
1505     while (*s != NULLCHAR) {
1506         while (*s == '\033') {
1507             while (*s != NULLCHAR && !isalpha(*s)) s++;
1508             if (*s != NULLCHAR) s++;
1509         }
1510         while (*s != NULLCHAR && *s != '\033') {
1511             if (*s == '(' || *s == '[') {
1512                 *p = NULLCHAR;
1513                 return retbuf;
1514             }
1515             *p++ = *s++;
1516         }
1517     }
1518     *p = NULLCHAR;
1519     return retbuf;
1520 }
1521
1522 /* Remove all highlighting escape sequences in s */
1523 char *
1524 StripHighlight(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             *p++ = *s++;
1537         }
1538     }
1539     *p = NULLCHAR;
1540     return retbuf;
1541 }
1542
1543 char *variantNames[] = VARIANT_NAMES;
1544 char *
1545 VariantName(v)
1546      VariantClass v;
1547 {
1548     return variantNames[v];
1549 }
1550
1551
1552 /* Identify a variant from the strings the chess servers use or the
1553    PGN Variant tag names we use. */
1554 VariantClass
1555 StringToVariant(e)
1556      char *e;
1557 {
1558     char *p;
1559     int wnum = -1;
1560     VariantClass v = VariantNormal;
1561     int i, found = FALSE;
1562     char buf[MSG_SIZ];
1563
1564     if (!e) return v;
1565
1566     /* [HGM] skip over optional board-size prefixes */
1567     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569         while( *e++ != '_');
1570     }
1571
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr") 
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in 
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767     
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948
1949 }
1950
1951
1952 void
1953 VariantSwitch(Board board, VariantClass newVariant)
1954 {
1955    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974
1975   if (appData.debugMode) {
1976     fprintf(debugFP, "Switch board from %s to %s\n",
1977                VariantName(gameInfo.variant), VariantName(newVariant));
1978     setbuf(debugFP, NULL);
1979   }
1980     shuffleOpenings = 0;       /* [HGM] shuffle */
1981     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982     switch(newVariant) {
1983             case VariantShogi:
1984               newWidth = 9;  newHeight = 9;
1985               gameInfo.holdingsSize = 7;
1986             case VariantBughouse:
1987             case VariantCrazyhouse:
1988               newHoldingsWidth = 2; break;
1989             default:
1990               newHoldingsWidth = gameInfo.holdingsSize = 0;
1991     }
1992
1993     if(newWidth  != gameInfo.boardWidth  ||
1994        newHeight != gameInfo.boardHeight ||
1995        newHoldingsWidth != gameInfo.holdingsWidth ) {
1996
1997         /* shift position to new playing area, if needed */
1998         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999            for(i=0; i<BOARD_HEIGHT; i++) 
2000                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2002                                                      board[i][j];
2003            for(i=0; i<newHeight; i++) {
2004                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2006            }
2007         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++)
2009                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                  board[i][j];
2012         }
2013
2014         gameInfo.boardWidth  = newWidth;
2015         gameInfo.boardHeight = newHeight;
2016         gameInfo.holdingsWidth = newHoldingsWidth;
2017         gameInfo.variant = newVariant;
2018         InitDrawingSizes(-2, 0);
2019
2020         /* [HGM] The following should definitely be solved in a better way */
2021 #if 0
2022         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024         saveEP = epStatus[0];
2025 #endif
2026         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2027 #if 0
2028         epStatus[0] = saveEP;
2029         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2031 #endif
2032     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2033
2034     forwardMostMove = oldForwardMostMove;
2035     backwardMostMove = oldBackwardMostMove;
2036     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2037 }
2038
2039 static int loggedOn = FALSE;
2040
2041 /*-- Game start info cache: --*/
2042 int gs_gamenum;
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2049
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2052
2053 void
2054 read_from_ics(isr, closure, data, count, error)
2055      InputSourceRef isr;
2056      VOIDSTAR closure;
2057      char *data;
2058      int count;
2059      int error;
2060 {
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2070     
2071     static int started = STARTED_NONE;
2072     static char parse[20000];
2073     static int parse_pos = 0;
2074     static char buf[BUF_SIZE + 1];
2075     static int firstTime = TRUE, intfSet = FALSE;
2076     static ColorClass prevColor = ColorNormal;
2077     static int savingComment = FALSE;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085
2086     if (appData.debugMode) {
2087       if (!error) {
2088         fprintf(debugFP, "<ICS: ");
2089         show_bytes(debugFP, data, count);
2090         fprintf(debugFP, "\n");
2091       }
2092     }
2093
2094     if (appData.debugMode) { int f = forwardMostMove;
2095         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106         /* Copy in new characters, removing nulls and \r's */
2107         buf_len = leftover_len;
2108         for (i = 0; i < count; i++) {
2109             if (data[i] != NULLCHAR && data[i] != '\r')
2110               buf[buf_len++] = data[i];
2111             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2112                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') 
2113                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114         }
2115
2116         buf[buf_len] = NULLCHAR;
2117         next_out = leftover_len;
2118         leftover_start = 0;
2119         
2120         i = 0;
2121         while (i < buf_len) {
2122             /* Deal with part of the TELNET option negotiation
2123                protocol.  We refuse to do anything beyond the
2124                defaults, except that we allow the WILL ECHO option,
2125                which ICS uses to turn off password echoing when we are
2126                directly connected to it.  We reject this option
2127                if localLineEditing mode is on (always on in xboard)
2128                and we are talking to port 23, which might be a real
2129                telnet server that will try to keep WILL ECHO on permanently.
2130              */
2131             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2132                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2133                 unsigned char option;
2134                 oldi = i;
2135                 switch ((unsigned char) buf[++i]) {
2136                   case TN_WILL:
2137                     if (appData.debugMode)
2138                       fprintf(debugFP, "\n<WILL ");
2139                     switch (option = (unsigned char) buf[++i]) {
2140                       case TN_ECHO:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "ECHO ");
2143                         /* Reply only if this is a change, according
2144                            to the protocol rules. */
2145                         if (remoteEchoOption) break;
2146                         if (appData.localLineEditing &&
2147                             atoi(appData.icsPort) == TN_PORT) {
2148                             TelnetRequest(TN_DONT, TN_ECHO);
2149                         } else {
2150                             EchoOff();
2151                             TelnetRequest(TN_DO, TN_ECHO);
2152                             remoteEchoOption = TRUE;
2153                         }
2154                         break;
2155                       default:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "%d ", option);
2158                         /* Whatever this is, we don't want it. */
2159                         TelnetRequest(TN_DONT, option);
2160                         break;
2161                     }
2162                     break;
2163                   case TN_WONT:
2164                     if (appData.debugMode)
2165                       fprintf(debugFP, "\n<WONT ");
2166                     switch (option = (unsigned char) buf[++i]) {
2167                       case TN_ECHO:
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "ECHO ");
2170                         /* Reply only if this is a change, according
2171                            to the protocol rules. */
2172                         if (!remoteEchoOption) break;
2173                         EchoOn();
2174                         TelnetRequest(TN_DONT, TN_ECHO);
2175                         remoteEchoOption = FALSE;
2176                         break;
2177                       default:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", (unsigned char) option);
2180                         /* Whatever this is, it must already be turned
2181                            off, because we never agree to turn on
2182                            anything non-default, so according to the
2183                            protocol rules, we don't reply. */
2184                         break;
2185                     }
2186                     break;
2187                   case TN_DO:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<DO ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       default:
2192                         /* Whatever this is, we refuse to do it. */
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         TelnetRequest(TN_WONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_DONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<DONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       default:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "%d ", option);
2206                         /* Whatever this is, we are already not doing
2207                            it, because we never agree to do anything
2208                            non-default, so according to the protocol
2209                            rules, we don't reply. */
2210                         break;
2211                     }
2212                     break;
2213                   case TN_IAC:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<IAC ");
2216                     /* Doubled IAC; pass it through */
2217                     i--;
2218                     break;
2219                   default:
2220                     if (appData.debugMode)
2221                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2222                     /* Drop all other telnet commands on the floor */
2223                     break;
2224                 }
2225                 if (oldi > next_out)
2226                   SendToPlayer(&buf[next_out], oldi - next_out);
2227                 if (++i > next_out)
2228                   next_out = i;
2229                 continue;
2230             }
2231                 
2232             /* OK, this at least will *usually* work */
2233             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2234                 loggedOn = TRUE;
2235             }
2236             
2237             if (loggedOn && !intfSet) {
2238                 if (ics_type == ICS_ICC) {
2239                   sprintf(str,
2240                           "/set-quietly interface %s\n/set-quietly style 12\n",
2241                           programVersion);
2242
2243                 } else if (ics_type == ICS_CHESSNET) {
2244                   sprintf(str, "/style 12\n");
2245                 } else {
2246                   strcpy(str, "alias $ @\n$set interface ");
2247                   strcat(str, programVersion);
2248                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2249 #ifdef WIN32
2250                   strcat(str, "$iset nohighlight 1\n");
2251 #endif
2252                   strcat(str, "$iset lock 1\n$style 12\n");
2253                 }
2254                 SendToICS(str);
2255                 intfSet = TRUE;
2256             }
2257
2258             if (started == STARTED_COMMENT) {
2259                 /* Accumulate characters in comment */
2260                 parse[parse_pos++] = buf[i];
2261                 if (buf[i] == '\n') {
2262                     parse[parse_pos] = NULLCHAR;
2263                     if(!suppressKibitz) // [HGM] kibitz
2264                         AppendComment(forwardMostMove, StripHighlight(parse));
2265                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2266                         int nrDigit = 0, nrAlph = 0, i;
2267                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2268                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2269                         parse[parse_pos] = NULLCHAR;
2270                         // try to be smart: if it does not look like search info, it should go to
2271                         // ICS interaction window after all, not to engine-output window.
2272                         for(i=0; i<parse_pos; i++) { // count letters and digits
2273                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2274                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2275                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2276                         }
2277                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2278                             int depth=0; float score;
2279                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2280                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2281                                 pvInfoList[forwardMostMove-1].depth = depth;
2282                                 pvInfoList[forwardMostMove-1].score = 100*score;
2283                             }
2284                             OutputKibitz(suppressKibitz, parse);
2285                         } else {
2286                             char tmp[MSG_SIZ];
2287                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2288                             SendToPlayer(tmp, strlen(tmp));
2289                         }
2290                     }
2291                     started = STARTED_NONE;
2292                 } else {
2293                     /* Don't match patterns against characters in chatter */
2294                     i++;
2295                     continue;
2296                 }
2297             }
2298             if (started == STARTED_CHATTER) {
2299                 if (buf[i] != '\n') {
2300                     /* Don't match patterns against characters in chatter */
2301                     i++;
2302                     continue;
2303                 }
2304                 started = STARTED_NONE;
2305             }
2306
2307             /* Kludge to deal with rcmd protocol */
2308             if (firstTime && looking_at(buf, &i, "\001*")) {
2309                 DisplayFatalError(&buf[1], 0, 1);
2310                 continue;
2311             } else {
2312                 firstTime = FALSE;
2313             }
2314
2315             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2316                 ics_type = ICS_ICC;
2317                 ics_prefix = "/";
2318                 if (appData.debugMode)
2319                   fprintf(debugFP, "ics_type %d\n", ics_type);
2320                 continue;
2321             }
2322             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2323                 ics_type = ICS_FICS;
2324                 ics_prefix = "$";
2325                 if (appData.debugMode)
2326                   fprintf(debugFP, "ics_type %d\n", ics_type);
2327                 continue;
2328             }
2329             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2330                 ics_type = ICS_CHESSNET;
2331                 ics_prefix = "/";
2332                 if (appData.debugMode)
2333                   fprintf(debugFP, "ics_type %d\n", ics_type);
2334                 continue;
2335             }
2336
2337             if (!loggedOn &&
2338                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2339                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2340                  looking_at(buf, &i, "will be \"*\""))) {
2341               strcpy(ics_handle, star_match[0]);
2342               continue;
2343             }
2344
2345             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2346               char buf[MSG_SIZ];
2347               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2348               DisplayIcsInteractionTitle(buf);
2349               have_set_title = TRUE;
2350             }
2351
2352             /* skip finger notes */
2353             if (started == STARTED_NONE &&
2354                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2355                  (buf[i] == '1' && buf[i+1] == '0')) &&
2356                 buf[i+2] == ':' && buf[i+3] == ' ') {
2357               started = STARTED_CHATTER;
2358               i += 3;
2359               continue;
2360             }
2361
2362             /* skip formula vars */
2363             if (started == STARTED_NONE &&
2364                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2365               started = STARTED_CHATTER;
2366               i += 3;
2367               continue;
2368             }
2369
2370             oldi = i;
2371             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2372             if (appData.autoKibitz && started == STARTED_NONE && 
2373                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2374                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2375                 if(looking_at(buf, &i, "* kibitzes: ") &&
2376                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2377                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2378                         suppressKibitz = TRUE;
2379                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2380                                 && (gameMode == IcsPlayingWhite)) ||
2381                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2382                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2383                             started = STARTED_CHATTER; // own kibitz we simply discard
2384                         else {
2385                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2386                             parse_pos = 0; parse[0] = NULLCHAR;
2387                             savingComment = TRUE;
2388                             suppressKibitz = gameMode != IcsObserving ? 2 :
2389                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2390                         } 
2391                         continue;
2392                 } else
2393                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2394                     started = STARTED_CHATTER;
2395                     suppressKibitz = TRUE;
2396                 }
2397             } // [HGM] kibitz: end of patch
2398
2399             if (appData.zippyTalk || appData.zippyPlay) {
2400                 /* [DM] Backup address for color zippy lines */
2401                 backup = i;
2402 #if ZIPPY
2403        #ifdef WIN32
2404                if (loggedOn == TRUE)
2405                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2406                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2407        #else
2408                 if (ZippyControl(buf, &i) ||
2409                     ZippyConverse(buf, &i) ||
2410                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2411                       loggedOn = TRUE;
2412                       if (!appData.colorize) continue;
2413                 }
2414        #endif
2415 #endif
2416             } // [DM] 'else { ' deleted
2417                 if (/* Don't color "message" or "messages" output */
2418                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2419                     looking_at(buf, &i, "*. * at *:*: ") ||
2420                     looking_at(buf, &i, "--* (*:*): ") ||
2421                     /* Regular tells and says */
2422                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2423                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2424                     looking_at(buf, &i, "* says: ") ||
2425                     /* Message notifications (same color as tells) */
2426                     looking_at(buf, &i, "* has left a message ") ||
2427                     looking_at(buf, &i, "* just sent you a message:\n") ||
2428                     /* Whispers and kibitzes */
2429                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2430                     looking_at(buf, &i, "* kibitzes: ") ||
2431                     /* Channel tells */
2432                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2433
2434                   if (tkind == 1 && strchr(star_match[0], ':')) {
2435                       /* Avoid "tells you:" spoofs in channels */
2436                      tkind = 3;
2437                   }
2438                   if (star_match[0][0] == NULLCHAR ||
2439                       strchr(star_match[0], ' ') ||
2440                       (tkind == 3 && strchr(star_match[1], ' '))) {
2441                     /* Reject bogus matches */
2442                     i = oldi;
2443                   } else {
2444                     if (appData.colorize) {
2445                       if (oldi > next_out) {
2446                         SendToPlayer(&buf[next_out], oldi - next_out);
2447                         next_out = oldi;
2448                       }
2449                       switch (tkind) {
2450                       case 1:
2451                         Colorize(ColorTell, FALSE);
2452                         curColor = ColorTell;
2453                         break;
2454                       case 2:
2455                         Colorize(ColorKibitz, FALSE);
2456                         curColor = ColorKibitz;
2457                         break;
2458                       case 3:
2459                         p = strrchr(star_match[1], '(');
2460                         if (p == NULL) {
2461                           p = star_match[1];
2462                         } else {
2463                           p++;
2464                         }
2465                         if (atoi(p) == 1) {
2466                           Colorize(ColorChannel1, FALSE);
2467                           curColor = ColorChannel1;
2468                         } else {
2469                           Colorize(ColorChannel, FALSE);
2470                           curColor = ColorChannel;
2471                         }
2472                         break;
2473                       case 5:
2474                         curColor = ColorNormal;
2475                         break;
2476                       }
2477                     }
2478                     if (started == STARTED_NONE && appData.autoComment &&
2479                         (gameMode == IcsObserving ||
2480                          gameMode == IcsPlayingWhite ||
2481                          gameMode == IcsPlayingBlack)) {
2482                       parse_pos = i - oldi;
2483                       memcpy(parse, &buf[oldi], parse_pos);
2484                       parse[parse_pos] = NULLCHAR;
2485                       started = STARTED_COMMENT;
2486                       savingComment = TRUE;
2487                     } else {
2488                       started = STARTED_CHATTER;
2489                       savingComment = FALSE;
2490                     }
2491                     loggedOn = TRUE;
2492                     continue;
2493                   }
2494                 }
2495
2496                 if (looking_at(buf, &i, "* s-shouts: ") ||
2497                     looking_at(buf, &i, "* c-shouts: ")) {
2498                     if (appData.colorize) {
2499                         if (oldi > next_out) {
2500                             SendToPlayer(&buf[next_out], oldi - next_out);
2501                             next_out = oldi;
2502                         }
2503                         Colorize(ColorSShout, FALSE);
2504                         curColor = ColorSShout;
2505                     }
2506                     loggedOn = TRUE;
2507                     started = STARTED_CHATTER;
2508                     continue;
2509                 }
2510
2511                 if (looking_at(buf, &i, "--->")) {
2512                     loggedOn = TRUE;
2513                     continue;
2514                 }
2515
2516                 if (looking_at(buf, &i, "* shouts: ") ||
2517                     looking_at(buf, &i, "--> ")) {
2518                     if (appData.colorize) {
2519                         if (oldi > next_out) {
2520                             SendToPlayer(&buf[next_out], oldi - next_out);
2521                             next_out = oldi;
2522                         }
2523                         Colorize(ColorShout, FALSE);
2524                         curColor = ColorShout;
2525                     }
2526                     loggedOn = TRUE;
2527                     started = STARTED_CHATTER;
2528                     continue;
2529                 }
2530
2531                 if (looking_at( buf, &i, "Challenge:")) {
2532                     if (appData.colorize) {
2533                         if (oldi > next_out) {
2534                             SendToPlayer(&buf[next_out], oldi - next_out);
2535                             next_out = oldi;
2536                         }
2537                         Colorize(ColorChallenge, FALSE);
2538                         curColor = ColorChallenge;
2539                     }
2540                     loggedOn = TRUE;
2541                     continue;
2542                 }
2543
2544                 if (looking_at(buf, &i, "* offers you") ||
2545                     looking_at(buf, &i, "* offers to be") ||
2546                     looking_at(buf, &i, "* would like to") ||
2547                     looking_at(buf, &i, "* requests to") ||
2548                     looking_at(buf, &i, "Your opponent offers") ||
2549                     looking_at(buf, &i, "Your opponent requests")) {
2550
2551                     if (appData.colorize) {
2552                         if (oldi > next_out) {
2553                             SendToPlayer(&buf[next_out], oldi - next_out);
2554                             next_out = oldi;
2555                         }
2556                         Colorize(ColorRequest, FALSE);
2557                         curColor = ColorRequest;
2558                     }
2559                     continue;
2560                 }
2561
2562                 if (looking_at(buf, &i, "* (*) seeking")) {
2563                     if (appData.colorize) {
2564                         if (oldi > next_out) {
2565                             SendToPlayer(&buf[next_out], oldi - next_out);
2566                             next_out = oldi;
2567                         }
2568                         Colorize(ColorSeek, FALSE);
2569                         curColor = ColorSeek;
2570                     }
2571                     continue;
2572             }
2573
2574             if (looking_at(buf, &i, "\\   ")) {
2575                 if (prevColor != ColorNormal) {
2576                     if (oldi > next_out) {
2577                         SendToPlayer(&buf[next_out], oldi - next_out);
2578                         next_out = oldi;
2579                     }
2580                     Colorize(prevColor, TRUE);
2581                     curColor = prevColor;
2582                 }
2583                 if (savingComment) {
2584                     parse_pos = i - oldi;
2585                     memcpy(parse, &buf[oldi], parse_pos);
2586                     parse[parse_pos] = NULLCHAR;
2587                     started = STARTED_COMMENT;
2588                 } else {
2589                     started = STARTED_CHATTER;
2590                 }
2591                 continue;
2592             }
2593
2594             if (looking_at(buf, &i, "Black Strength :") ||
2595                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2596                 looking_at(buf, &i, "<10>") ||
2597                 looking_at(buf, &i, "#@#")) {
2598                 /* Wrong board style */
2599                 loggedOn = TRUE;
2600                 SendToICS(ics_prefix);
2601                 SendToICS("set style 12\n");
2602                 SendToICS(ics_prefix);
2603                 SendToICS("refresh\n");
2604                 continue;
2605             }
2606             
2607             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2608                 ICSInitScript();
2609                 have_sent_ICS_logon = 1;
2610                 continue;
2611             }
2612               
2613             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2614                 (looking_at(buf, &i, "\n<12> ") ||
2615                  looking_at(buf, &i, "<12> "))) {
2616                 loggedOn = TRUE;
2617                 if (oldi > next_out) {
2618                     SendToPlayer(&buf[next_out], oldi - next_out);
2619                 }
2620                 next_out = i;
2621                 started = STARTED_BOARD;
2622                 parse_pos = 0;
2623                 continue;
2624             }
2625
2626             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2627                 looking_at(buf, &i, "<b1> ")) {
2628                 if (oldi > next_out) {
2629                     SendToPlayer(&buf[next_out], oldi - next_out);
2630                 }
2631                 next_out = i;
2632                 started = STARTED_HOLDINGS;
2633                 parse_pos = 0;
2634                 continue;
2635             }
2636
2637             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2638                 loggedOn = TRUE;
2639                 /* Header for a move list -- first line */
2640
2641                 switch (ics_getting_history) {
2642                   case H_FALSE:
2643                     switch (gameMode) {
2644                       case IcsIdle:
2645                       case BeginningOfGame:
2646                         /* User typed "moves" or "oldmoves" while we
2647                            were idle.  Pretend we asked for these
2648                            moves and soak them up so user can step
2649                            through them and/or save them.
2650                            */
2651                         Reset(FALSE, TRUE);
2652                         gameMode = IcsObserving;
2653                         ModeHighlight();
2654                         ics_gamenum = -1;
2655                         ics_getting_history = H_GOT_UNREQ_HEADER;
2656                         break;
2657                       case EditGame: /*?*/
2658                       case EditPosition: /*?*/
2659                         /* Should above feature work in these modes too? */
2660                         /* For now it doesn't */
2661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2662                         break;
2663                       default:
2664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2665                         break;
2666                     }
2667                     break;
2668                   case H_REQUESTED:
2669                     /* Is this the right one? */
2670                     if (gameInfo.white && gameInfo.black &&
2671                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2672                         strcmp(gameInfo.black, star_match[2]) == 0) {
2673                         /* All is well */
2674                         ics_getting_history = H_GOT_REQ_HEADER;
2675                     }
2676                     break;
2677                   case H_GOT_REQ_HEADER:
2678                   case H_GOT_UNREQ_HEADER:
2679                   case H_GOT_UNWANTED_HEADER:
2680                   case H_GETTING_MOVES:
2681                     /* Should not happen */
2682                     DisplayError(_("Error gathering move list: two headers"), 0);
2683                     ics_getting_history = H_FALSE;
2684                     break;
2685                 }
2686
2687                 /* Save player ratings into gameInfo if needed */
2688                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2689                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2690                     (gameInfo.whiteRating == -1 ||
2691                      gameInfo.blackRating == -1)) {
2692
2693                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2694                     gameInfo.blackRating = string_to_rating(star_match[3]);
2695                     if (appData.debugMode)
2696                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2697                               gameInfo.whiteRating, gameInfo.blackRating);
2698                 }
2699                 continue;
2700             }
2701
2702             if (looking_at(buf, &i,
2703               "* * match, initial time: * minute*, increment: * second")) {
2704                 /* Header for a move list -- second line */
2705                 /* Initial board will follow if this is a wild game */
2706                 if (gameInfo.event != NULL) free(gameInfo.event);
2707                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2708                 gameInfo.event = StrSave(str);
2709                 /* [HGM] we switched variant. Translate boards if needed. */
2710                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2711                 continue;
2712             }
2713
2714             if (looking_at(buf, &i, "Move  ")) {
2715                 /* Beginning of a move list */
2716                 switch (ics_getting_history) {
2717                   case H_FALSE:
2718                     /* Normally should not happen */
2719                     /* Maybe user hit reset while we were parsing */
2720                     break;
2721                   case H_REQUESTED:
2722                     /* Happens if we are ignoring a move list that is not
2723                      * the one we just requested.  Common if the user
2724                      * tries to observe two games without turning off
2725                      * getMoveList */
2726                     break;
2727                   case H_GETTING_MOVES:
2728                     /* Should not happen */
2729                     DisplayError(_("Error gathering move list: nested"), 0);
2730                     ics_getting_history = H_FALSE;
2731                     break;
2732                   case H_GOT_REQ_HEADER:
2733                     ics_getting_history = H_GETTING_MOVES;
2734                     started = STARTED_MOVES;
2735                     parse_pos = 0;
2736                     if (oldi > next_out) {
2737                         SendToPlayer(&buf[next_out], oldi - next_out);
2738                     }
2739                     break;
2740                   case H_GOT_UNREQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES_NOHIDE;
2743                     parse_pos = 0;
2744                     break;
2745                   case H_GOT_UNWANTED_HEADER:
2746                     ics_getting_history = H_FALSE;
2747                     break;
2748                 }
2749                 continue;
2750             }                           
2751             
2752             if (looking_at(buf, &i, "% ") ||
2753                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2754                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2755                 savingComment = FALSE;
2756                 switch (started) {
2757                   case STARTED_MOVES:
2758                   case STARTED_MOVES_NOHIDE:
2759                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2760                     parse[parse_pos + i - oldi] = NULLCHAR;
2761                     ParseGameHistory(parse);
2762 #if ZIPPY
2763                     if (appData.zippyPlay && first.initDone) {
2764                         FeedMovesToProgram(&first, forwardMostMove);
2765                         if (gameMode == IcsPlayingWhite) {
2766                             if (WhiteOnMove(forwardMostMove)) {
2767                                 if (first.sendTime) {
2768                                   if (first.useColors) {
2769                                     SendToProgram("black\n", &first); 
2770                                   }
2771                                   SendTimeRemaining(&first, TRUE);
2772                                 }
2773 #if 0
2774                                 if (first.useColors) {
2775                                   SendToProgram("white\ngo\n", &first);
2776                                 } else {
2777                                   SendToProgram("go\n", &first);
2778                                 }
2779 #else
2780                                 if (first.useColors) {
2781                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2782                                 }
2783                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2784 #endif
2785                                 first.maybeThinking = TRUE;
2786                             } else {
2787                                 if (first.usePlayother) {
2788                                   if (first.sendTime) {
2789                                     SendTimeRemaining(&first, TRUE);
2790                                   }
2791                                   SendToProgram("playother\n", &first);
2792                                   firstMove = FALSE;
2793                                 } else {
2794                                   firstMove = TRUE;
2795                                 }
2796                             }
2797                         } else if (gameMode == IcsPlayingBlack) {
2798                             if (!WhiteOnMove(forwardMostMove)) {
2799                                 if (first.sendTime) {
2800                                   if (first.useColors) {
2801                                     SendToProgram("white\n", &first);
2802                                   }
2803                                   SendTimeRemaining(&first, FALSE);
2804                                 }
2805 #if 0
2806                                 if (first.useColors) {
2807                                   SendToProgram("black\ngo\n", &first);
2808                                 } else {
2809                                   SendToProgram("go\n", &first);
2810                                 }
2811 #else
2812                                 if (first.useColors) {
2813                                   SendToProgram("black\n", &first);
2814                                 }
2815                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2816 #endif
2817                                 first.maybeThinking = TRUE;
2818                             } else {
2819                                 if (first.usePlayother) {
2820                                   if (first.sendTime) {
2821                                     SendTimeRemaining(&first, FALSE);
2822                                   }
2823                                   SendToProgram("playother\n", &first);
2824                                   firstMove = FALSE;
2825                                 } else {
2826                                   firstMove = TRUE;
2827                                 }
2828                             }
2829                         }                       
2830                     }
2831 #endif
2832                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2833                         /* Moves came from oldmoves or moves command
2834                            while we weren't doing anything else.
2835                            */
2836                         currentMove = forwardMostMove;
2837                         ClearHighlights();/*!!could figure this out*/
2838                         flipView = appData.flipView;
2839                         DrawPosition(FALSE, boards[currentMove]);
2840                         DisplayBothClocks();
2841                         sprintf(str, "%s vs. %s",
2842                                 gameInfo.white, gameInfo.black);
2843                         DisplayTitle(str);
2844                         gameMode = IcsIdle;
2845                     } else {
2846                         /* Moves were history of an active game */
2847                         if (gameInfo.resultDetails != NULL) {
2848                             free(gameInfo.resultDetails);
2849                             gameInfo.resultDetails = NULL;
2850                         }
2851                     }
2852                     HistorySet(parseList, backwardMostMove,
2853                                forwardMostMove, currentMove-1);
2854                     DisplayMove(currentMove - 1);
2855                     if (started == STARTED_MOVES) next_out = i;
2856                     started = STARTED_NONE;
2857                     ics_getting_history = H_FALSE;
2858                     break;
2859
2860                   case STARTED_OBSERVE:
2861                     started = STARTED_NONE;
2862                     SendToICS(ics_prefix);
2863                     SendToICS("refresh\n");
2864                     break;
2865
2866                   default:
2867                     break;
2868                 }
2869                 if(bookHit) { // [HGM] book: simulate book reply
2870                     static char bookMove[MSG_SIZ]; // a bit generous?
2871
2872                     programStats.nodes = programStats.depth = programStats.time = 
2873                     programStats.score = programStats.got_only_move = 0;
2874                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2875
2876                     strcpy(bookMove, "move ");
2877                     strcat(bookMove, bookHit);
2878                     HandleMachineMove(bookMove, &first);
2879                 }
2880                 continue;
2881             }
2882             
2883             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2884                  started == STARTED_HOLDINGS ||
2885                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2886                 /* Accumulate characters in move list or board */
2887                 parse[parse_pos++] = buf[i];
2888             }
2889             
2890             /* Start of game messages.  Mostly we detect start of game
2891                when the first board image arrives.  On some versions
2892                of the ICS, though, we need to do a "refresh" after starting
2893                to observe in order to get the current board right away. */
2894             if (looking_at(buf, &i, "Adding game * to observation list")) {
2895                 started = STARTED_OBSERVE;
2896                 continue;
2897             }
2898
2899             /* Handle auto-observe */
2900             if (appData.autoObserve &&
2901                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2902                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2903                 char *player;
2904                 /* Choose the player that was highlighted, if any. */
2905                 if (star_match[0][0] == '\033' ||
2906                     star_match[1][0] != '\033') {
2907                     player = star_match[0];
2908                 } else {
2909                     player = star_match[2];
2910                 }
2911                 sprintf(str, "%sobserve %s\n",
2912                         ics_prefix, StripHighlightAndTitle(player));
2913                 SendToICS(str);
2914
2915                 /* Save ratings from notify string */
2916                 strcpy(player1Name, star_match[0]);
2917                 player1Rating = string_to_rating(star_match[1]);
2918                 strcpy(player2Name, star_match[2]);
2919                 player2Rating = string_to_rating(star_match[3]);
2920
2921                 if (appData.debugMode)
2922                   fprintf(debugFP, 
2923                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2924                           player1Name, player1Rating,
2925                           player2Name, player2Rating);
2926
2927                 continue;
2928             }
2929
2930             /* Deal with automatic examine mode after a game,
2931                and with IcsObserving -> IcsExamining transition */
2932             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2933                 looking_at(buf, &i, "has made you an examiner of game *")) {
2934
2935                 int gamenum = atoi(star_match[0]);
2936                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2937                     gamenum == ics_gamenum) {
2938                     /* We were already playing or observing this game;
2939                        no need to refetch history */
2940                     gameMode = IcsExamining;
2941                     if (pausing) {
2942                         pauseExamForwardMostMove = forwardMostMove;
2943                     } else if (currentMove < forwardMostMove) {
2944                         ForwardInner(forwardMostMove);
2945                     }
2946                 } else {
2947                     /* I don't think this case really can happen */
2948                     SendToICS(ics_prefix);
2949                     SendToICS("refresh\n");
2950                 }
2951                 continue;
2952             }    
2953             
2954             /* Error messages */
2955 //          if (ics_user_moved) {
2956             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2957                 if (looking_at(buf, &i, "Illegal move") ||
2958                     looking_at(buf, &i, "Not a legal move") ||
2959                     looking_at(buf, &i, "Your king is in check") ||
2960                     looking_at(buf, &i, "It isn't your turn") ||
2961                     looking_at(buf, &i, "It is not your move")) {
2962                     /* Illegal move */
2963                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2964                         currentMove = --forwardMostMove;
2965                         DisplayMove(currentMove - 1); /* before DMError */
2966                         DrawPosition(FALSE, boards[currentMove]);
2967                         SwitchClocks();
2968                         DisplayBothClocks();
2969                     }
2970                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2971                     ics_user_moved = 0;
2972                     continue;
2973                 }
2974             }
2975
2976             if (looking_at(buf, &i, "still have time") ||
2977                 looking_at(buf, &i, "not out of time") ||
2978                 looking_at(buf, &i, "either player is out of time") ||
2979                 looking_at(buf, &i, "has timeseal; checking")) {
2980                 /* We must have called his flag a little too soon */
2981                 whiteFlag = blackFlag = FALSE;
2982                 continue;
2983             }
2984
2985             if (looking_at(buf, &i, "added * seconds to") ||
2986                 looking_at(buf, &i, "seconds were added to")) {
2987                 /* Update the clocks */
2988                 SendToICS(ics_prefix);
2989                 SendToICS("refresh\n");
2990                 continue;
2991             }
2992
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994                 ics_clock_paused = TRUE;
2995                 StopClocks();
2996                 continue;
2997             }
2998
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000                 ics_clock_paused = FALSE;
3001                 StartClocks();
3002                 continue;
3003             }
3004
3005             /* Grab player ratings from the Creating: message.
3006                Note we have to check for the special case when
3007                the ICS inserts things like [white] or [black]. */
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3010                 /* star_matches:
3011                    0    player 1 name (not necessarily white)
3012                    1    player 1 rating
3013                    2    empty, white, or black (IGNORED)
3014                    3    player 2 name (not necessarily black)
3015                    4    player 2 rating
3016                    
3017                    The names/ratings are sorted out when the game
3018                    actually starts (below).
3019                 */
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021                 player1Rating = string_to_rating(star_match[1]);
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023                 player2Rating = string_to_rating(star_match[4]);
3024
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, 
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",
3028                           player1Name, player1Rating,
3029                           player2Name, player2Rating);
3030
3031                 continue;
3032             }
3033             
3034             /* Improved generic start/end-of-game messages */
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037                 /* If tkind == 0: */
3038                 /* star_match[0] is the game number */
3039                 /*           [1] is the white player's name */
3040                 /*           [2] is the black player's name */
3041                 /* For end-of-game: */
3042                 /*           [3] is the reason for the game end */
3043                 /*           [4] is a PGN end game-token, preceded by " " */
3044                 /* For start-of-game: */
3045                 /*           [3] begins with "Creating" or "Continuing" */
3046                 /*           [4] is " *" or empty (don't care). */
3047                 int gamenum = atoi(star_match[0]);
3048                 char *whitename, *blackname, *why, *endtoken;
3049                 ChessMove endtype = (ChessMove) 0;
3050
3051                 if (tkind == 0) {
3052                   whitename = star_match[1];
3053                   blackname = star_match[2];
3054                   why = star_match[3];
3055                   endtoken = star_match[4];
3056                 } else {
3057                   whitename = star_match[1];
3058                   blackname = star_match[3];
3059                   why = star_match[5];
3060                   endtoken = star_match[6];
3061                 }
3062
3063                 /* Game start messages */
3064                 if (strncmp(why, "Creating ", 9) == 0 ||
3065                     strncmp(why, "Continuing ", 11) == 0) {
3066                     gs_gamenum = gamenum;
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);
3068 #if ZIPPY
3069                     if (appData.zippyPlay) {
3070                         ZippyGameStart(whitename, blackname);
3071                     }
3072 #endif /*ZIPPY*/
3073                     continue;
3074                 }
3075
3076                 /* Game end messages */
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078                     ics_gamenum != gamenum) {
3079                     continue;
3080                 }
3081                 while (endtoken[0] == ' ') endtoken++;
3082                 switch (endtoken[0]) {
3083                   case '*':
3084                   default:
3085                     endtype = GameUnfinished;
3086                     break;
3087                   case '0':
3088                     endtype = BlackWins;
3089                     break;
3090                   case '1':
3091                     if (endtoken[1] == '/')
3092                       endtype = GameIsDrawn;
3093                     else
3094                       endtype = WhiteWins;
3095                     break;
3096                 }
3097                 GameEnds(endtype, why, GE_ICS);
3098 #if ZIPPY
3099                 if (appData.zippyPlay && first.initDone) {
3100                     ZippyGameEnd(endtype, why);
3101                     if (first.pr == NULL) {
3102                       /* Start the next process early so that we'll
3103                          be ready for the next challenge */
3104                       StartChessProgram(&first);
3105                     }
3106                     /* Send "new" early, in case this command takes
3107                        a long time to finish, so that we'll be ready
3108                        for the next challenge. */
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3110                     Reset(TRUE, TRUE);
3111                 }
3112 #endif /*ZIPPY*/
3113                 continue;
3114             }
3115
3116             if (looking_at(buf, &i, "Removing game * from observation") ||
3117                 looking_at(buf, &i, "no longer observing game *") ||
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119                 if (gameMode == IcsObserving &&
3120                     atoi(star_match[0]) == ics_gamenum)
3121                   {
3122                       /* icsEngineAnalyze */
3123                       if (appData.icsEngineAnalyze) {
3124                             ExitAnalyzeMode();
3125                             ModeHighlight();
3126                       }
3127                       StopClocks();
3128                       gameMode = IcsIdle;
3129                       ics_gamenum = -1;
3130                       ics_user_moved = FALSE;
3131                   }
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "no longer examining game *")) {
3136                 if (gameMode == IcsExamining &&
3137                     atoi(star_match[0]) == ics_gamenum)
3138                   {
3139                       gameMode = IcsIdle;
3140                       ics_gamenum = -1;
3141                       ics_user_moved = FALSE;
3142                   }
3143                 continue;
3144             }
3145
3146             /* Advance leftover_start past any newlines we find,
3147                so only partial lines can get reparsed */
3148             if (looking_at(buf, &i, "\n")) {
3149                 prevColor = curColor;
3150                 if (curColor != ColorNormal) {
3151                     if (oldi > next_out) {
3152                         SendToPlayer(&buf[next_out], oldi - next_out);
3153                         next_out = oldi;
3154                     }
3155                     Colorize(ColorNormal, FALSE);
3156                     curColor = ColorNormal;
3157                 }
3158                 if (started == STARTED_BOARD) {
3159                     started = STARTED_NONE;
3160                     parse[parse_pos] = NULLCHAR;
3161                     ParseBoard12(parse);
3162                     ics_user_moved = 0;
3163
3164                     /* Send premove here */
3165                     if (appData.premove) {
3166                       char str[MSG_SIZ];
3167                       if (currentMove == 0 &&
3168                           gameMode == IcsPlayingWhite &&
3169                           appData.premoveWhite) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveWhiteText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (currentMove == 1 &&
3176                                  gameMode == IcsPlayingBlack &&
3177                                  appData.premoveBlack) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveBlackText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (gotPremove) {
3184                         gotPremove = 0;
3185                         ClearPremoveHighlights();
3186                         if (appData.debugMode)
3187                           fprintf(debugFP, "Sending premove:\n");
3188                           UserMoveEvent(premoveFromX, premoveFromY, 
3189                                         premoveToX, premoveToY, 
3190                                         premovePromoChar);
3191                       }
3192                     }
3193
3194                     /* Usually suppress following prompt */
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196                         if (looking_at(buf, &i, "*% ")) {
3197                             savingComment = FALSE;
3198                         }
3199                     }
3200                     next_out = i;
3201                 } else if (started == STARTED_HOLDINGS) {
3202                     int gamenum;
3203                     char new_piece[MSG_SIZ];
3204                     started = STARTED_NONE;
3205                     parse[parse_pos] = NULLCHAR;
3206                     if (appData.debugMode)
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208                                                         parse, currentMove);
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210                         gamenum == ics_gamenum) {
3211                         if (gameInfo.variant == VariantNormal) {
3212                           /* [HGM] We seem to switch variant during a game!
3213                            * Presumably no holdings were displayed, so we have
3214                            * to move the position two files to the right to
3215                            * create room for them!
3216                            */
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218                           /* Get a move list just to see the header, which
3219                              will tell us whether this is really bug or zh */
3220                           if (ics_getting_history == H_FALSE) {
3221                             ics_getting_history = H_REQUESTED;
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3223                             SendToICS(str);
3224                           }
3225                         }
3226                         new_piece[0] = NULLCHAR;
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",
3228                                &gamenum, white_holding, black_holding,
3229                                new_piece);
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3232                         /* [HGM] copy holdings to board holdings area */
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3235 #if ZIPPY
3236                         if (appData.zippyPlay && first.initDone) {
3237                             ZippyHoldings(white_holding, black_holding,
3238                                           new_piece);
3239                         }
3240 #endif /*ZIPPY*/
3241                         if (tinyLayout || smallLayout) {
3242                             char wh[16], bh[16];
3243                             PackHolding(wh, white_holding);
3244                             PackHolding(bh, black_holding);
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246                                     gameInfo.white, gameInfo.black);
3247                         } else {
3248                             sprintf(str, "%s [%s] vs. %s [%s]",
3249                                     gameInfo.white, white_holding,
3250                                     gameInfo.black, black_holding);
3251                         }
3252
3253                         DrawPosition(FALSE, boards[currentMove]);
3254                         DisplayTitle(str);
3255                     }
3256                     /* Suppress following prompt */
3257                     if (looking_at(buf, &i, "*% ")) {
3258                         savingComment = FALSE;
3259                     }
3260                     next_out = i;
3261                 }
3262                 continue;
3263             }
3264
3265             i++;                /* skip unparsed character and loop back */
3266         }
3267         
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269             started != STARTED_HOLDINGS && i > next_out) {
3270             SendToPlayer(&buf[next_out], i - next_out);
3271             next_out = i;
3272         }
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3274         
3275         leftover_len = buf_len - leftover_start;
3276         /* if buffer ends with something we couldn't parse,
3277            reparse it after appending the next read */
3278         
3279     } else if (count == 0) {
3280         RemoveInputSource(isr);
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3282     } else {
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);
3284     }
3285 }
3286
3287
3288 /* Board style 12 looks like this:
3289    
3290    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3291    
3292  * The "<12> " is stripped before it gets to this routine.  The two
3293  * trailing 0's (flip state and clock ticking) are later addition, and
3294  * some chess servers may not have them, or may have only the first.
3295  * Additional trailing fields may be added in the future.  
3296  */
3297
3298 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3299
3300 #define RELATION_OBSERVING_PLAYED    0
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE      1
3303 #define RELATION_PLAYING_NOTMYMOVE  -1
3304 #define RELATION_EXAMINING           2
3305 #define RELATION_ISOLATED_BOARD     -3
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3307
3308 void
3309 ParseBoard12(string)
3310      char *string;
3311
3312     GameMode newGameMode;
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316     char to_play, board_chars[200];
3317     char move_str[500], str[500], elapsed_time[500];
3318     char black[32], white[32];
3319     Board board;
3320     int prevMove = currentMove;
3321     int ticking = 2;
3322     ChessMove moveType;
3323     int fromX, fromY, toX, toY;
3324     char promoChar;
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326     char *bookHit = NULL; // [HGM] book
3327
3328     fromX = fromY = toX = toY = -1;
3329     
3330     newGame = FALSE;
3331
3332     if (appData.debugMode)
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);
3334
3335     move_str[0] = NULLCHAR;
3336     elapsed_time[0] = NULLCHAR;
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3338         int  i = 0, j;
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340             if(string[i] == ' ') { ranks++; files = 0; }
3341             else files++;
3342             i++;
3343         }
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];
3345         board_chars[i] = '\0';
3346         string += i + 1;
3347     }
3348     n = sscanf(string, PATTERN, &to_play, &double_push,
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350                &gamenum, white, black, &relation, &basetime, &increment,
3351                &white_stren, &black_stren, &white_time, &black_time,
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,
3353                &ticking);
3354
3355     if (n < 21) {
3356         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357         DisplayError(str, 0);
3358         return;
3359     }
3360
3361     /* Convert the move number to internal form */
3362     moveNum = (moveNum - 1) * 2;
3363     if (to_play == 'B') moveNum++;
3364     if (moveNum >= MAX_MOVES) {
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3366                         0, 1);
3367       return;
3368     }
3369     
3370     switch (relation) {
3371       case RELATION_OBSERVING_PLAYED:
3372       case RELATION_OBSERVING_STATIC:
3373         if (gamenum == -1) {
3374             /* Old ICC buglet */
3375             relation = RELATION_OBSERVING_STATIC;
3376         }
3377         newGameMode = IcsObserving;
3378         break;
3379       case RELATION_PLAYING_MYMOVE:
3380       case RELATION_PLAYING_NOTMYMOVE:
3381         newGameMode =
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383             IcsPlayingWhite : IcsPlayingBlack;
3384         break;
3385       case RELATION_EXAMINING:
3386         newGameMode = IcsExamining;
3387         break;
3388       case RELATION_ISOLATED_BOARD:
3389       default:
3390         /* Just display this board.  If user was doing something else,
3391            we will forget about it until the next board comes. */ 
3392         newGameMode = IcsIdle;
3393         break;
3394       case RELATION_STARTING_POSITION:
3395         newGameMode = gameMode;
3396         break;
3397     }
3398     
3399     /* Modify behavior for initial board display on move listing
3400        of wild games.
3401        */
3402     switch (ics_getting_history) {
3403       case H_FALSE:
3404       case H_REQUESTED:
3405         break;
3406       case H_GOT_REQ_HEADER:
3407       case H_GOT_UNREQ_HEADER:
3408         /* This is the initial position of the current game */
3409         gamenum = ics_gamenum;
3410         moveNum = 0;            /* old ICS bug workaround */
3411         if (to_play == 'B') {
3412           startedFromSetupPosition = TRUE;
3413           blackPlaysFirst = TRUE;
3414           moveNum = 1;
3415           if (forwardMostMove == 0) forwardMostMove = 1;
3416           if (backwardMostMove == 0) backwardMostMove = 1;
3417           if (currentMove == 0) currentMove = 1;
3418         }
3419         newGameMode = gameMode;
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3421         break;
3422       case H_GOT_UNWANTED_HEADER:
3423         /* This is an initial board that we don't want */
3424         return;
3425       case H_GETTING_MOVES:
3426         /* Should not happen */
3427         DisplayError(_("Error gathering move list: extra board"), 0);
3428         ics_getting_history = H_FALSE;
3429         return;
3430     }
3431     
3432     /* Take action if this is the first board of a new game, or of a
3433        different game than is currently being displayed.  */
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435         relation == RELATION_ISOLATED_BOARD) {
3436         
3437         /* Forget the old game and get the history (if any) of the new one */
3438         if (gameMode != BeginningOfGame) {
3439           Reset(FALSE, TRUE);
3440         }
3441         newGame = TRUE;
3442         if (appData.autoRaiseBoard) BoardToTop();
3443         prevMove = -3;
3444         if (gamenum == -1) {
3445             newGameMode = IcsIdle;
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447                    appData.getMoveList) {
3448             /* Need to get game history */
3449             ics_getting_history = H_REQUESTED;
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451             SendToICS(str);
3452         }
3453         
3454         /* Initially flip the board to have black on the bottom if playing
3455            black or if the ICS flip flag is set, but let the user change
3456            it with the Flip View button. */
3457         flipView = appData.autoFlipView ? 
3458           (newGameMode == IcsPlayingBlack) || ics_flip :
3459           appData.flipView;
3460         
3461         /* Done with values from previous mode; copy in new ones */
3462         gameMode = newGameMode;
3463         ModeHighlight();
3464         ics_gamenum = gamenum;
3465         if (gamenum == gs_gamenum) {
3466             int klen = strlen(gs_kind);
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468             sprintf(str, "ICS %s", gs_kind);
3469             gameInfo.event = StrSave(str);
3470         } else {
3471             gameInfo.event = StrSave("ICS game");
3472         }
3473         gameInfo.site = StrSave(appData.icsHost);
3474         gameInfo.date = PGNDate();
3475         gameInfo.round = StrSave("-");
3476         gameInfo.white = StrSave(white);
3477         gameInfo.black = StrSave(black);
3478         timeControl = basetime * 60 * 1000;
3479         timeControl_2 = 0;
3480         timeIncrement = increment * 1000;
3481         movesPerSession = 0;
3482         gameInfo.timeControl = TimeControlTagValue();
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );
3484   if (appData.debugMode) {
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487     setbuf(debugFP, NULL);
3488   }
3489
3490         gameInfo.outOfBook = NULL;
3491         
3492         /* Do we have the ratings? */
3493         if (strcmp(player1Name, white) == 0 &&
3494             strcmp(player2Name, black) == 0) {
3495             if (appData.debugMode)
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497                       player1Rating, player2Rating);
3498             gameInfo.whiteRating = player1Rating;
3499             gameInfo.blackRating = player2Rating;
3500         } else if (strcmp(player2Name, white) == 0 &&
3501                    strcmp(player1Name, black) == 0) {
3502             if (appData.debugMode)
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504                       player2Rating, player1Rating);
3505             gameInfo.whiteRating = player2Rating;
3506             gameInfo.blackRating = player1Rating;
3507         }
3508         player1Name[0] = player2Name[0] = NULLCHAR;
3509
3510         /* Silence shouts if requested */
3511         if (appData.quietPlay &&
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513             SendToICS(ics_prefix);
3514             SendToICS("set shout 0\n");
3515         }
3516     }
3517     
3518     /* Deal with midgame name changes */
3519     if (!newGame) {
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521             if (gameInfo.white) free(gameInfo.white);
3522             gameInfo.white = StrSave(white);
3523         }
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525             if (gameInfo.black) free(gameInfo.black);
3526             gameInfo.black = StrSave(black);
3527         }
3528     }
3529     
3530     /* Throw away game result if anything actually changes in examine mode */
3531     if (gameMode == IcsExamining && !newGame) {
3532         gameInfo.result = GameUnfinished;
3533         if (gameInfo.resultDetails != NULL) {
3534             free(gameInfo.resultDetails);
3535             gameInfo.resultDetails = NULL;
3536         }
3537     }
3538     
3539     /* In pausing && IcsExamining mode, we ignore boards coming
3540        in if they are in a different variation than we are. */
3541     if (pauseExamInvalid) return;
3542     if (pausing && gameMode == IcsExamining) {
3543         if (moveNum <= pauseExamForwardMostMove) {
3544             pauseExamInvalid = TRUE;
3545             forwardMostMove = pauseExamForwardMostMove;
3546             return;
3547         }
3548     }
3549     
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3552   }
3553     /* Parse the board */
3554     for (k = 0; k < ranks; k++) {
3555       for (j = 0; j < files; j++)
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557       if(gameInfo.holdingsWidth > 1) {
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3560       }
3561     }
3562     CopyBoard(boards[moveNum], board);
3563     if (moveNum == 0) {
3564         startedFromSetupPosition =
3565           !CompareBoards(board, initialPosition);
3566         if(startedFromSetupPosition)
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3568     }
3569
3570     /* [HGM] Set castling rights. Take the outermost Rooks,
3571        to make it also work for FRC opening positions. Note that board12
3572        is really defective for later FRC positions, as it has no way to
3573        indicate which Rook can castle if they are on the same side of King.
3574        For the initial position we grant rights to the outermost Rooks,
3575        and remember thos rights, and we then copy them on positions
3576        later in an FRC game. This means WB might not recognize castlings with
3577        Rooks that have moved back to their original position as illegal,
3578        but in ICS mode that is not its job anyway.
3579     */
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3582
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584             if(board[0][i] == WhiteRook) j = i;
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[BOARD_HEIGHT-1][k] == bKing)
3601                 initialRights[5] = castlingRights[moveNum][5] = k;
3602     } else { int r;
3603         r = castlingRights[moveNum][0] = initialRights[0];
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605         r = castlingRights[moveNum][1] = initialRights[1];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607         r = castlingRights[moveNum][3] = initialRights[3];
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609         r = castlingRights[moveNum][4] = initialRights[4];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611         /* wildcastle kludge: always assume King has rights */
3612         r = castlingRights[moveNum][2] = initialRights[2];
3613         r = castlingRights[moveNum][5] = initialRights[5];
3614     }
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3617
3618     
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {
3621         /* This was an initial position from a move list, not
3622            the current position */
3623         return;
3624     }
3625     
3626     /* Update currentMove and known move number limits */
3627     newMove = newGame || moveNum > forwardMostMove;
3628
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631         takeback = forwardMostMove - moveNum;
3632         for (i = 0; i < takeback; i++) {
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634              SendToProgram("undo\n", &first);
3635         }
3636     }
3637
3638     if (newGame) {
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;
3640         if (gameMode == IcsExamining && moveNum == 0) {
3641           /* Workaround for ICS limitation: we are not told the wild
3642              type when starting to examine a game.  But if we ask for
3643              the move list, the move list header will tell us */
3644             ics_getting_history = H_REQUESTED;
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3646             SendToICS(str);
3647         }
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650         forwardMostMove = moveNum;
3651         if (!pausing || currentMove > forwardMostMove)
3652           currentMove = forwardMostMove;
3653     } else {
3654         /* New part of history that is not contiguous with old part */ 
3655         if (pausing && gameMode == IcsExamining) {
3656             pauseExamInvalid = TRUE;
3657             forwardMostMove = pauseExamForwardMostMove;
3658             return;
3659         }
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662             ics_getting_history = H_REQUESTED;
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664             SendToICS(str);
3665         }
3666     }
3667     
3668     /* Update the clocks */
3669     if (strchr(elapsed_time, '.')) {
3670       /* Time is in ms */
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3673     } else {
3674       /* Time is in seconds */
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3677     }
3678       
3679
3680 #if ZIPPY
3681     if (appData.zippyPlay && newGame &&
3682         gameMode != IcsObserving && gameMode != IcsIdle &&
3683         gameMode != IcsExamining)
3684       ZippyFirstBoard(moveNum, basetime, increment);
3685 #endif
3686     
3687     /* Put the move on the move list, first converting
3688        to canonical algebraic form. */
3689     if (moveNum > 0) {
3690   if (appData.debugMode) {
3691     if (appData.debugMode) { int f = forwardMostMove;
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3694     }
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698     setbuf(debugFP, NULL);
3699   }
3700         if (moveNum <= backwardMostMove) {
3701             /* We don't know what the board looked like before
3702                this move.  Punt. */
3703             strcpy(parseList[moveNum - 1], move_str);
3704             strcat(parseList[moveNum - 1], " ");
3705             strcat(parseList[moveNum - 1], elapsed_time);
3706             moveList[moveNum - 1][0] = NULLCHAR;
3707         } else if (strcmp(move_str, "none") == 0) {
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709             /* Again, we don't know what the board looked like;
3710                this is really the start of the game. */
3711             parseList[moveNum - 1][0] = NULLCHAR;
3712             moveList[moveNum - 1][0] = NULLCHAR;
3713             backwardMostMove = moveNum;
3714             startedFromSetupPosition = TRUE;
3715             fromX = fromY = toX = toY = -1;
3716         } else {
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3718           //                 So we parse the long-algebraic move string in stead of the SAN move
3719           int valid; char buf[MSG_SIZ], *prom;
3720
3721           // str looks something like "Q/a1-a2"; kill the slash
3722           if(str[1] == '/') 
3723                 sprintf(buf, "%c%s", str[0], str+2);
3724           else  strcpy(buf, str); // might be castling
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3726                 strcat(buf, prom); // long move lacks promo specification!
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728                 if(appData.debugMode) 
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730                 strcpy(move_str, buf);
3731           }
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)
3734                || ParseOneMove(buf, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);
3736           // end of long SAN patch
3737           if (valid) {
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3740                                      fromY, fromX, toY, toX, promoChar,
3741                                      parseList[moveNum-1]);
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743                              castlingRights[moveNum]) ) {
3744               case MT_NONE:
3745               case MT_STALEMATE:
3746               default:
3747                 break;
3748               case MT_CHECK:
3749                 if(gameInfo.variant != VariantShogi)
3750                     strcat(parseList[moveNum - 1], "+");
3751                 break;
3752               case MT_CHECKMATE:
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754                 strcat(parseList[moveNum - 1], "#");
3755                 break;
3756             }
3757             strcat(parseList[moveNum - 1], " ");
3758             strcat(parseList[moveNum - 1], elapsed_time);
3759             /* currentMoveString is set as a side-effect of ParseOneMove */
3760             strcpy(moveList[moveNum - 1], currentMoveString);
3761             strcat(moveList[moveNum - 1], "\n");
3762           } else {
3763             /* Move from ICS was illegal!?  Punt. */
3764   if (appData.debugMode) {
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3767   }
3768 #if 0
3769             if (appData.testLegality && appData.debugMode) {
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771                 DisplayError(str, 0);
3772             }
3773 #endif
3774             strcpy(parseList[moveNum - 1], move_str);
3775             strcat(parseList[moveNum - 1], " ");
3776             strcat(parseList[moveNum - 1], elapsed_time);
3777             moveList[moveNum - 1][0] = NULLCHAR;
3778             fromX = fromY = toX = toY = -1;
3779           }
3780         }
3781   if (appData.debugMode) {
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783     setbuf(debugFP, NULL);
3784   }
3785
3786 #if ZIPPY
3787         /* Send move to chess program (BEFORE animating it). */
3788         if (appData.zippyPlay && !newGame && newMove && 
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795                             move_str);
3796                     DisplayError(str, 0);
3797                 } else {
3798                     if (first.sendTime) {
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800                     }
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802                     if (firstMove && !bookHit) {
3803                         firstMove = FALSE;
3804                         if (first.useColors) {
3805                           SendToProgram(gameMode == IcsPlayingWhite ?
3806                                         "white\ngo\n" :
3807                                         "black\ngo\n", &first);
3808                         } else {
3809                           SendToProgram("go\n", &first);
3810                         }
3811                         first.maybeThinking = TRUE;
3812                     }
3813                 }
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817                 DisplayError(str, 0);
3818               } else {
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820                 SendMoveToProgram(moveNum - 1, &first);
3821               }
3822             }
3823         }
3824 #endif
3825     }
3826
3827     if (moveNum > 0 && !gotPremove) {
3828         /* If move comes from a remote source, animate it.  If it
3829            isn't remote, it will have already been animated. */
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832         }
3833         if (!pausing && appData.highlightLastMove) {
3834             SetHighlights(fromX, fromY, toX, toY);
3835         }
3836     }
3837     
3838     /* Start the clocks */
3839     whiteFlag = blackFlag = FALSE;
3840     appData.clockMode = !(basetime == 0 && increment == 0);
3841     if (ticking == 0) {
3842       ics_clock_paused = TRUE;
3843       StopClocks();
3844     } else if (ticking == 1) {
3845       ics_clock_paused = FALSE;
3846     }
3847     if (gameMode == IcsIdle ||
3848         relation == RELATION_OBSERVING_STATIC ||
3849         relation == RELATION_EXAMINING ||
3850         ics_clock_paused)
3851       DisplayBothClocks();
3852     else
3853       StartClocks();
3854     
3855     /* Display opponents and material strengths */
3856     if (gameInfo.variant != VariantBughouse &&
3857         gameInfo.variant != VariantCrazyhouse) {
3858         if (tinyLayout || smallLayout) {
3859             if(gameInfo.variant == VariantNormal)
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3862                     basetime, increment);
3863             else
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3866                     basetime, increment, (int) gameInfo.variant);
3867         } else {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, VariantName(gameInfo.variant));
3876         }
3877         DisplayTitle(str);
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3880   }
3881     }
3882
3883    
3884     /* Display the board */
3885     if (!pausing) {
3886       
3887       if (appData.premove)
3888           if (!gotPremove || 
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891               ClearPremoveHighlights();
3892
3893       DrawPosition(FALSE, boards[currentMove]);
3894       DisplayMove(moveNum - 1);
3895       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3898         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3899       }
3900     }
3901
3902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3903 #if ZIPPY
3904     if(bookHit) { // [HGM] book: simulate book reply
3905         static char bookMove[MSG_SIZ]; // a bit generous?
3906
3907         programStats.nodes = programStats.depth = programStats.time = 
3908         programStats.score = programStats.got_only_move = 0;
3909         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3910
3911         strcpy(bookMove, "move ");
3912         strcat(bookMove, bookHit);
3913         HandleMachineMove(bookMove, &first);
3914     }
3915 #endif
3916 }
3917
3918 void
3919 GetMoveListEvent()
3920 {
3921     char buf[MSG_SIZ];
3922     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923         ics_getting_history = H_REQUESTED;
3924         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3925         SendToICS(buf);
3926     }
3927 }
3928
3929 void
3930 AnalysisPeriodicEvent(force)
3931      int force;
3932 {
3933     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934          && !force) || !appData.periodicUpdates)
3935       return;
3936
3937     /* Send . command to Crafty to collect stats */
3938     SendToProgram(".\n", &first);
3939
3940     /* Don't send another until we get a response (this makes
3941        us stop sending to old Crafty's which don't understand
3942        the "." command (sending illegal cmds resets node count & time,
3943        which looks bad)) */
3944     programStats.ok_to_send = 0;
3945 }
3946
3947 void
3948 SendMoveToProgram(moveNum, cps)
3949      int moveNum;
3950      ChessProgramState *cps;
3951 {
3952     char buf[MSG_SIZ];
3953
3954     if (cps->useUsermove) {
3955       SendToProgram("usermove ", cps);
3956     }
3957     if (cps->useSAN) {
3958       char *space;
3959       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3960         int len = space - parseList[moveNum];
3961         memcpy(buf, parseList[moveNum], len);
3962         buf[len++] = '\n';
3963         buf[len] = NULLCHAR;
3964       } else {
3965         sprintf(buf, "%s\n", parseList[moveNum]);
3966       }
3967       SendToProgram(buf, cps);
3968     } else {
3969       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3970         AlphaRank(moveList[moveNum], 4);
3971         SendToProgram(moveList[moveNum], cps);
3972         AlphaRank(moveList[moveNum], 4); // and back
3973       } else
3974       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3975        * the engine. It would be nice to have a better way to identify castle 
3976        * moves here. */
3977       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3978                                                                          && cps->useOOCastle) {
3979         int fromX = moveList[moveNum][0] - AAA; 
3980         int fromY = moveList[moveNum][1] - ONE;
3981         int toX = moveList[moveNum][2] - AAA; 
3982         int toY = moveList[moveNum][3] - ONE;
3983         if((boards[moveNum][fromY][fromX] == WhiteKing 
3984             && boards[moveNum][toY][toX] == WhiteRook)
3985            || (boards[moveNum][fromY][fromX] == BlackKing 
3986                && boards[moveNum][toY][toX] == BlackRook)) {
3987           if(toX > fromX) SendToProgram("O-O\n", cps);
3988           else SendToProgram("O-O-O\n", cps);
3989         }
3990         else SendToProgram(moveList[moveNum], cps);
3991       }
3992       else SendToProgram(moveList[moveNum], cps);
3993       /* End of additions by Tord */
3994     }
3995
3996     /* [HGM] setting up the opening has brought engine in force mode! */
3997     /*       Send 'go' if we are in a mode where machine should play. */
3998     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3999         (gameMode == TwoMachinesPlay   ||
4000 #ifdef ZIPPY
4001          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4002 #endif
4003          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4004         SendToProgram("go\n", cps);
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "(extra)\n");
4007   }
4008     }
4009     setboardSpoiledMachineBlack = 0;
4010 }
4011
4012 void
4013 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4014      ChessMove moveType;
4015      int fromX, fromY, toX, toY;
4016 {
4017     char user_move[MSG_SIZ];
4018
4019     switch (moveType) {
4020       default:
4021         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4022                 (int)moveType, fromX, fromY, toX, toY);
4023         DisplayError(user_move + strlen("say "), 0);
4024         break;
4025       case WhiteKingSideCastle:
4026       case BlackKingSideCastle:
4027       case WhiteQueenSideCastleWild:
4028       case BlackQueenSideCastleWild:
4029       /* PUSH Fabien */
4030       case WhiteHSideCastleFR:
4031       case BlackHSideCastleFR:
4032       /* POP Fabien */
4033         sprintf(user_move, "o-o\n");
4034         break;
4035       case WhiteQueenSideCastle:
4036       case BlackQueenSideCastle:
4037       case WhiteKingSideCastleWild:
4038       case BlackKingSideCastleWild:
4039       /* PUSH Fabien */
4040       case WhiteASideCastleFR:
4041       case BlackASideCastleFR:
4042       /* POP Fabien */
4043         sprintf(user_move, "o-o-o\n");
4044         break;
4045       case WhitePromotionQueen:
4046       case BlackPromotionQueen:
4047       case WhitePromotionRook:
4048       case BlackPromotionRook:
4049       case WhitePromotionBishop:
4050       case BlackPromotionBishop:
4051       case WhitePromotionKnight:
4052       case BlackPromotionKnight:
4053       case WhitePromotionKing:
4054       case BlackPromotionKing:
4055       case WhitePromotionChancellor:
4056       case BlackPromotionChancellor:
4057       case WhitePromotionArchbishop:
4058       case BlackPromotionArchbishop:
4059         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4060             sprintf(user_move, "%c%c%c%c=%c\n",
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4062                 PieceToChar(WhiteFerz));
4063         else if(gameInfo.variant == VariantGreat)
4064             sprintf(user_move, "%c%c%c%c=%c\n",
4065                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4066                 PieceToChar(WhiteMan));
4067         else
4068             sprintf(user_move, "%c%c%c%c=%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070                 PieceToChar(PromoPiece(moveType)));
4071         break;
4072       case WhiteDrop:
4073       case BlackDrop:
4074         sprintf(user_move, "%c@%c%c\n",
4075                 ToUpper(PieceToChar((ChessSquare) fromX)),
4076                 AAA + toX, ONE + toY);
4077         break;
4078       case NormalMove:
4079       case WhiteCapturesEnPassant:
4080       case BlackCapturesEnPassant:
4081       case IllegalMove:  /* could be a variant we don't quite understand */
4082         sprintf(user_move, "%c%c%c%c\n",
4083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4084         break;
4085     }
4086     SendToICS(user_move);
4087 }
4088
4089 void
4090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4091      int rf, ff, rt, ft;
4092      char promoChar;
4093      char move[7];
4094 {
4095     if (rf == DROP_RANK) {
4096         sprintf(move, "%c@%c%c\n",
4097                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4098     } else {
4099         if (promoChar == 'x' || promoChar == NULLCHAR) {
4100             sprintf(move, "%c%c%c%c\n",
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4102         } else {
4103             sprintf(move, "%c%c%c%c%c\n",
4104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4105         }
4106     }
4107 }
4108
4109 void
4110 ProcessICSInitScript(f)
4111      FILE *f;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     while (fgets(buf, MSG_SIZ, f)) {
4116         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4117     }
4118
4119     fclose(f);
4120 }
4121
4122
4123 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4124 void
4125 AlphaRank(char *move, int n)
4126 {
4127 //    char *p = move, c; int x, y;
4128
4129     if (appData.debugMode) {
4130         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4131     }
4132
4133     if(move[1]=='*' && 
4134        move[2]>='0' && move[2]<='9' &&
4135        move[3]>='a' && move[3]<='x'    ) {
4136         move[1] = '@';
4137         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4138         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4139     } else
4140     if(move[0]>='0' && move[0]<='9' &&
4141        move[1]>='a' && move[1]<='x' &&
4142        move[2]>='0' && move[2]<='9' &&
4143        move[3]>='a' && move[3]<='x'    ) {
4144         /* input move, Shogi -> normal */
4145         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4146         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4149     } else
4150     if(move[1]=='@' &&
4151        move[3]>='0' && move[3]<='9' &&
4152        move[2]>='a' && move[2]<='x'    ) {
4153         move[1] = '*';
4154         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4156     } else
4157     if(
4158        move[0]>='a' && move[0]<='x' &&
4159        move[3]>='0' && move[3]<='9' &&
4160        move[2]>='a' && move[2]<='x'    ) {
4161          /* output move, normal -> Shogi */
4162         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4163         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4167     }
4168     if (appData.debugMode) {
4169         fprintf(debugFP, "   out = '%s'\n", move);
4170     }
4171 }
4172
4173 /* Parser for moves from gnuchess, ICS, or user typein box */
4174 Boolean
4175 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4176      char *move;
4177      int moveNum;
4178      ChessMove *moveType;
4179      int *fromX, *fromY, *toX, *toY;
4180      char *promoChar;
4181 {       
4182     if (appData.debugMode) {
4183         fprintf(debugFP, "move to parse: %s\n", move);
4184     }
4185     *moveType = yylexstr(moveNum, move);
4186
4187     switch (*moveType) {
4188       case WhitePromotionChancellor:
4189       case BlackPromotionChancellor:
4190       case WhitePromotionArchbishop:
4191       case BlackPromotionArchbishop:
4192       case WhitePromotionQueen:
4193       case BlackPromotionQueen:
4194       case WhitePromotionRook:
4195       case BlackPromotionRook:
4196       case WhitePromotionBishop:
4197       case BlackPromotionBishop:
4198       case WhitePromotionKnight:
4199       case BlackPromotionKnight:
4200       case WhitePromotionKing:
4201       case BlackPromotionKing:
4202       case NormalMove:
4203       case WhiteCapturesEnPassant:
4204       case BlackCapturesEnPassant:
4205       case WhiteKingSideCastle:
4206       case WhiteQueenSideCastle:
4207       case BlackKingSideCastle:
4208       case BlackQueenSideCastle:
4209       case WhiteKingSideCastleWild:
4210       case WhiteQueenSideCastleWild:
4211       case BlackKingSideCastleWild:
4212       case BlackQueenSideCastleWild:
4213       /* Code added by Tord: */
4214       case WhiteHSideCastleFR:
4215       case WhiteASideCastleFR:
4216       case BlackHSideCastleFR:
4217       case BlackASideCastleFR:
4218       /* End of code added by Tord */
4219       case IllegalMove:         /* bug or odd chess variant */
4220         *fromX = currentMoveString[0] - AAA;
4221         *fromY = currentMoveString[1] - ONE;
4222         *toX = currentMoveString[2] - AAA;
4223         *toY = currentMoveString[3] - ONE;
4224         *promoChar = currentMoveString[4];
4225         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4226             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4227     if (appData.debugMode) {
4228         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4229     }
4230             *fromX = *fromY = *toX = *toY = 0;
4231             return FALSE;
4232         }
4233         if (appData.testLegality) {
4234           return (*moveType != IllegalMove);
4235         } else {
4236           return !(fromX == fromY && toX == toY);
4237         }
4238
4239       case WhiteDrop:
4240       case BlackDrop:
4241         *fromX = *moveType == WhiteDrop ?
4242           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4243           (int) CharToPiece(ToLower(currentMoveString[0]));
4244         *fromY = DROP_RANK;
4245         *toX = currentMoveString[2] - AAA;
4246         *toY = currentMoveString[3] - ONE;
4247         *promoChar = NULLCHAR;
4248         return TRUE;
4249
4250       case AmbiguousMove:
4251       case ImpossibleMove:
4252       case (ChessMove) 0:       /* end of file */
4253       case ElapsedTime:
4254       case Comment:
4255       case PGNTag:
4256       case NAG:
4257       case WhiteWins:
4258       case BlackWins:
4259       case GameIsDrawn:
4260       default:
4261     if (appData.debugMode) {
4262         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4263     }
4264         /* bug? */
4265         *fromX = *fromY = *toX = *toY = 0;
4266         *promoChar = NULLCHAR;
4267         return FALSE;
4268     }
4269 }
4270
4271 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4272 // All positions will have equal probability, but the current method will not provide a unique
4273 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4274 #define DARK 1
4275 #define LITE 2
4276 #define ANY 3
4277
4278 int squaresLeft[4];
4279 int piecesLeft[(int)BlackPawn];
4280 int seed, nrOfShuffles;
4281
4282 void GetPositionNumber()
4283 {       // sets global variable seed
4284         int i;
4285
4286         seed = appData.defaultFrcPosition;
4287         if(seed < 0) { // randomize based on time for negative FRC position numbers
4288                 for(i=0; i<50; i++) seed += random();
4289                 seed = random() ^ random() >> 8 ^ random() << 8;
4290                 if(seed<0) seed = -seed;
4291         }
4292 }
4293
4294 int put(Board board, int pieceType, int rank, int n, int shade)
4295 // put the piece on the (n-1)-th empty squares of the given shade
4296 {
4297         int i;
4298
4299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4300                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4301                         board[rank][i] = (ChessSquare) pieceType;
4302                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4303                         squaresLeft[ANY]--;
4304                         piecesLeft[pieceType]--; 
4305                         return i;
4306                 }
4307         }
4308         return -1;
4309 }
4310
4311
4312 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4313 // calculate where the next piece goes, (any empty square), and put it there
4314 {
4315         int i;
4316
4317         i = seed % squaresLeft[shade];
4318         nrOfShuffles *= squaresLeft[shade];
4319         seed /= squaresLeft[shade];
4320         put(board, pieceType, rank, i, shade);
4321 }
4322
4323 void AddTwoPieces(Board board, int pieceType, int rank)
4324 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4325 {
4326         int i, n=squaresLeft[ANY], j=n-1, k;
4327
4328         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4329         i = seed % k;  // pick one
4330         nrOfShuffles *= k;
4331         seed /= k;
4332         while(i >= j) i -= j--;
4333         j = n - 1 - j; i += j;
4334         put(board, pieceType, rank, j, ANY);
4335         put(board, pieceType, rank, i, ANY);
4336 }
4337
4338 void SetUpShuffle(Board board, int number)
4339 {
4340         int i, p, first=1;
4341
4342         GetPositionNumber(); nrOfShuffles = 1;
4343
4344         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4345         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4346         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4347
4348         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4349
4350         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4351             p = (int) board[0][i];
4352             if(p < (int) BlackPawn) piecesLeft[p] ++;
4353             board[0][i] = EmptySquare;
4354         }
4355
4356         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4357             // shuffles restricted to allow normal castling put KRR first
4358             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4359                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4360             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4361                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4363                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4365                 put(board, WhiteRook, 0, 0, ANY);
4366             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4367         }
4368
4369         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4370             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4371             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4372                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4373                 while(piecesLeft[p] >= 2) {
4374                     AddOnePiece(board, p, 0, LITE);
4375                     AddOnePiece(board, p, 0, DARK);
4376                 }
4377                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4378             }
4379
4380         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4381             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4382             // but we leave King and Rooks for last, to possibly obey FRC restriction
4383             if(p == (int)WhiteRook) continue;
4384             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4385             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4386         }
4387
4388         // now everything is placed, except perhaps King (Unicorn) and Rooks
4389
4390         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4391             // Last King gets castling rights
4392             while(piecesLeft[(int)WhiteUnicorn]) {
4393                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4394                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4395             }
4396
4397             while(piecesLeft[(int)WhiteKing]) {
4398                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4399                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4400             }
4401
4402
4403         } else {
4404             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4405             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4406         }
4407
4408         // Only Rooks can be left; simply place them all
4409         while(piecesLeft[(int)WhiteRook]) {
4410                 i = put(board, WhiteRook, 0, 0, ANY);
4411                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4412                         if(first) {
4413                                 first=0;
4414                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4415                         }
4416                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4417                 }
4418         }
4419         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4420             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4421         }
4422
4423         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4424 }
4425
4426 int SetCharTable( char *table, const char * map )
4427 /* [HGM] moved here from winboard.c because of its general usefulness */
4428 /*       Basically a safe strcpy that uses the last character as King */
4429 {
4430     int result = FALSE; int NrPieces;
4431
4432     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4433                     && NrPieces >= 12 && !(NrPieces&1)) {
4434         int i; /* [HGM] Accept even length from 12 to 34 */
4435
4436         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4437         for( i=0; i<NrPieces/2-1; i++ ) {
4438             table[i] = map[i];
4439             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4440         }
4441         table[(int) WhiteKing]  = map[NrPieces/2-1];
4442         table[(int) BlackKing]  = map[NrPieces-1];
4443
4444         result = TRUE;
4445     }
4446
4447     return result;
4448 }
4449
4450 void Prelude(Board board)
4451 {       // [HGM] superchess: random selection of exo-pieces
4452         int i, j, k; ChessSquare p; 
4453         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4454
4455         GetPositionNumber(); // use FRC position number
4456
4457         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4458             SetCharTable(pieceToChar, appData.pieceToCharTable);
4459             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4460                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4461         }
4462
4463         j = seed%4;                 seed /= 4; 
4464         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4467         j = seed%3 + (seed%3 >= j); seed /= 3; 
4468         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4469         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4470         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4471         j = seed%3;                 seed /= 3; 
4472         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4473         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4474         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4475         j = seed%2 + (seed%2 >= j); seed /= 2; 
4476         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4477         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4478         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4479         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4480         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4481         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4482         put(board, exoPieces[0],    0, 0, ANY);
4483         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4484 }
4485
4486 void
4487 InitPosition(redraw)
4488      int redraw;
4489 {
4490     ChessSquare (* pieces)[BOARD_SIZE];
4491     int i, j, pawnRow, overrule,
4492     oldx = gameInfo.boardWidth,
4493     oldy = gameInfo.boardHeight,
4494     oldh = gameInfo.holdingsWidth,
4495     oldv = gameInfo.variant;
4496
4497     currentMove = forwardMostMove = backwardMostMove = 0;
4498     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4499
4500     /* [AS] Initialize pv info list [HGM] and game status */
4501     {
4502         for( i=0; i<MAX_MOVES; i++ ) {
4503             pvInfoList[i].depth = 0;
4504             epStatus[i]=EP_NONE;
4505             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4506         }
4507
4508         initialRulePlies = 0; /* 50-move counter start */
4509
4510         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4511         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4512     }
4513
4514     
4515     /* [HGM] logic here is completely changed. In stead of full positions */
4516     /* the initialized data only consist of the two backranks. The switch */
4517     /* selects which one we will use, which is than copied to the Board   */
4518     /* initialPosition, which for the rest is initialized by Pawns and    */
4519     /* empty squares. This initial position is then copied to boards[0],  */
4520     /* possibly after shuffling, so that it remains available.            */
4521
4522     gameInfo.holdingsWidth = 0; /* default board sizes */
4523     gameInfo.boardWidth    = 8;
4524     gameInfo.boardHeight   = 8;
4525     gameInfo.holdingsSize  = 0;
4526     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4527     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4528     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4529
4530     switch (gameInfo.variant) {
4531     case VariantFischeRandom:
4532       shuffleOpenings = TRUE;
4533     default:
4534       pieces = FIDEArray;
4535       break;
4536     case VariantShatranj:
4537       pieces = ShatranjArray;
4538       nrCastlingRights = 0;
4539       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4540       break;
4541     case VariantTwoKings:
4542       pieces = twoKingsArray;
4543       break;
4544     case VariantCapaRandom:
4545       shuffleOpenings = TRUE;
4546     case VariantCapablanca:
4547       pieces = CapablancaArray;
4548       gameInfo.boardWidth = 10;
4549       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4550       break;
4551     case VariantGothic:
4552       pieces = GothicArray;
4553       gameInfo.boardWidth = 10;
4554       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4555       break;
4556     case VariantJanus:
4557       pieces = JanusArray;
4558       gameInfo.boardWidth = 10;
4559       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4560       nrCastlingRights = 6;
4561         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4562         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4563         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4564         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4565         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4566         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4567       break;
4568     case VariantFalcon:
4569       pieces = FalconArray;
4570       gameInfo.boardWidth = 10;
4571       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4572       break;
4573     case VariantXiangqi:
4574       pieces = XiangqiArray;
4575       gameInfo.boardWidth  = 9;
4576       gameInfo.boardHeight = 10;
4577       nrCastlingRights = 0;
4578       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4579       break;
4580     case VariantShogi:
4581       pieces = ShogiArray;
4582       gameInfo.boardWidth  = 9;
4583       gameInfo.boardHeight = 9;
4584       gameInfo.holdingsSize = 7;
4585       nrCastlingRights = 0;
4586       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4587       break;
4588     case VariantCourier:
4589       pieces = CourierArray;
4590       gameInfo.boardWidth  = 12;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4593       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4594       break;
4595     case VariantKnightmate:
4596       pieces = KnightmateArray;
4597       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4598       break;
4599     case VariantFairy:
4600       pieces = fairyArray;
4601       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4602       break;
4603     case VariantGreat:
4604       pieces = GreatArray;
4605       gameInfo.boardWidth = 10;
4606       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4607       gameInfo.holdingsSize = 8;
4608       break;
4609     case VariantSuper:
4610       pieces = FIDEArray;
4611       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4612       gameInfo.holdingsSize = 8;
4613       startedFromSetupPosition = TRUE;
4614       break;
4615     case VariantCrazyhouse:
4616     case VariantBughouse:
4617       pieces = FIDEArray;
4618       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4619       gameInfo.holdingsSize = 5;
4620       break;
4621     case VariantWildCastle:
4622       pieces = FIDEArray;
4623       /* !!?shuffle with kings guaranteed to be on d or e file */
4624       shuffleOpenings = 1;
4625       break;
4626     case VariantNoCastle:
4627       pieces = FIDEArray;
4628       nrCastlingRights = 0;
4629       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4630       /* !!?unconstrained back-rank shuffle */
4631       shuffleOpenings = 1;
4632       break;
4633     }
4634
4635     overrule = 0;
4636     if(appData.NrFiles >= 0) {
4637         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4638         gameInfo.boardWidth = appData.NrFiles;
4639     }
4640     if(appData.NrRanks >= 0) {
4641         gameInfo.boardHeight = appData.NrRanks;
4642     }
4643     if(appData.holdingsSize >= 0) {
4644         i = appData.holdingsSize;
4645         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4646         gameInfo.holdingsSize = i;
4647     }
4648     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4649     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4650         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4651
4652     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4653     if(pawnRow < 1) pawnRow = 1;
4654
4655     /* User pieceToChar list overrules defaults */
4656     if(appData.pieceToCharTable != NULL)
4657         SetCharTable(pieceToChar, appData.pieceToCharTable);
4658
4659     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4660
4661         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4662             s = (ChessSquare) 0; /* account holding counts in guard band */
4663         for( i=0; i<BOARD_HEIGHT; i++ )
4664             initialPosition[i][j] = s;
4665
4666         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4667         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4668         initialPosition[pawnRow][j] = WhitePawn;
4669         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4670         if(gameInfo.variant == VariantXiangqi) {
4671             if(j&1) {
4672                 initialPosition[pawnRow][j] = 
4673                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4674                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4675                    initialPosition[2][j] = WhiteCannon;
4676                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4677                 }
4678             }
4679         }
4680         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4681     }
4682     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4683
4684             j=BOARD_LEFT+1;
4685             initialPosition[1][j] = WhiteBishop;
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4687             j=BOARD_RGHT-2;
4688             initialPosition[1][j] = WhiteRook;
4689             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4690     }
4691
4692     if( nrCastlingRights == -1) {
4693         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4694         /*       This sets default castling rights from none to normal corners   */
4695         /* Variants with other castling rights must set them themselves above    */
4696         nrCastlingRights = 6;
4697        
4698         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4699         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4700         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4701         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4702         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4703         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4704      }
4705
4706      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4707      if(gameInfo.variant == VariantGreat) { // promotion commoners
4708         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4709         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4710         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4711         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4712      }
4713 #if 0
4714     if(gameInfo.variant == VariantFischeRandom) {
4715       if( appData.defaultFrcPosition < 0 ) {
4716         ShuffleFRC( initialPosition );
4717       }
4718       else {
4719         SetupFRC( initialPosition, appData.defaultFrcPosition );
4720       }
4721       startedFromSetupPosition = TRUE;
4722     } else 
4723 #else
4724   if (appData.debugMode) {
4725     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4726   }
4727     if(shuffleOpenings) {
4728         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4729         startedFromSetupPosition = TRUE;
4730     }
4731 #endif
4732     if(startedFromPositionFile) {
4733       /* [HGM] loadPos: use PositionFile for every new game */
4734       CopyBoard(initialPosition, filePosition);
4735       for(i=0; i<nrCastlingRights; i++)
4736           castlingRights[0][i] = initialRights[i] = fileRights[i];
4737       startedFromSetupPosition = TRUE;
4738     }
4739
4740     CopyBoard(boards[0], initialPosition);
4741     if(oldx != gameInfo.boardWidth ||
4742        oldy != gameInfo.boardHeight ||
4743        oldh != gameInfo.holdingsWidth
4744 #ifdef GOTHIC
4745        || oldv == VariantGothic ||        // For licensing popups
4746        gameInfo.variant == VariantGothic
4747 #endif
4748 #ifdef FALCON
4749        || oldv == VariantFalcon ||
4750        gameInfo.variant == VariantFalcon
4751 #endif
4752                                          )
4753       {
4754             InitDrawingSizes(-2 ,0);
4755       }
4756
4757     if (redraw)
4758       DrawPosition(TRUE, boards[currentMove]);
4759
4760 }
4761
4762 void
4763 SendBoard(cps, moveNum)
4764      ChessProgramState *cps;
4765      int moveNum;
4766 {
4767     char message[MSG_SIZ];
4768     
4769     if (cps->useSetboard) {
4770       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771       sprintf(message, "setboard %s\n", fen);
4772       SendToProgram(message, cps);
4773       free(fen);
4774
4775     } else {
4776       ChessSquare *bp;
4777       int i, j;
4778       /* Kludge to set black to move, avoiding the troublesome and now
4779        * deprecated "black" command.
4780        */
4781       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782
4783       SendToProgram("edit\n", cps);
4784       SendToProgram("#\n", cps);
4785       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786         bp = &boards[moveNum][i][BOARD_LEFT];
4787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788           if ((int) *bp < (int) BlackPawn) {
4789             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4790                     AAA + j, ONE + i);
4791             if(message[0] == '+' || message[0] == '~') {
4792                 sprintf(message, "%c%c%c+\n",
4793                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4794                         AAA + j, ONE + i);
4795             }
4796             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797                 message[1] = BOARD_RGHT   - 1 - j + '1';
4798                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799             }
4800             SendToProgram(message, cps);
4801           }
4802         }
4803       }
4804     
4805       SendToProgram("c\n", cps);
4806       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807         bp = &boards[moveNum][i][BOARD_LEFT];
4808         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809           if (((int) *bp != (int) EmptySquare)
4810               && ((int) *bp >= (int) BlackPawn)) {
4811             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826     
4827       SendToProgram(".\n", cps);
4828     }
4829     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4830 }
4831
4832 int
4833 IsPromotion(fromX, fromY, toX, toY)
4834      int fromX, fromY, toX, toY;
4835 {
4836     /* [HGM] add Shogi promotions */
4837     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4838     ChessSquare piece;
4839
4840     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842    /* [HGM] Note to self: line above also weeds out drops */
4843     piece = boards[currentMove][fromY][fromX];
4844     if(gameInfo.variant == VariantShogi) {
4845         promotionZoneSize = 3;
4846         highestPromotingPiece = (int)WhiteKing;
4847         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848            and if in normal chess we then allow promotion to King, why not
4849            allow promotion of other piece in Shogi?                         */
4850     }
4851     if((int)piece >= BlackPawn) {
4852         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853              return FALSE;
4854         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855     } else {
4856         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4857            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858     }
4859     return ( (int)piece <= highestPromotingPiece );
4860 }
4861
4862 int
4863 InPalace(row, column)
4864      int row, column;
4865 {   /* [HGM] for Xiangqi */
4866     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867          column < (BOARD_WIDTH + 4)/2 &&
4868          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4869     return FALSE;
4870 }
4871
4872 int
4873 PieceForSquare (x, y)
4874      int x;
4875      int y;
4876 {
4877   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4878      return -1;
4879   else
4880      return boards[currentMove][y][x];
4881 }
4882
4883 int
4884 OKToStartUserMove(x, y)
4885      int x, y;
4886 {
4887     ChessSquare from_piece;
4888     int white_piece;
4889
4890     if (matchMode) return FALSE;
4891     if (gameMode == EditPosition) return TRUE;
4892
4893     if (x >= 0 && y >= 0)
4894       from_piece = boards[currentMove][y][x];
4895     else
4896       from_piece = EmptySquare;
4897
4898     if (from_piece == EmptySquare) return FALSE;
4899
4900     white_piece = (int)from_piece >= (int)WhitePawn &&
4901       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4902
4903     switch (gameMode) {
4904       case PlayFromGameFile:
4905       case AnalyzeFile:
4906       case TwoMachinesPlay:
4907       case EndOfGame:
4908         return FALSE;
4909
4910       case IcsObserving:
4911       case IcsIdle:
4912         return FALSE;
4913
4914       case MachinePlaysWhite:
4915       case IcsPlayingBlack:
4916         if (appData.zippyPlay) return FALSE;
4917         if (white_piece) {
4918             DisplayMoveError(_("You are playing Black"));
4919             return FALSE;
4920         }
4921         break;
4922
4923       case MachinePlaysBlack:
4924       case IcsPlayingWhite:
4925         if (appData.zippyPlay) return FALSE;
4926         if (!white_piece) {
4927             DisplayMoveError(_("You are playing White"));
4928             return FALSE;
4929         }
4930         break;
4931
4932       case EditGame:
4933         if (!white_piece && WhiteOnMove(currentMove)) {
4934             DisplayMoveError(_("It is White's turn"));
4935             return FALSE;
4936         }           
4937         if (white_piece && !WhiteOnMove(currentMove)) {
4938             DisplayMoveError(_("It is Black's turn"));
4939             return FALSE;
4940         }           
4941         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942             /* Editing correspondence game history */
4943             /* Could disallow this or prompt for confirmation */
4944             cmailOldMove = -1;
4945         }
4946         if (currentMove < forwardMostMove) {
4947             /* Discarding moves */
4948             /* Could prompt for confirmation here,
4949                but I don't think that's such a good idea */
4950             forwardMostMove = currentMove;
4951         }
4952         break;
4953
4954       case BeginningOfGame:
4955         if (appData.icsActive) return FALSE;
4956         if (!appData.noChessProgram) {
4957             if (!white_piece) {
4958                 DisplayMoveError(_("You are playing White"));
4959                 return FALSE;
4960             }
4961         }
4962         break;
4963         
4964       case Training:
4965         if (!white_piece && WhiteOnMove(currentMove)) {
4966             DisplayMoveError(_("It is White's turn"));
4967             return FALSE;
4968         }           
4969         if (white_piece && !WhiteOnMove(currentMove)) {
4970             DisplayMoveError(_("It is Black's turn"));
4971             return FALSE;
4972         }           
4973         break;
4974
4975       default:
4976       case IcsExamining:
4977         break;
4978     }
4979     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980         && gameMode != AnalyzeFile && gameMode != Training) {
4981         DisplayMoveError(_("Displayed position is not current"));
4982         return FALSE;
4983     }
4984     return TRUE;
4985 }
4986
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4992
4993
4994 ChessMove
4995 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4996      int fromX, fromY, toX, toY;
4997      int promoChar;
4998 {
4999     ChessMove moveType;
5000     ChessSquare pdown, pup;
5001
5002     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003     if ((fromX == toX) && (fromY == toY)) {
5004         return ImpossibleMove;
5005     }
5006
5007     /* [HGM] suppress all moves into holdings area and guard band */
5008     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009             return ImpossibleMove;
5010
5011     /* [HGM] <sameColor> moved to here from winboard.c */
5012     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5013     pdown = boards[currentMove][fromY][fromX];
5014     pup = boards[currentMove][toY][toX];
5015     if (    gameMode != EditPosition &&
5016             (WhitePawn <= pdown && pdown < BlackPawn &&
5017              WhitePawn <= pup && pup < BlackPawn  ||
5018              BlackPawn <= pdown && pdown < EmptySquare &&
5019              BlackPawn <= pup && pup < EmptySquare 
5020             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5023         )           )
5024          return ImpossibleMove;
5025
5026     /* Check if the user is playing in turn.  This is complicated because we
5027        let the user "pick up" a piece before it is his turn.  So the piece he
5028        tried to pick up may have been captured by the time he puts it down!
5029        Therefore we use the color the user is supposed to be playing in this
5030        test, not the color of the piece that is currently on the starting
5031        square---except in EditGame mode, where the user is playing both
5032        sides; fortunately there the capture race can't happen.  (It can
5033        now happen in IcsExamining mode, but that's just too bad.  The user
5034        will get a somewhat confusing message in that case.)
5035        */
5036
5037     switch (gameMode) {
5038       case PlayFromGameFile:
5039       case AnalyzeFile:
5040       case TwoMachinesPlay:
5041       case EndOfGame:
5042       case IcsObserving:
5043       case IcsIdle:
5044         /* We switched into a game mode where moves are not accepted,
5045            perhaps while the mouse button was down. */
5046         return ImpossibleMove;
5047
5048       case MachinePlaysWhite:
5049         /* User is moving for Black */
5050         if (WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is White's turn"));
5052             return ImpossibleMove;
5053         }
5054         break;
5055
5056       case MachinePlaysBlack:
5057         /* User is moving for White */
5058         if (!WhiteOnMove(currentMove)) {
5059             DisplayMoveError(_("It is Black's turn"));
5060             return ImpossibleMove;
5061         }
5062         break;
5063
5064       case EditGame:
5065       case IcsExamining:
5066       case BeginningOfGame:
5067       case AnalyzeMode:
5068       case Training:
5069         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5070             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5071             /* User is moving for Black */
5072             if (WhiteOnMove(currentMove)) {
5073                 DisplayMoveError(_("It is White's turn"));
5074                 return ImpossibleMove;
5075             }
5076         } else {
5077             /* User is moving for White */
5078             if (!WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is Black's turn"));
5080                 return ImpossibleMove;
5081             }
5082         }
5083         break;
5084
5085       case IcsPlayingBlack:
5086         /* User is moving for Black */
5087         if (WhiteOnMove(currentMove)) {
5088             if (!appData.premove) {
5089                 DisplayMoveError(_("It is White's turn"));
5090             } else if (toX >= 0 && toY >= 0) {
5091                 premoveToX = toX;
5092                 premoveToY = toY;
5093                 premoveFromX = fromX;
5094                 premoveFromY = fromY;
5095                 premovePromoChar = promoChar;
5096                 gotPremove = 1;
5097                 if (appData.debugMode) 
5098                     fprintf(debugFP, "Got premove: fromX %d,"
5099                             "fromY %d, toX %d, toY %d\n",
5100                             fromX, fromY, toX, toY);
5101             }
5102             return ImpossibleMove;
5103         }
5104         break;
5105
5106       case IcsPlayingWhite:
5107         /* User is moving for White */
5108         if (!WhiteOnMove(currentMove)) {
5109             if (!appData.premove) {
5110                 DisplayMoveError(_("It is Black's turn"));
5111             } else if (toX >= 0 && toY >= 0) {
5112                 premoveToX = toX;
5113                 premoveToY = toY;
5114                 premoveFromX = fromX;
5115                 premoveFromY = fromY;
5116                 premovePromoChar = promoChar;
5117                 gotPremove = 1;
5118                 if (appData.debugMode) 
5119                     fprintf(debugFP, "Got premove: fromX %d,"
5120                             "fromY %d, toX %d, toY %d\n",
5121                             fromX, fromY, toX, toY);
5122             }
5123             return ImpossibleMove;
5124         }
5125         break;
5126
5127       default:
5128         break;
5129
5130       case EditPosition:
5131         /* EditPosition, empty square, or different color piece;
5132            click-click move is possible */
5133         if (toX == -2 || toY == -2) {
5134             boards[0][fromY][fromX] = EmptySquare;
5135             return AmbiguousMove;
5136         } else if (toX >= 0 && toY >= 0) {
5137             boards[0][toY][toX] = boards[0][fromY][fromX];
5138             boards[0][fromY][fromX] = EmptySquare;
5139             return AmbiguousMove;
5140         }
5141         return ImpossibleMove;
5142     }
5143
5144     /* [HGM] If move started in holdings, it means a drop */
5145     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5146          if( pup != EmptySquare ) return ImpossibleMove;
5147          if(appData.testLegality) {
5148              /* it would be more logical if LegalityTest() also figured out
5149               * which drops are legal. For now we forbid pawns on back rank.
5150               * Shogi is on its own here...
5151               */
5152              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5153                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5154                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155          }
5156          return WhiteDrop; /* Not needed to specify white or black yet */
5157     }
5158
5159     userOfferedDraw = FALSE;
5160         
5161     /* [HGM] always test for legality, to get promotion info */
5162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163                           epStatus[currentMove], castlingRights[currentMove],
5164                                          fromY, fromX, toY, toX, promoChar);
5165
5166     /* [HGM] but possibly ignore an IllegalMove result */
5167     if (appData.testLegality) {
5168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5169             DisplayMoveError(_("Illegal move"));
5170             return ImpossibleMove;
5171         }
5172     }
5173 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174     return moveType;
5175     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5176        function is made into one that returns an OK move type if FinishMove
5177        should be called. This to give the calling driver routine the
5178        opportunity to finish the userMove input with a promotion popup,
5179        without bothering the user with this for invalid or illegal moves */
5180
5181 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5182 }
5183
5184 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 int
5186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187      ChessMove moveType;
5188      int fromX, fromY, toX, toY;
5189      /*char*/int promoChar;
5190 {
5191     char *bookHit = 0;
5192 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5193     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5194         // [HGM] superchess: suppress promotions to non-available piece
5195         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5196         if(WhiteOnMove(currentMove)) {
5197             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5198         } else {
5199             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5200         }
5201     }
5202
5203     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5204        move type in caller when we know the move is a legal promotion */
5205     if(moveType == NormalMove && promoChar)
5206         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5207 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5208     /* [HGM] convert drag-and-drop piece drops to standard form */
5209     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5210          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5211          fromX = boards[currentMove][fromY][fromX];
5212          fromY = DROP_RANK;
5213     }
5214
5215     /* [HGM] <popupFix> The following if has been moved here from
5216        UserMoveEvent(). Because it seemed to belon here (why not allow
5217        piece drops in training games?), and because it can only be
5218        performed after it is known to what we promote. */
5219     if (gameMode == Training) {
5220       /* compare the move played on the board to the next move in the
5221        * game. If they match, display the move and the opponent's response. 
5222        * If they don't match, display an error message.
5223        */
5224       int saveAnimate;
5225       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5226       CopyBoard(testBoard, boards[currentMove]);
5227       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5228
5229       if (CompareBoards(testBoard, boards[currentMove+1])) {
5230         ForwardInner(currentMove+1);
5231
5232         /* Autoplay the opponent's response.
5233          * if appData.animate was TRUE when Training mode was entered,
5234          * the response will be animated.
5235          */
5236         saveAnimate = appData.animate;
5237         appData.animate = animateTraining;
5238         ForwardInner(currentMove+1);
5239         appData.animate = saveAnimate;
5240
5241         /* check for the end of the game */
5242         if (currentMove >= forwardMostMove) {
5243           gameMode = PlayFromGameFile;
5244           ModeHighlight();
5245           SetTrainingModeOff();
5246           DisplayInformation(_("End of game"));
5247         }
5248       } else {
5249         DisplayError(_("Incorrect move"), 0);
5250       }
5251       return 1;
5252     }
5253
5254   /* Ok, now we know that the move is good, so we can kill
5255      the previous line in Analysis Mode */
5256   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5257     forwardMostMove = currentMove;
5258   }
5259
5260   /* If we need the chess program but it's dead, restart it */
5261   ResurrectChessProgram();
5262
5263   /* A user move restarts a paused game*/
5264   if (pausing)
5265     PauseEvent();
5266
5267   thinkOutput[0] = NULLCHAR;
5268
5269   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5270
5271   if (gameMode == BeginningOfGame) {
5272     if (appData.noChessProgram) {
5273       gameMode = EditGame;
5274       SetGameInfo();
5275     } else {
5276       char buf[MSG_SIZ];
5277       gameMode = MachinePlaysBlack;
5278       StartClocks();
5279       SetGameInfo();
5280       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5281       DisplayTitle(buf);
5282       if (first.sendName) {
5283         sprintf(buf, "name %s\n", gameInfo.white);
5284         SendToProgram(buf, &first);
5285       }
5286       StartClocks();
5287     }
5288     ModeHighlight();
5289   }
5290 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5291   /* Relay move to ICS or chess engine */
5292   if (appData.icsActive) {
5293     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5294         gameMode == IcsExamining) {
5295       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5296       ics_user_moved = 1;
5297     }
5298   } else {
5299     if (first.sendTime && (gameMode == BeginningOfGame ||
5300                            gameMode == MachinePlaysWhite ||
5301                            gameMode == MachinePlaysBlack)) {
5302       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5303     }
5304     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5305          // [HGM] book: if program might be playing, let it use book
5306         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5307         first.maybeThinking = TRUE;
5308     } else SendMoveToProgram(forwardMostMove-1, &first);
5309     if (currentMove == cmailOldMove + 1) {
5310       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5311     }
5312   }
5313
5314   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5315
5316   switch (gameMode) {
5317   case EditGame:
5318     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5319                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5320     case MT_NONE:
5321     case MT_CHECK:
5322       break;
5323     case MT_CHECKMATE:
5324     case MT_STAINMATE:
5325       if (WhiteOnMove(currentMove)) {
5326         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5327       } else {
5328         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5329       }
5330       break;
5331     case MT_STALEMATE:
5332       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5333       break;
5334     }
5335     break;
5336     
5337   case MachinePlaysBlack:
5338   case MachinePlaysWhite:
5339     /* disable certain menu options while machine is thinking */
5340     SetMachineThinkingEnables();
5341     break;
5342
5343   default:
5344     break;
5345   }
5346
5347   if(bookHit) { // [HGM] book: simulate book reply
5348         static char bookMove[MSG_SIZ]; // a bit generous?
5349
5350         programStats.nodes = programStats.depth = programStats.time = 
5351         programStats.score = programStats.got_only_move = 0;
5352         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5353
5354         strcpy(bookMove, "move ");
5355         strcat(bookMove, bookHit);
5356         HandleMachineMove(bookMove, &first);
5357   }
5358   return 1;
5359 }
5360
5361 void
5362 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5363      int fromX, fromY, toX, toY;
5364      int promoChar;
5365 {
5366     /* [HGM] This routine was added to allow calling of its two logical
5367        parts from other modules in the old way. Before, UserMoveEvent()
5368        automatically called FinishMove() if the move was OK, and returned
5369        otherwise. I separated the two, in order to make it possible to
5370        slip a promotion popup in between. But that it always needs two
5371        calls, to the first part, (now called UserMoveTest() ), and to
5372        FinishMove if the first part succeeded. Calls that do not need
5373        to do anything in between, can call this routine the old way. 
5374     */
5375   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5376   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5377   if(moveType != ImpossibleMove)
5378     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5379 }
5380
5381 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5382 {
5383 //    char * hint = lastHint;
5384     FrontEndProgramStats stats;
5385
5386     stats.which = cps == &first ? 0 : 1;
5387     stats.depth = cpstats->depth;
5388     stats.nodes = cpstats->nodes;
5389     stats.score = cpstats->score;
5390     stats.time = cpstats->time;
5391     stats.pv = cpstats->movelist;
5392     stats.hint = lastHint;
5393     stats.an_move_index = 0;
5394     stats.an_move_count = 0;
5395
5396     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5397         stats.hint = cpstats->move_name;
5398         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5399         stats.an_move_count = cpstats->nr_moves;
5400     }
5401
5402     SetProgramStats( &stats );
5403 }
5404
5405 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5406 {   // [HGM] book: this routine intercepts moves to simulate book replies
5407     char *bookHit = NULL;
5408
5409     //first determine if the incoming move brings opponent into his book
5410     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5411         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5412     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5413     if(bookHit != NULL && !cps->bookSuspend) {
5414         // make sure opponent is not going to reply after receiving move to book position
5415         SendToProgram("force\n", cps);
5416         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5417     }
5418     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5419     // now arrange restart after book miss
5420     if(bookHit) {
5421         // after a book hit we never send 'go', and the code after the call to this routine
5422         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5423         char buf[MSG_SIZ];
5424         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5425         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5426         SendToProgram(buf, cps);
5427         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5428     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5429         SendToProgram("go\n", cps);
5430         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5431     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5432         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5433             SendToProgram("go\n", cps); 
5434         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5435     }
5436     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5437 }
5438
5439 char *savedMessage;
5440 ChessProgramState *savedState;
5441 void DeferredBookMove(void)
5442 {
5443         if(savedState->lastPing != savedState->lastPong)
5444                     ScheduleDelayedEvent(DeferredBookMove, 10);
5445         else
5446         HandleMachineMove(savedMessage, savedState);
5447 }
5448
5449 void
5450 HandleMachineMove(message, cps)
5451      char *message;
5452      ChessProgramState *cps;
5453 {
5454     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5455     char realname[MSG_SIZ];
5456     int fromX, fromY, toX, toY;
5457     ChessMove moveType;
5458     char promoChar;
5459     char *p;
5460     int machineWhite;
5461     char *bookHit;
5462
5463 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5464     /*
5465      * Kludge to ignore BEL characters
5466      */
5467     while (*message == '\007') message++;
5468
5469     /*
5470      * [HGM] engine debug message: ignore lines starting with '#' character
5471      */
5472     if(cps->debug && *message == '#') return;
5473
5474     /*
5475      * Look for book output
5476      */
5477     if (cps == &first && bookRequested) {
5478         if (message[0] == '\t' || message[0] == ' ') {
5479             /* Part of the book output is here; append it */
5480             strcat(bookOutput, message);
5481             strcat(bookOutput, "  \n");
5482             return;
5483         } else if (bookOutput[0] != NULLCHAR) {
5484             /* All of book output has arrived; display it */
5485             char *p = bookOutput;
5486             while (*p != NULLCHAR) {
5487                 if (*p == '\t') *p = ' ';
5488                 p++;
5489             }
5490             DisplayInformation(bookOutput);
5491             bookRequested = FALSE;
5492             /* Fall through to parse the current output */
5493         }
5494     }
5495
5496     /*
5497      * Look for machine move.
5498      */
5499     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5500         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5501     {
5502         /* This method is only useful on engines that support ping */
5503         if (cps->lastPing != cps->lastPong) {
5504           if (gameMode == BeginningOfGame) {
5505             /* Extra move from before last new; ignore */
5506             if (appData.debugMode) {
5507                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5508             }
5509           } else {
5510             if (appData.debugMode) {
5511                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5512                         cps->which, gameMode);
5513             }
5514
5515             SendToProgram("undo\n", cps);
5516           }
5517           return;
5518         }
5519
5520         switch (gameMode) {
5521           case BeginningOfGame:
5522             /* Extra move from before last reset; ignore */
5523             if (appData.debugMode) {
5524                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5525             }
5526             return;
5527
5528           case EndOfGame:
5529           case IcsIdle:
5530           default:
5531             /* Extra move after we tried to stop.  The mode test is
5532                not a reliable way of detecting this problem, but it's
5533                the best we can do on engines that don't support ping.
5534             */
5535             if (appData.debugMode) {
5536                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5537                         cps->which, gameMode);
5538             }
5539             SendToProgram("undo\n", cps);
5540             return;
5541
5542           case MachinePlaysWhite:
5543           case IcsPlayingWhite:
5544             machineWhite = TRUE;
5545             break;
5546
5547           case MachinePlaysBlack:
5548           case IcsPlayingBlack:
5549             machineWhite = FALSE;
5550             break;
5551
5552           case TwoMachinesPlay:
5553             machineWhite = (cps->twoMachinesColor[0] == 'w');
5554             break;
5555         }
5556         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5557             if (appData.debugMode) {
5558                 fprintf(debugFP,
5559                         "Ignoring move out of turn by %s, gameMode %d"
5560                         ", forwardMost %d\n",
5561                         cps->which, gameMode, forwardMostMove);
5562             }
5563             return;
5564         }
5565
5566     if (appData.debugMode) { int f = forwardMostMove;
5567         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5568                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5569     }
5570         if(cps->alphaRank) AlphaRank(machineMove, 4);
5571         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5572                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5573             /* Machine move could not be parsed; ignore it. */
5574             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5575                     machineMove, cps->which);
5576             DisplayError(buf1, 0);
5577             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5578                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5579             if (gameMode == TwoMachinesPlay) {
5580               GameEnds(machineWhite ? BlackWins : WhiteWins,
5581                        buf1, GE_XBOARD);
5582             }
5583             return;
5584         }
5585
5586         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5587         /* So we have to redo legality test with true e.p. status here,  */
5588         /* to make sure an illegal e.p. capture does not slip through,   */
5589         /* to cause a forfeit on a justified illegal-move complaint      */
5590         /* of the opponent.                                              */
5591         if( gameMode==TwoMachinesPlay && appData.testLegality
5592             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5593                                                               ) {
5594            ChessMove moveType;
5595            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5596                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5597                              fromY, fromX, toY, toX, promoChar);
5598             if (appData.debugMode) {
5599                 int i;
5600                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5601                     castlingRights[forwardMostMove][i], castlingRank[i]);
5602                 fprintf(debugFP, "castling rights\n");
5603             }
5604             if(moveType == IllegalMove) {
5605                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5606                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5607                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5608                            buf1, GE_XBOARD);
5609                 return;
5610            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5611            /* [HGM] Kludge to handle engines that send FRC-style castling
5612               when they shouldn't (like TSCP-Gothic) */
5613            switch(moveType) {
5614              case WhiteASideCastleFR:
5615              case BlackASideCastleFR:
5616                toX+=2;
5617                currentMoveString[2]++;
5618                break;
5619              case WhiteHSideCastleFR:
5620              case BlackHSideCastleFR:
5621                toX--;
5622                currentMoveString[2]--;
5623                break;
5624              default: ; // nothing to do, but suppresses warning of pedantic compilers
5625            }
5626         }
5627         hintRequested = FALSE;
5628         lastHint[0] = NULLCHAR;
5629         bookRequested = FALSE;
5630         /* Program may be pondering now */
5631         cps->maybeThinking = TRUE;
5632         if (cps->sendTime == 2) cps->sendTime = 1;
5633         if (cps->offeredDraw) cps->offeredDraw--;
5634
5635 #if ZIPPY
5636         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5637             first.initDone) {
5638           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5639           ics_user_moved = 1;
5640           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5641                 char buf[3*MSG_SIZ];
5642
5643                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5644                         programStats.score / 100.,
5645                         programStats.depth,
5646                         programStats.time / 100.,
5647                         u64ToDouble(programStats.nodes),
5648                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5649                         programStats.movelist);
5650                 SendToICS(buf);
5651           }
5652         }
5653 #endif
5654         /* currentMoveString is set as a side-effect of ParseOneMove */
5655         strcpy(machineMove, currentMoveString);
5656         strcat(machineMove, "\n");
5657         strcpy(moveList[forwardMostMove], machineMove);
5658
5659         /* [AS] Save move info and clear stats for next move */
5660         pvInfoList[ forwardMostMove ].score = programStats.score;
5661         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5662         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5663         ClearProgramStats();
5664         thinkOutput[0] = NULLCHAR;
5665         hiddenThinkOutputState = 0;
5666
5667         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5668
5669         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5670         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5671             int count = 0;
5672
5673             while( count < adjudicateLossPlies ) {
5674                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5675
5676                 if( count & 1 ) {
5677                     score = -score; /* Flip score for winning side */
5678                 }
5679
5680                 if( score > adjudicateLossThreshold ) {
5681                     break;
5682                 }
5683
5684                 count++;
5685             }
5686
5687             if( count >= adjudicateLossPlies ) {
5688                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5689
5690                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5691                     "Xboard adjudication", 
5692                     GE_XBOARD );
5693
5694                 return;
5695             }
5696         }
5697
5698         if( gameMode == TwoMachinesPlay ) {
5699           // [HGM] some adjudications useful with buggy engines
5700             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5701           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5702
5703
5704             if( appData.testLegality )
5705             {   /* [HGM] Some more adjudications for obstinate engines */
5706                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5707                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5708                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5709                 static int moveCount = 6;
5710                 ChessMove result;
5711                 char *reason = NULL;
5712
5713                 /* Count what is on board. */
5714                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5715                 {   ChessSquare p = boards[forwardMostMove][i][j];
5716                     int m=i;
5717
5718                     switch((int) p)
5719                     {   /* count B,N,R and other of each side */
5720                         case WhiteKing:
5721                         case BlackKing:
5722                              NrK++; break; // [HGM] atomic: count Kings
5723                         case WhiteKnight:
5724                              NrWN++; break;
5725                         case WhiteBishop:
5726                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5727                              bishopsColor |= 1 << ((i^j)&1);
5728                              NrWB++; break;
5729                         case BlackKnight:
5730                              NrBN++; break;
5731                         case BlackBishop:
5732                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5733                              bishopsColor |= 1 << ((i^j)&1);
5734                              NrBB++; break;
5735                         case WhiteRook:
5736                              NrWR++; break;
5737                         case BlackRook:
5738                              NrBR++; break;
5739                         case WhiteQueen:
5740                              NrWQ++; break;
5741                         case BlackQueen:
5742                              NrBQ++; break;
5743                         case EmptySquare: 
5744                              break;
5745                         case BlackPawn:
5746                              m = 7-i;
5747                         case WhitePawn:
5748                              PawnAdvance += m; NrPawns++;
5749                     }
5750                     NrPieces += (p != EmptySquare);
5751                     NrW += ((int)p < (int)BlackPawn);
5752                     if(gameInfo.variant == VariantXiangqi && 
5753                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5754                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5755                         NrW -= ((int)p < (int)BlackPawn);
5756                     }
5757                 }
5758
5759                 /* Some material-based adjudications that have to be made before stalemate test */
5760                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5761                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5762                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5763                      if(appData.checkMates) {
5764                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5765                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5766                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5767                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5768                          return;
5769                      }
5770                 }
5771
5772                 /* Bare King in Shatranj (loses) or Losers (wins) */
5773                 if( NrW == 1 || NrPieces - NrW == 1) {
5774                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5775                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5776                      if(appData.checkMates) {
5777                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5778                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5779                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5780                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5781                          return;
5782                      }
5783                   } else
5784                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5785                   {    /* bare King */
5786                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5787                         if(appData.checkMates) {
5788                             /* but only adjudicate if adjudication enabled */
5789                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5790                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5791                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5792                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5793                             return;
5794                         }
5795                   }
5796                 } else bare = 1;
5797
5798
5799             // don't wait for engine to announce game end if we can judge ourselves
5800             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5801                                        castlingRights[forwardMostMove]) ) {
5802               case MT_CHECK:
5803                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5804                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5805                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5806                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5807                             checkCnt++;
5808                         if(checkCnt >= 2) {
5809                             reason = "Xboard adjudication: 3rd check";
5810                             epStatus[forwardMostMove] = EP_CHECKMATE;
5811                             break;
5812                         }
5813                     }
5814                 }
5815               case MT_NONE:
5816               default:
5817                 break;
5818               case MT_STALEMATE:
5819               case MT_STAINMATE:
5820                 reason = "Xboard adjudication: Stalemate";
5821                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5822                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5823                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5824                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5825                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5826                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5827                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5828                                                                         EP_CHECKMATE : EP_WINS);
5829                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5830                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5831                 }
5832                 break;
5833               case MT_CHECKMATE:
5834                 reason = "Xboard adjudication: Checkmate";
5835                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5836                 break;
5837             }
5838
5839                 switch(i = epStatus[forwardMostMove]) {
5840                     case EP_STALEMATE:
5841                         result = GameIsDrawn; break;
5842                     case EP_CHECKMATE:
5843                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5844                     case EP_WINS:
5845                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5846                     default:
5847                         result = (ChessMove) 0;
5848                 }
5849                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5850                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5851                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5852                     GameEnds( result, reason, GE_XBOARD );
5853                     return;
5854                 }
5855
5856                 /* Next absolutely insufficient mating material. */
5857                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5858                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5859                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5860                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5861                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5862
5863                      /* always flag draws, for judging claims */
5864                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5865
5866                      if(appData.materialDraws) {
5867                          /* but only adjudicate them if adjudication enabled */
5868                          SendToProgram("force\n", cps->other); // suppress reply
5869                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5870                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5871                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5872                          return;
5873                      }
5874                 }
5875
5876                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5877                 if(NrPieces == 4 && 
5878                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5879                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5880                    || NrWN==2 || NrBN==2     /* KNNK */
5881                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5882                   ) ) {
5883                      if(--moveCount < 0 && appData.trivialDraws)
5884                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5885                           SendToProgram("force\n", cps->other); // suppress reply
5886                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5887                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5888                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5889                           return;
5890                      }
5891                 } else moveCount = 6;
5892             }
5893           }
5894 #if 1
5895     if (appData.debugMode) { int i;
5896       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5897               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5898               appData.drawRepeats);
5899       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5900            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5901
5902     }
5903 #endif
5904                 /* Check for rep-draws */
5905                 count = 0;
5906                 for(k = forwardMostMove-2;
5907                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5908                         epStatus[k] < EP_UNKNOWN &&
5909                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5910                     k-=2)
5911                 {   int rights=0;
5912 #if 0
5913     if (appData.debugMode) {
5914       fprintf(debugFP, " loop\n");
5915     }
5916 #endif
5917                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5918 #if 0
5919     if (appData.debugMode) {
5920       fprintf(debugFP, "match\n");
5921     }
5922 #endif
5923                         /* compare castling rights */
5924                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5925                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5926                                 rights++; /* King lost rights, while rook still had them */
5927                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5928                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5929                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5930                                    rights++; /* but at least one rook lost them */
5931                         }
5932                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5933                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5934                                 rights++; 
5935                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5936                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5937                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5938                                    rights++;
5939                         }
5940 #if 0
5941     if (appData.debugMode) {
5942       for(i=0; i<nrCastlingRights; i++)
5943       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5944     }
5945
5946     if (appData.debugMode) {
5947       fprintf(debugFP, " %d %d\n", rights, k);
5948     }
5949 #endif
5950                         if( rights == 0 && ++count > appData.drawRepeats-2
5951                             && appData.drawRepeats > 1) {
5952                              /* adjudicate after user-specified nr of repeats */
5953                              SendToProgram("force\n", cps->other); // suppress reply
5954                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5955                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5956                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5957                                 // [HGM] xiangqi: check for forbidden perpetuals
5958                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5959                                 for(m=forwardMostMove; m>k; m-=2) {
5960                                     if(MateTest(boards[m], PosFlags(m), 
5961                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5962                                         ourPerpetual = 0; // the current mover did not always check
5963                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5964                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5965                                         hisPerpetual = 0; // the opponent did not always check
5966                                 }
5967                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5968                                                                         ourPerpetual, hisPerpetual);
5969                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5970                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5971                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5972                                     return;
5973                                 }
5974                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5975                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5976                                 // Now check for perpetual chases
5977                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5978                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5979                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5980                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5981                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5982                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5983                                         return;
5984                                     }
5985                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5986                                         break; // Abort repetition-checking loop.
5987                                 }
5988                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5989                              }
5990                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5991                              return;
5992                         }
5993                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5994                              epStatus[forwardMostMove] = EP_REP_DRAW;
5995                     }
5996                 }
5997
5998                 /* Now we test for 50-move draws. Determine ply count */
5999                 count = forwardMostMove;
6000                 /* look for last irreversble move */
6001                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6002                     count--;
6003                 /* if we hit starting position, add initial plies */
6004                 if( count == backwardMostMove )
6005                     count -= initialRulePlies;
6006                 count = forwardMostMove - count; 
6007                 if( count >= 100)
6008                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6009                          /* this is used to judge if draw claims are legal */
6010                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6011                          SendToProgram("force\n", cps->other); // suppress reply
6012                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6013                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6014                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6015                          return;
6016                 }
6017
6018                 /* if draw offer is pending, treat it as a draw claim
6019                  * when draw condition present, to allow engines a way to
6020                  * claim draws before making their move to avoid a race
6021                  * condition occurring after their move
6022                  */
6023                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6024                          char *p = NULL;
6025                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6026                              p = "Draw claim: 50-move rule";
6027                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6028                              p = "Draw claim: 3-fold repetition";
6029                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6030                              p = "Draw claim: insufficient mating material";
6031                          if( p != NULL ) {
6032                              SendToProgram("force\n", cps->other); // suppress reply
6033                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6034                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6035                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036                              return;
6037                          }
6038                 }
6039
6040
6041                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6042                     SendToProgram("force\n", cps->other); // suppress reply
6043                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6044                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6045
6046                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6047
6048                     return;
6049                 }
6050         }
6051
6052         bookHit = NULL;
6053         if (gameMode == TwoMachinesPlay) {
6054             /* [HGM] relaying draw offers moved to after reception of move */
6055             /* and interpreting offer as claim if it brings draw condition */
6056             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6057                 SendToProgram("draw\n", cps->other);
6058             }
6059             if (cps->other->sendTime) {
6060                 SendTimeRemaining(cps->other,
6061                                   cps->other->twoMachinesColor[0] == 'w');
6062             }
6063             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6064             if (firstMove && !bookHit) {
6065                 firstMove = FALSE;
6066                 if (cps->other->useColors) {
6067                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6068                 }
6069                 SendToProgram("go\n", cps->other);
6070             }
6071             cps->other->maybeThinking = TRUE;
6072         }
6073
6074         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6075         
6076         if (!pausing && appData.ringBellAfterMoves) {
6077             RingBell();
6078         }
6079
6080         /* 
6081          * Reenable menu items that were disabled while
6082          * machine was thinking
6083          */
6084         if (gameMode != TwoMachinesPlay)
6085             SetUserThinkingEnables();
6086
6087         // [HGM] book: after book hit opponent has received move and is now in force mode
6088         // force the book reply into it, and then fake that it outputted this move by jumping
6089         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6090         if(bookHit) {
6091                 static char bookMove[MSG_SIZ]; // a bit generous?
6092
6093                 strcpy(bookMove, "move ");
6094                 strcat(bookMove, bookHit);
6095                 message = bookMove;
6096                 cps = cps->other;
6097                 programStats.nodes = programStats.depth = programStats.time = 
6098                 programStats.score = programStats.got_only_move = 0;
6099                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6100
6101                 if(cps->lastPing != cps->lastPong) {
6102                     savedMessage = message; // args for deferred call
6103                     savedState = cps;
6104                     ScheduleDelayedEvent(DeferredBookMove, 10);
6105                     return;
6106                 }
6107                 goto FakeBookMove;
6108         }
6109
6110         return;
6111     }
6112
6113     /* Set special modes for chess engines.  Later something general
6114      *  could be added here; for now there is just one kludge feature,
6115      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6116      *  when "xboard" is given as an interactive command.
6117      */
6118     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6119         cps->useSigint = FALSE;
6120         cps->useSigterm = FALSE;
6121     }
6122
6123     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6124      * want this, I was asked to put it in, and obliged.
6125      */
6126     if (!strncmp(message, "setboard ", 9)) {
6127         Board initial_position; int i;
6128
6129         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6130
6131         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6132             DisplayError(_("Bad FEN received from engine"), 0);
6133             return ;
6134         } else {
6135            Reset(FALSE, FALSE);
6136            CopyBoard(boards[0], initial_position);
6137            initialRulePlies = FENrulePlies;
6138            epStatus[0] = FENepStatus;
6139            for( i=0; i<nrCastlingRights; i++ )
6140                 castlingRights[0][i] = FENcastlingRights[i];
6141            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6142            else gameMode = MachinePlaysBlack;                 
6143            DrawPosition(FALSE, boards[currentMove]);
6144         }
6145         return;
6146     }
6147
6148     /*
6149      * Look for communication commands
6150      */
6151     if (!strncmp(message, "telluser ", 9)) {
6152         DisplayNote(message + 9);
6153         return;
6154     }
6155     if (!strncmp(message, "tellusererror ", 14)) {
6156         DisplayError(message + 14, 0);
6157         return;
6158     }
6159     if (!strncmp(message, "tellopponent ", 13)) {
6160       if (appData.icsActive) {
6161         if (loggedOn) {
6162           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6163           SendToICS(buf1);
6164         }
6165       } else {
6166         DisplayNote(message + 13);
6167       }
6168       return;
6169     }
6170     if (!strncmp(message, "tellothers ", 11)) {
6171       if (appData.icsActive) {
6172         if (loggedOn) {
6173           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6174           SendToICS(buf1);
6175         }
6176       }
6177       return;
6178     }
6179     if (!strncmp(message, "tellall ", 8)) {
6180       if (appData.icsActive) {
6181         if (loggedOn) {
6182           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6183           SendToICS(buf1);
6184         }
6185       } else {
6186         DisplayNote(message + 8);
6187       }
6188       return;
6189     }
6190     if (strncmp(message, "warning", 7) == 0) {
6191         /* Undocumented feature, use tellusererror in new code */
6192         DisplayError(message, 0);
6193         return;
6194     }
6195     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6196         strcpy(realname, cps->tidy);
6197         strcat(realname, " query");
6198         AskQuestion(realname, buf2, buf1, cps->pr);
6199         return;
6200     }
6201     /* Commands from the engine directly to ICS.  We don't allow these to be 
6202      *  sent until we are logged on. Crafty kibitzes have been known to 
6203      *  interfere with the login process.
6204      */
6205     if (loggedOn) {
6206         if (!strncmp(message, "tellics ", 8)) {
6207             SendToICS(message + 8);
6208             SendToICS("\n");
6209             return;
6210         }
6211         if (!strncmp(message, "tellicsnoalias ", 15)) {
6212             SendToICS(ics_prefix);
6213             SendToICS(message + 15);
6214             SendToICS("\n");
6215             return;
6216         }
6217         /* The following are for backward compatibility only */
6218         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6219             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6220             SendToICS(ics_prefix);
6221             SendToICS(message);
6222             SendToICS("\n");
6223             return;
6224         }
6225     }
6226     if (strncmp(message, "feature ", 8) == 0) {
6227       ParseFeatures(message+8, cps);
6228     }
6229     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6230         return;
6231     }
6232     /*
6233      * If the move is illegal, cancel it and redraw the board.
6234      * Also deal with other error cases.  Matching is rather loose
6235      * here to accommodate engines written before the spec.
6236      */
6237     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6238         strncmp(message, "Error", 5) == 0) {
6239         if (StrStr(message, "name") || 
6240             StrStr(message, "rating") || StrStr(message, "?") ||
6241             StrStr(message, "result") || StrStr(message, "board") ||
6242             StrStr(message, "bk") || StrStr(message, "computer") ||
6243             StrStr(message, "variant") || StrStr(message, "hint") ||
6244             StrStr(message, "random") || StrStr(message, "depth") ||
6245             StrStr(message, "accepted")) {
6246             return;
6247         }
6248         if (StrStr(message, "protover")) {
6249           /* Program is responding to input, so it's apparently done
6250              initializing, and this error message indicates it is
6251              protocol version 1.  So we don't need to wait any longer
6252              for it to initialize and send feature commands. */
6253           FeatureDone(cps, 1);
6254           cps->protocolVersion = 1;
6255           return;
6256         }
6257         cps->maybeThinking = FALSE;
6258
6259         if (StrStr(message, "draw")) {
6260             /* Program doesn't have "draw" command */
6261             cps->sendDrawOffers = 0;
6262             return;
6263         }
6264         if (cps->sendTime != 1 &&
6265             (StrStr(message, "time") || StrStr(message, "otim"))) {
6266           /* Program apparently doesn't have "time" or "otim" command */
6267           cps->sendTime = 0;
6268           return;
6269         }
6270         if (StrStr(message, "analyze")) {
6271             cps->analysisSupport = FALSE;
6272             cps->analyzing = FALSE;
6273             Reset(FALSE, TRUE);
6274             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6275             DisplayError(buf2, 0);
6276             return;
6277         }
6278         if (StrStr(message, "(no matching move)st")) {
6279           /* Special kludge for GNU Chess 4 only */
6280           cps->stKludge = TRUE;
6281           SendTimeControl(cps, movesPerSession, timeControl,
6282                           timeIncrement, appData.searchDepth,
6283                           searchTime);
6284           return;
6285         }
6286         if (StrStr(message, "(no matching move)sd")) {
6287           /* Special kludge for GNU Chess 4 only */
6288           cps->sdKludge = TRUE;
6289           SendTimeControl(cps, movesPerSession, timeControl,
6290                           timeIncrement, appData.searchDepth,
6291                           searchTime);
6292           return;
6293         }
6294         if (!StrStr(message, "llegal")) {
6295             return;
6296         }
6297         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6298             gameMode == IcsIdle) return;
6299         if (forwardMostMove <= backwardMostMove) return;
6300 #if 0
6301         /* Following removed: it caused a bug where a real illegal move
6302            message in analyze mored would be ignored. */
6303         if (cps == &first && programStats.ok_to_send == 0) {
6304             /* Bogus message from Crafty responding to "."  This filtering
6305                can miss some of the bad messages, but fortunately the bug 
6306                is fixed in current Crafty versions, so it doesn't matter. */
6307             return;
6308         }
6309 #endif
6310         if (pausing) PauseEvent();
6311         if (gameMode == PlayFromGameFile) {
6312             /* Stop reading this game file */
6313             gameMode = EditGame;
6314             ModeHighlight();
6315         }
6316         currentMove = --forwardMostMove;
6317         DisplayMove(currentMove-1); /* before DisplayMoveError */
6318         SwitchClocks();
6319         DisplayBothClocks();
6320         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6321                 parseList[currentMove], cps->which);
6322         DisplayMoveError(buf1);
6323         DrawPosition(FALSE, boards[currentMove]);
6324
6325         /* [HGM] illegal-move claim should forfeit game when Xboard */
6326         /* only passes fully legal moves                            */
6327         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6328             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6329                                 "False illegal-move claim", GE_XBOARD );
6330         }
6331         return;
6332     }
6333     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6334         /* Program has a broken "time" command that
6335            outputs a string not ending in newline.
6336            Don't use it. */
6337         cps->sendTime = 0;
6338     }
6339     
6340     /*
6341      * If chess program startup fails, exit with an error message.
6342      * Attempts to recover here are futile.
6343      */
6344     if ((StrStr(message, "unknown host") != NULL)
6345         || (StrStr(message, "No remote directory") != NULL)
6346         || (StrStr(message, "not found") != NULL)
6347         || (StrStr(message, "No such file") != NULL)
6348         || (StrStr(message, "can't alloc") != NULL)
6349         || (StrStr(message, "Permission denied") != NULL)) {
6350
6351         cps->maybeThinking = FALSE;
6352         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6353                 cps->which, cps->program, cps->host, message);
6354         RemoveInputSource(cps->isr);
6355         DisplayFatalError(buf1, 0, 1);
6356         return;
6357     }
6358     
6359     /* 
6360      * Look for hint output
6361      */
6362     if (sscanf(message, "Hint: %s", buf1) == 1) {
6363         if (cps == &first && hintRequested) {
6364             hintRequested = FALSE;
6365             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6366                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6367                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6368                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6369                                     fromY, fromX, toY, toX, promoChar, buf1);
6370                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6371                 DisplayInformation(buf2);
6372             } else {
6373                 /* Hint move could not be parsed!? */
6374               snprintf(buf2, sizeof(buf2),
6375                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6376                         buf1, cps->which);
6377                 DisplayError(buf2, 0);
6378             }
6379         } else {
6380             strcpy(lastHint, buf1);
6381         }
6382         return;
6383     }
6384
6385     /*
6386      * Ignore other messages if game is not in progress
6387      */
6388     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6389         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6390
6391     /*
6392      * look for win, lose, draw, or draw offer
6393      */
6394     if (strncmp(message, "1-0", 3) == 0) {
6395         char *p, *q, *r = "";
6396         p = strchr(message, '{');
6397         if (p) {
6398             q = strchr(p, '}');
6399             if (q) {
6400                 *q = NULLCHAR;
6401                 r = p + 1;
6402             }
6403         }
6404         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6405         return;
6406     } else if (strncmp(message, "0-1", 3) == 0) {
6407         char *p, *q, *r = "";
6408         p = strchr(message, '{');
6409         if (p) {
6410             q = strchr(p, '}');
6411             if (q) {
6412                 *q = NULLCHAR;
6413                 r = p + 1;
6414             }
6415         }
6416         /* Kludge for Arasan 4.1 bug */
6417         if (strcmp(r, "Black resigns") == 0) {
6418             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6419             return;
6420         }
6421         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6422         return;
6423     } else if (strncmp(message, "1/2", 3) == 0) {
6424         char *p, *q, *r = "";
6425         p = strchr(message, '{');
6426         if (p) {
6427             q = strchr(p, '}');
6428             if (q) {
6429                 *q = NULLCHAR;
6430                 r = p + 1;
6431             }
6432         }
6433             
6434         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6435         return;
6436
6437     } else if (strncmp(message, "White resign", 12) == 0) {
6438         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6439         return;
6440     } else if (strncmp(message, "Black resign", 12) == 0) {
6441         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6442         return;
6443     } else if (strncmp(message, "White matches", 13) == 0 ||
6444                strncmp(message, "Black matches", 13) == 0   ) {
6445         /* [HGM] ignore GNUShogi noises */
6446         return;
6447     } else if (strncmp(message, "White", 5) == 0 &&
6448                message[5] != '(' &&
6449                StrStr(message, "Black") == NULL) {
6450         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6451         return;
6452     } else if (strncmp(message, "Black", 5) == 0 &&
6453                message[5] != '(') {
6454         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6455         return;
6456     } else if (strcmp(message, "resign") == 0 ||
6457                strcmp(message, "computer resigns") == 0) {
6458         switch (gameMode) {
6459           case MachinePlaysBlack:
6460           case IcsPlayingBlack:
6461             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6462             break;
6463           case MachinePlaysWhite:
6464           case IcsPlayingWhite:
6465             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6466             break;
6467           case TwoMachinesPlay:
6468             if (cps->twoMachinesColor[0] == 'w')
6469               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6470             else
6471               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6472             break;
6473           default:
6474             /* can't happen */
6475             break;
6476         }
6477         return;
6478     } else if (strncmp(message, "opponent mates", 14) == 0) {
6479         switch (gameMode) {
6480           case MachinePlaysBlack:
6481           case IcsPlayingBlack:
6482             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6483             break;
6484           case MachinePlaysWhite:
6485           case IcsPlayingWhite:
6486             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6487             break;
6488           case TwoMachinesPlay:
6489             if (cps->twoMachinesColor[0] == 'w')
6490               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6491             else
6492               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6493             break;
6494           default:
6495             /* can't happen */
6496             break;
6497         }
6498         return;
6499     } else if (strncmp(message, "computer mates", 14) == 0) {
6500         switch (gameMode) {
6501           case MachinePlaysBlack:
6502           case IcsPlayingBlack:
6503             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6504             break;
6505           case MachinePlaysWhite:
6506           case IcsPlayingWhite:
6507             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6508             break;
6509           case TwoMachinesPlay:
6510             if (cps->twoMachinesColor[0] == 'w')
6511               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6512             else
6513               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6514             break;
6515           default:
6516             /* can't happen */
6517             break;
6518         }
6519         return;
6520     } else if (strncmp(message, "checkmate", 9) == 0) {
6521         if (WhiteOnMove(forwardMostMove)) {
6522             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6523         } else {
6524             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6525         }
6526         return;
6527     } else if (strstr(message, "Draw") != NULL ||
6528                strstr(message, "game is a draw") != NULL) {
6529         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6530         return;
6531     } else if (strstr(message, "offer") != NULL &&
6532                strstr(message, "draw") != NULL) {
6533 #if ZIPPY
6534         if (appData.zippyPlay && first.initDone) {
6535             /* Relay offer to ICS */
6536             SendToICS(ics_prefix);
6537             SendToICS("draw\n");
6538         }
6539 #endif
6540         cps->offeredDraw = 2; /* valid until this engine moves twice */
6541         if (gameMode == TwoMachinesPlay) {
6542             if (cps->other->offeredDraw) {
6543                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6544             /* [HGM] in two-machine mode we delay relaying draw offer      */
6545             /* until after we also have move, to see if it is really claim */
6546             }
6547 #if 0
6548               else {
6549                 if (cps->other->sendDrawOffers) {
6550                     SendToProgram("draw\n", cps->other);
6551                 }
6552             }
6553 #endif
6554         } else if (gameMode == MachinePlaysWhite ||
6555                    gameMode == MachinePlaysBlack) {
6556           if (userOfferedDraw) {
6557             DisplayInformation(_("Machine accepts your draw offer"));
6558             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6559           } else {
6560             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6561           }
6562         }
6563     }
6564
6565     
6566     /*
6567      * Look for thinking output
6568      */
6569     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6570           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6571                                 ) {
6572         int plylev, mvleft, mvtot, curscore, time;
6573         char mvname[MOVE_LEN];
6574         u64 nodes; // [DM]
6575         char plyext;
6576         int ignore = FALSE;
6577         int prefixHint = FALSE;
6578         mvname[0] = NULLCHAR;
6579
6580         switch (gameMode) {
6581           case MachinePlaysBlack:
6582           case IcsPlayingBlack:
6583             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6584             break;
6585           case MachinePlaysWhite:
6586           case IcsPlayingWhite:
6587             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6588             break;
6589           case AnalyzeMode:
6590           case AnalyzeFile:
6591             break;
6592           case IcsObserving: /* [DM] icsEngineAnalyze */
6593             if (!appData.icsEngineAnalyze) ignore = TRUE;
6594             break;
6595           case TwoMachinesPlay:
6596             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6597                 ignore = TRUE;
6598             }
6599             break;
6600           default:
6601             ignore = TRUE;
6602             break;
6603         }
6604
6605         if (!ignore) {
6606             buf1[0] = NULLCHAR;
6607             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6608                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6609
6610                 if (plyext != ' ' && plyext != '\t') {
6611                     time *= 100;
6612                 }
6613
6614                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6615                 if( cps->scoreIsAbsolute && 
6616                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6617                 {
6618                     curscore = -curscore;
6619                 }
6620
6621
6622                 programStats.depth = plylev;
6623                 programStats.nodes = nodes;
6624                 programStats.time = time;
6625                 programStats.score = curscore;
6626                 programStats.got_only_move = 0;
6627
6628                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6629                         int ticklen;
6630
6631                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6632                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6633                         if(WhiteOnMove(forwardMostMove)) 
6634                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6635                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6636                 }
6637
6638                 /* Buffer overflow protection */
6639                 if (buf1[0] != NULLCHAR) {
6640                     if (strlen(buf1) >= sizeof(programStats.movelist)
6641                         && appData.debugMode) {
6642                         fprintf(debugFP,
6643                                 "PV is too long; using the first %d bytes.\n",
6644                                 sizeof(programStats.movelist) - 1);
6645                     }
6646
6647                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6648                 } else {
6649                     sprintf(programStats.movelist, " no PV\n");
6650                 }
6651
6652                 if (programStats.seen_stat) {
6653                     programStats.ok_to_send = 1;
6654                 }
6655
6656                 if (strchr(programStats.movelist, '(') != NULL) {
6657                     programStats.line_is_book = 1;
6658                     programStats.nr_moves = 0;
6659                     programStats.moves_left = 0;
6660                 } else {
6661                     programStats.line_is_book = 0;
6662                 }
6663
6664                 SendProgramStatsToFrontend( cps, &programStats );
6665
6666                 /* 
6667                     [AS] Protect the thinkOutput buffer from overflow... this
6668                     is only useful if buf1 hasn't overflowed first!
6669                 */
6670                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6671                         plylev, 
6672                         (gameMode == TwoMachinesPlay ?
6673                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6674                         ((double) curscore) / 100.0,
6675                         prefixHint ? lastHint : "",
6676                         prefixHint ? " " : "" );
6677
6678                 if( buf1[0] != NULLCHAR ) {
6679                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6680
6681                     if( strlen(buf1) > max_len ) {
6682                         if( appData.debugMode) {
6683                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6684                         }
6685                         buf1[max_len+1] = '\0';
6686                     }
6687
6688                     strcat( thinkOutput, buf1 );
6689                 }
6690
6691                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6692                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6693                     DisplayMove(currentMove - 1);
6694                     DisplayAnalysis();
6695                 }
6696                 return;
6697
6698             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6699                 /* crafty (9.25+) says "(only move) <move>"
6700                  * if there is only 1 legal move
6701                  */
6702                 sscanf(p, "(only move) %s", buf1);
6703                 sprintf(thinkOutput, "%s (only move)", buf1);
6704                 sprintf(programStats.movelist, "%s (only move)", buf1);
6705                 programStats.depth = 1;
6706                 programStats.nr_moves = 1;
6707                 programStats.moves_left = 1;
6708                 programStats.nodes = 1;
6709                 programStats.time = 1;
6710                 programStats.got_only_move = 1;
6711
6712                 /* Not really, but we also use this member to
6713                    mean "line isn't going to change" (Crafty
6714                    isn't searching, so stats won't change) */
6715                 programStats.line_is_book = 1;
6716
6717                 SendProgramStatsToFrontend( cps, &programStats );
6718                 
6719                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6720                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6721                     DisplayMove(currentMove - 1);
6722                     DisplayAnalysis();
6723                 }
6724                 return;
6725             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6726                               &time, &nodes, &plylev, &mvleft,
6727                               &mvtot, mvname) >= 5) {
6728                 /* The stat01: line is from Crafty (9.29+) in response
6729                    to the "." command */
6730                 programStats.seen_stat = 1;
6731                 cps->maybeThinking = TRUE;
6732
6733                 if (programStats.got_only_move || !appData.periodicUpdates)
6734                   return;
6735
6736                 programStats.depth = plylev;
6737                 programStats.time = time;
6738                 programStats.nodes = nodes;
6739                 programStats.moves_left = mvleft;
6740                 programStats.nr_moves = mvtot;
6741                 strcpy(programStats.move_name, mvname);
6742                 programStats.ok_to_send = 1;
6743                 programStats.movelist[0] = '\0';
6744
6745                 SendProgramStatsToFrontend( cps, &programStats );
6746
6747                 DisplayAnalysis();
6748                 return;
6749
6750             } else if (strncmp(message,"++",2) == 0) {
6751                 /* Crafty 9.29+ outputs this */
6752                 programStats.got_fail = 2;
6753                 return;
6754
6755             } else if (strncmp(message,"--",2) == 0) {
6756                 /* Crafty 9.29+ outputs this */
6757                 programStats.got_fail = 1;
6758                 return;
6759
6760             } else if (thinkOutput[0] != NULLCHAR &&
6761                        strncmp(message, "    ", 4) == 0) {
6762                 unsigned message_len;
6763
6764                 p = message;
6765                 while (*p && *p == ' ') p++;
6766
6767                 message_len = strlen( p );
6768
6769                 /* [AS] Avoid buffer overflow */
6770                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6771                     strcat(thinkOutput, " ");
6772                     strcat(thinkOutput, p);
6773                 }
6774
6775                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6776                     strcat(programStats.movelist, " ");
6777                     strcat(programStats.movelist, p);
6778                 }
6779
6780                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6781                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6782                     DisplayMove(currentMove - 1);
6783                     DisplayAnalysis();
6784                 }
6785                 return;
6786             }
6787         }
6788         else {
6789             buf1[0] = NULLCHAR;
6790
6791             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6792                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6793             {
6794                 ChessProgramStats cpstats;
6795
6796                 if (plyext != ' ' && plyext != '\t') {
6797                     time *= 100;
6798                 }
6799
6800                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6801                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6802                     curscore = -curscore;
6803                 }
6804
6805                 cpstats.depth = plylev;
6806                 cpstats.nodes = nodes;
6807                 cpstats.time = time;
6808                 cpstats.score = curscore;
6809                 cpstats.got_only_move = 0;
6810                 cpstats.movelist[0] = '\0';
6811
6812                 if (buf1[0] != NULLCHAR) {
6813                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6814                 }
6815
6816                 cpstats.ok_to_send = 0;
6817                 cpstats.line_is_book = 0;
6818                 cpstats.nr_moves = 0;
6819                 cpstats.moves_left = 0;
6820
6821                 SendProgramStatsToFrontend( cps, &cpstats );
6822             }
6823         }
6824     }
6825 }
6826
6827
6828 /* Parse a game score from the character string "game", and
6829    record it as the history of the current game.  The game
6830    score is NOT assumed to start from the standard position. 
6831    The display is not updated in any way.
6832    */
6833 void
6834 ParseGameHistory(game)
6835      char *game;
6836 {
6837     ChessMove moveType;
6838     int fromX, fromY, toX, toY, boardIndex;
6839     char promoChar;
6840     char *p, *q;
6841     char buf[MSG_SIZ];
6842
6843     if (appData.debugMode)
6844       fprintf(debugFP, "Parsing game history: %s\n", game);
6845
6846     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6847     gameInfo.site = StrSave(appData.icsHost);
6848     gameInfo.date = PGNDate();
6849     gameInfo.round = StrSave("-");
6850
6851     /* Parse out names of players */
6852     while (*game == ' ') game++;
6853     p = buf;
6854     while (*game != ' ') *p++ = *game++;
6855     *p = NULLCHAR;
6856     gameInfo.white = StrSave(buf);
6857     while (*game == ' ') game++;
6858     p = buf;
6859     while (*game != ' ' && *game != '\n') *p++ = *game++;
6860     *p = NULLCHAR;
6861     gameInfo.black = StrSave(buf);
6862
6863     /* Parse moves */
6864     boardIndex = blackPlaysFirst ? 1 : 0;
6865     yynewstr(game);
6866     for (;;) {
6867         yyboardindex = boardIndex;
6868         moveType = (ChessMove) yylex();
6869         switch (moveType) {
6870           case IllegalMove:             /* maybe suicide chess, etc. */
6871   if (appData.debugMode) {
6872     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6873     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6874     setbuf(debugFP, NULL);
6875   }
6876           case WhitePromotionChancellor:
6877           case BlackPromotionChancellor:
6878           case WhitePromotionArchbishop:
6879           case BlackPromotionArchbishop:
6880           case WhitePromotionQueen:
6881           case BlackPromotionQueen:
6882           case WhitePromotionRook:
6883           case BlackPromotionRook:
6884           case WhitePromotionBishop:
6885           case BlackPromotionBishop:
6886           case WhitePromotionKnight:
6887           case BlackPromotionKnight:
6888           case WhitePromotionKing:
6889           case BlackPromotionKing:
6890           case NormalMove:
6891           case WhiteCapturesEnPassant:
6892           case BlackCapturesEnPassant:
6893           case WhiteKingSideCastle:
6894           case WhiteQueenSideCastle:
6895           case BlackKingSideCastle:
6896           case BlackQueenSideCastle:
6897           case WhiteKingSideCastleWild:
6898           case WhiteQueenSideCastleWild:
6899           case BlackKingSideCastleWild:
6900           case BlackQueenSideCastleWild:
6901           /* PUSH Fabien */
6902           case WhiteHSideCastleFR:
6903           case WhiteASideCastleFR:
6904           case BlackHSideCastleFR:
6905           case BlackASideCastleFR:
6906           /* POP Fabien */
6907             fromX = currentMoveString[0] - AAA;
6908             fromY = currentMoveString[1] - ONE;
6909             toX = currentMoveString[2] - AAA;
6910             toY = currentMoveString[3] - ONE;
6911             promoChar = currentMoveString[4];
6912             break;
6913           case WhiteDrop:
6914           case BlackDrop:
6915             fromX = moveType == WhiteDrop ?
6916               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6917             (int) CharToPiece(ToLower(currentMoveString[0]));
6918             fromY = DROP_RANK;
6919             toX = currentMoveString[2] - AAA;
6920             toY = currentMoveString[3] - ONE;
6921             promoChar = NULLCHAR;
6922             break;
6923           case AmbiguousMove:
6924             /* bug? */
6925             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6926   if (appData.debugMode) {
6927     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6928     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6929     setbuf(debugFP, NULL);
6930   }
6931             DisplayError(buf, 0);
6932             return;
6933           case ImpossibleMove:
6934             /* bug? */
6935             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6936   if (appData.debugMode) {
6937     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6938     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6939     setbuf(debugFP, NULL);
6940   }
6941             DisplayError(buf, 0);
6942             return;
6943           case (ChessMove) 0:   /* end of file */
6944             if (boardIndex < backwardMostMove) {
6945                 /* Oops, gap.  How did that happen? */
6946                 DisplayError(_("Gap in move list"), 0);
6947                 return;
6948             }
6949             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6950             if (boardIndex > forwardMostMove) {
6951                 forwardMostMove = boardIndex;
6952             }
6953             return;
6954           case ElapsedTime:
6955             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6956                 strcat(parseList[boardIndex-1], " ");
6957                 strcat(parseList[boardIndex-1], yy_text);
6958             }
6959             continue;
6960           case Comment:
6961           case PGNTag:
6962           case NAG:
6963           default:
6964             /* ignore */
6965             continue;
6966           case WhiteWins:
6967           case BlackWins:
6968           case GameIsDrawn:
6969           case GameUnfinished:
6970             if (gameMode == IcsExamining) {
6971                 if (boardIndex < backwardMostMove) {
6972                     /* Oops, gap.  How did that happen? */
6973                     return;
6974                 }
6975                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6976                 return;
6977             }
6978             gameInfo.result = moveType;
6979             p = strchr(yy_text, '{');
6980             if (p == NULL) p = strchr(yy_text, '(');
6981             if (p == NULL) {
6982                 p = yy_text;
6983                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6984             } else {
6985                 q = strchr(p, *p == '{' ? '}' : ')');
6986                 if (q != NULL) *q = NULLCHAR;
6987                 p++;
6988             }
6989             gameInfo.resultDetails = StrSave(p);
6990             continue;
6991         }
6992         if (boardIndex >= forwardMostMove &&
6993             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6994             backwardMostMove = blackPlaysFirst ? 1 : 0;
6995             return;
6996         }
6997         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6998                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6999                                  parseList[boardIndex]);
7000         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7001         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7002         /* currentMoveString is set as a side-effect of yylex */
7003         strcpy(moveList[boardIndex], currentMoveString);
7004         strcat(moveList[boardIndex], "\n");
7005         boardIndex++;
7006         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7007                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7008         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7009                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7010           case MT_NONE:
7011           case MT_STALEMATE:
7012           default:
7013             break;
7014           case MT_CHECK:
7015             if(gameInfo.variant != VariantShogi)
7016                 strcat(parseList[boardIndex - 1], "+");
7017             break;
7018           case MT_CHECKMATE:
7019           case MT_STAINMATE:
7020             strcat(parseList[boardIndex - 1], "#");
7021             break;
7022         }
7023     }
7024 }
7025
7026
7027 /* Apply a move to the given board  */
7028 void
7029 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7030      int fromX, fromY, toX, toY;
7031      int promoChar;
7032      Board board;
7033      char *castling;
7034      char *ep;
7035 {
7036   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7037
7038     /* [HGM] compute & store e.p. status and castling rights for new position */
7039     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7040     { int i;
7041
7042       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7043       oldEP = *ep;
7044       *ep = EP_NONE;
7045
7046       if( board[toY][toX] != EmptySquare ) 
7047            *ep = EP_CAPTURE;  
7048
7049       if( board[fromY][fromX] == WhitePawn ) {
7050            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7051                *ep = EP_PAWN_MOVE;
7052            if( toY-fromY==2) {
7053                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7054                         gameInfo.variant != VariantBerolina || toX < fromX)
7055                       *ep = toX | berolina;
7056                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7057                         gameInfo.variant != VariantBerolina || toX > fromX) 
7058                       *ep = toX;
7059            }
7060       } else 
7061       if( board[fromY][fromX] == BlackPawn ) {
7062            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7063                *ep = EP_PAWN_MOVE; 
7064            if( toY-fromY== -2) {
7065                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7066                         gameInfo.variant != VariantBerolina || toX < fromX)
7067                       *ep = toX | berolina;
7068                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7069                         gameInfo.variant != VariantBerolina || toX > fromX) 
7070                       *ep = toX;
7071            }
7072        }
7073
7074        for(i=0; i<nrCastlingRights; i++) {
7075            if(castling[i] == fromX && castlingRank[i] == fromY ||
7076               castling[i] == toX   && castlingRank[i] == toY   
7077              ) castling[i] = -1; // revoke for moved or captured piece
7078        }
7079
7080     }
7081
7082   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7083   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7084        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7085          
7086   if (fromX == toX && fromY == toY) return;
7087
7088   if (fromY == DROP_RANK) {
7089         /* must be first */
7090         piece = board[toY][toX] = (ChessSquare) fromX;
7091   } else {
7092      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7093      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7094      if(gameInfo.variant == VariantKnightmate)
7095          king += (int) WhiteUnicorn - (int) WhiteKing;
7096
7097     /* Code added by Tord: */
7098     /* FRC castling assumed when king captures friendly rook. */
7099     if (board[fromY][fromX] == WhiteKing &&
7100              board[toY][toX] == WhiteRook) {
7101       board[fromY][fromX] = EmptySquare;
7102       board[toY][toX] = EmptySquare;
7103       if(toX > fromX) {
7104         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7105       } else {
7106         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7107       }
7108     } else if (board[fromY][fromX] == BlackKing &&
7109                board[toY][toX] == BlackRook) {
7110       board[fromY][fromX] = EmptySquare;
7111       board[toY][toX] = EmptySquare;
7112       if(toX > fromX) {
7113         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7114       } else {
7115         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7116       }
7117     /* End of code added by Tord */
7118
7119     } else if (board[fromY][fromX] == king
7120         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7121         && toY == fromY && toX > fromX+1) {
7122         board[fromY][fromX] = EmptySquare;
7123         board[toY][toX] = king;
7124         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7125         board[fromY][BOARD_RGHT-1] = EmptySquare;
7126     } else if (board[fromY][fromX] == king
7127         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7128                && toY == fromY && toX < fromX-1) {
7129         board[fromY][fromX] = EmptySquare;
7130         board[toY][toX] = king;
7131         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7132         board[fromY][BOARD_LEFT] = EmptySquare;
7133     } else if (board[fromY][fromX] == WhitePawn
7134                && toY == BOARD_HEIGHT-1
7135                && gameInfo.variant != VariantXiangqi
7136                ) {
7137         /* white pawn promotion */
7138         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7139         if (board[toY][toX] == EmptySquare) {
7140             board[toY][toX] = WhiteQueen;
7141         }
7142         if(gameInfo.variant==VariantBughouse ||
7143            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7144             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7145         board[fromY][fromX] = EmptySquare;
7146     } else if ((fromY == BOARD_HEIGHT-4)
7147                && (toX != fromX)
7148                && gameInfo.variant != VariantXiangqi
7149                && gameInfo.variant != VariantBerolina
7150                && (board[fromY][fromX] == WhitePawn)
7151                && (board[toY][toX] == EmptySquare)) {
7152         board[fromY][fromX] = EmptySquare;
7153         board[toY][toX] = WhitePawn;
7154         captured = board[toY - 1][toX];
7155         board[toY - 1][toX] = EmptySquare;
7156     } else if ((fromY == BOARD_HEIGHT-4)
7157                && (toX == fromX)
7158                && gameInfo.variant == VariantBerolina
7159                && (board[fromY][fromX] == WhitePawn)
7160                && (board[toY][toX] == EmptySquare)) {
7161         board[fromY][fromX] = EmptySquare;
7162         board[toY][toX] = WhitePawn;
7163         if(oldEP & EP_BEROLIN_A) {
7164                 captured = board[fromY][fromX-1];
7165                 board[fromY][fromX-1] = EmptySquare;
7166         }else{  captured = board[fromY][fromX+1];
7167                 board[fromY][fromX+1] = EmptySquare;
7168         }
7169     } else if (board[fromY][fromX] == king
7170         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7171                && toY == fromY && toX > fromX+1) {
7172         board[fromY][fromX] = EmptySquare;
7173         board[toY][toX] = king;
7174         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7175         board[fromY][BOARD_RGHT-1] = EmptySquare;
7176     } else if (board[fromY][fromX] == king
7177         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7178                && toY == fromY && toX < fromX-1) {
7179         board[fromY][fromX] = EmptySquare;
7180         board[toY][toX] = king;
7181         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7182         board[fromY][BOARD_LEFT] = EmptySquare;
7183     } else if (fromY == 7 && fromX == 3
7184                && board[fromY][fromX] == BlackKing
7185                && toY == 7 && toX == 5) {
7186         board[fromY][fromX] = EmptySquare;
7187         board[toY][toX] = BlackKing;
7188         board[fromY][7] = EmptySquare;
7189         board[toY][4] = BlackRook;
7190     } else if (fromY == 7 && fromX == 3
7191                && board[fromY][fromX] == BlackKing
7192                && toY == 7 && toX == 1) {
7193         board[fromY][fromX] = EmptySquare;
7194         board[toY][toX] = BlackKing;
7195         board[fromY][0] = EmptySquare;
7196         board[toY][2] = BlackRook;
7197     } else if (board[fromY][fromX] == BlackPawn
7198                && toY == 0
7199                && gameInfo.variant != VariantXiangqi
7200                ) {
7201         /* black pawn promotion */
7202         board[0][toX] = CharToPiece(ToLower(promoChar));
7203         if (board[0][toX] == EmptySquare) {
7204             board[0][toX] = BlackQueen;
7205         }
7206         if(gameInfo.variant==VariantBughouse ||
7207            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7208             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7209         board[fromY][fromX] = EmptySquare;
7210     } else if ((fromY == 3)
7211                && (toX != fromX)
7212                && gameInfo.variant != VariantXiangqi
7213                && gameInfo.variant != VariantBerolina
7214                && (board[fromY][fromX] == BlackPawn)
7215                && (board[toY][toX] == EmptySquare)) {
7216         board[fromY][fromX] = EmptySquare;
7217         board[toY][toX] = BlackPawn;
7218         captured = board[toY + 1][toX];
7219         board[toY + 1][toX] = EmptySquare;
7220     } else if ((fromY == 3)
7221                && (toX == fromX)
7222                && gameInfo.variant == VariantBerolina
7223                && (board[fromY][fromX] == BlackPawn)
7224                && (board[toY][toX] == EmptySquare)) {
7225         board[fromY][fromX] = EmptySquare;
7226         board[toY][toX] = BlackPawn;
7227         if(oldEP & EP_BEROLIN_A) {
7228                 captured = board[fromY][fromX-1];
7229                 board[fromY][fromX-1] = EmptySquare;
7230         }else{  captured = board[fromY][fromX+1];
7231                 board[fromY][fromX+1] = EmptySquare;
7232         }
7233     } else {
7234         board[toY][toX] = board[fromY][fromX];
7235         board[fromY][fromX] = EmptySquare;
7236     }
7237
7238     /* [HGM] now we promote for Shogi, if needed */
7239     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7240         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7241   }
7242
7243     if (gameInfo.holdingsWidth != 0) {
7244
7245       /* !!A lot more code needs to be written to support holdings  */
7246       /* [HGM] OK, so I have written it. Holdings are stored in the */
7247       /* penultimate board files, so they are automaticlly stored   */
7248       /* in the game history.                                       */
7249       if (fromY == DROP_RANK) {
7250         /* Delete from holdings, by decreasing count */
7251         /* and erasing image if necessary            */
7252         p = (int) fromX;
7253         if(p < (int) BlackPawn) { /* white drop */
7254              p -= (int)WhitePawn;
7255              if(p >= gameInfo.holdingsSize) p = 0;
7256              if(--board[p][BOARD_WIDTH-2] == 0)
7257                   board[p][BOARD_WIDTH-1] = EmptySquare;
7258         } else {                  /* black drop */
7259              p -= (int)BlackPawn;
7260              if(p >= gameInfo.holdingsSize) p = 0;
7261              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7262                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7263         }
7264       }
7265       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7266           && gameInfo.variant != VariantBughouse        ) {
7267         /* [HGM] holdings: Add to holdings, if holdings exist */
7268         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7269                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7270                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7271         }
7272         p = (int) captured;
7273         if (p >= (int) BlackPawn) {
7274           p -= (int)BlackPawn;
7275           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7276                   /* in Shogi restore piece to its original  first */
7277                   captured = (ChessSquare) (DEMOTED captured);
7278                   p = DEMOTED p;
7279           }
7280           p = PieceToNumber((ChessSquare)p);
7281           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7282           board[p][BOARD_WIDTH-2]++;
7283           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7284         } else {
7285           p -= (int)WhitePawn;
7286           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7287                   captured = (ChessSquare) (DEMOTED captured);
7288                   p = DEMOTED p;
7289           }
7290           p = PieceToNumber((ChessSquare)p);
7291           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7292           board[BOARD_HEIGHT-1-p][1]++;
7293           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7294         }
7295       }
7296
7297     } else if (gameInfo.variant == VariantAtomic) {
7298       if (captured != EmptySquare) {
7299         int y, x;
7300         for (y = toY-1; y <= toY+1; y++) {
7301           for (x = toX-1; x <= toX+1; x++) {
7302             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7303                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7304               board[y][x] = EmptySquare;
7305             }
7306           }
7307         }
7308         board[toY][toX] = EmptySquare;
7309       }
7310     }
7311     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7312         /* [HGM] Shogi promotions */
7313         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7314     }
7315
7316     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7317                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7318         // [HGM] superchess: take promotion piece out of holdings
7319         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7320         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7321             if(!--board[k][BOARD_WIDTH-2])
7322                 board[k][BOARD_WIDTH-1] = EmptySquare;
7323         } else {
7324             if(!--board[BOARD_HEIGHT-1-k][1])
7325                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7326         }
7327     }
7328
7329 }
7330
7331 /* Updates forwardMostMove */
7332 void
7333 MakeMove(fromX, fromY, toX, toY, promoChar)
7334      int fromX, fromY, toX, toY;
7335      int promoChar;
7336 {
7337 //    forwardMostMove++; // [HGM] bare: moved downstream
7338
7339     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7340         int timeLeft; static int lastLoadFlag=0; int king, piece;
7341         piece = boards[forwardMostMove][fromY][fromX];
7342         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7343         if(gameInfo.variant == VariantKnightmate)
7344             king += (int) WhiteUnicorn - (int) WhiteKing;
7345         if(forwardMostMove == 0) {
7346             if(blackPlaysFirst) 
7347                 fprintf(serverMoves, "%s;", second.tidy);
7348             fprintf(serverMoves, "%s;", first.tidy);
7349             if(!blackPlaysFirst) 
7350                 fprintf(serverMoves, "%s;", second.tidy);
7351         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7352         lastLoadFlag = loadFlag;
7353         // print base move
7354         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7355         // print castling suffix
7356         if( toY == fromY && piece == king ) {
7357             if(toX-fromX > 1)
7358                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7359             if(fromX-toX >1)
7360                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7361         }
7362         // e.p. suffix
7363         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7364              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7365              boards[forwardMostMove][toY][toX] == EmptySquare
7366              && fromX != toX )
7367                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7368         // promotion suffix
7369         if(promoChar != NULLCHAR)
7370                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7371         if(!loadFlag) {
7372             fprintf(serverMoves, "/%d/%d",
7373                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7374             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7375             else                      timeLeft = blackTimeRemaining/1000;
7376             fprintf(serverMoves, "/%d", timeLeft);
7377         }
7378         fflush(serverMoves);
7379     }
7380
7381     if (forwardMostMove+1 >= MAX_MOVES) {
7382       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7383                         0, 1);
7384       return;
7385     }
7386     SwitchClocks();
7387     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7388     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7389     if (commentList[forwardMostMove+1] != NULL) {
7390         free(commentList[forwardMostMove+1]);
7391         commentList[forwardMostMove+1] = NULL;
7392     }
7393     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7394     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7395     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7396                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7397     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7398     gameInfo.result = GameUnfinished;
7399     if (gameInfo.resultDetails != NULL) {
7400         free(gameInfo.resultDetails);
7401         gameInfo.resultDetails = NULL;
7402     }
7403     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7404                               moveList[forwardMostMove - 1]);
7405     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7406                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7407                              fromY, fromX, toY, toX, promoChar,
7408                              parseList[forwardMostMove - 1]);
7409     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7410                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7411                             castlingRights[forwardMostMove]) ) {
7412       case MT_NONE:
7413       case MT_STALEMATE:
7414       default:
7415         break;
7416       case MT_CHECK:
7417         if(gameInfo.variant != VariantShogi)
7418             strcat(parseList[forwardMostMove - 1], "+");
7419         break;
7420       case MT_CHECKMATE:
7421       case MT_STAINMATE:
7422         strcat(parseList[forwardMostMove - 1], "#");
7423         break;
7424     }
7425     if (appData.debugMode) {
7426         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7427     }
7428
7429 }
7430
7431 /* Updates currentMove if not pausing */
7432 void
7433 ShowMove(fromX, fromY, toX, toY)
7434 {
7435     int instant = (gameMode == PlayFromGameFile) ?
7436         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7437     if(appData.noGUI) return;
7438     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7439         if (!instant) {
7440             if (forwardMostMove == currentMove + 1) {
7441                 AnimateMove(boards[forwardMostMove - 1],
7442                             fromX, fromY, toX, toY);
7443             }
7444             if (appData.highlightLastMove) {
7445                 SetHighlights(fromX, fromY, toX, toY);
7446             }
7447         }
7448         currentMove = forwardMostMove;
7449     }
7450
7451     if (instant) return;
7452
7453     DisplayMove(currentMove - 1);
7454     DrawPosition(FALSE, boards[currentMove]);
7455     DisplayBothClocks();
7456     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7457 }
7458
7459 void SendEgtPath(ChessProgramState *cps)
7460 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7461         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7462
7463         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7464
7465         while(*p) {
7466             char c, *q = name+1, *r, *s;
7467
7468             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7469             while(*p && *p != ',') *q++ = *p++;
7470             *q++ = ':'; *q = 0;
7471             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7472                 strcmp(name, ",nalimov:") == 0 ) {
7473                 // take nalimov path from the menu-changeable option first, if it is defined
7474                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7475                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7476             } else
7477             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7478                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7479                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7480                 s = r = StrStr(s, ":") + 1; // beginning of path info
7481                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7482                 c = *r; *r = 0;             // temporarily null-terminate path info
7483                     *--q = 0;               // strip of trailig ':' from name
7484                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7485                 *r = c;
7486                 SendToProgram(buf,cps);     // send egtbpath command for this format
7487             }
7488             if(*p == ',') p++; // read away comma to position for next format name
7489         }
7490 }
7491
7492 void
7493 InitChessProgram(cps, setup)
7494      ChessProgramState *cps;
7495      int setup; /* [HGM] needed to setup FRC opening position */
7496 {
7497     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7498     if (appData.noChessProgram) return;
7499     hintRequested = FALSE;
7500     bookRequested = FALSE;
7501
7502     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7503     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7504     if(cps->memSize) { /* [HGM] memory */
7505         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7506         SendToProgram(buf, cps);
7507     }
7508     SendEgtPath(cps); /* [HGM] EGT */
7509     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7510         sprintf(buf, "cores %d\n", appData.smpCores);
7511         SendToProgram(buf, cps);
7512     }
7513
7514     SendToProgram(cps->initString, cps);
7515     if (gameInfo.variant != VariantNormal &&
7516         gameInfo.variant != VariantLoadable
7517         /* [HGM] also send variant if board size non-standard */
7518         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7519                                             ) {
7520       char *v = VariantName(gameInfo.variant);
7521       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7522         /* [HGM] in protocol 1 we have to assume all variants valid */
7523         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7524         DisplayFatalError(buf, 0, 1);
7525         return;
7526       }
7527
7528       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7529       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7530       if( gameInfo.variant == VariantXiangqi )
7531            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7532       if( gameInfo.variant == VariantShogi )
7533            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7534       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7535            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7536       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7537                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7538            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7539       if( gameInfo.variant == VariantCourier )
7540            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7541       if( gameInfo.variant == VariantSuper )
7542            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7543       if( gameInfo.variant == VariantGreat )
7544            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7545
7546       if(overruled) {
7547            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7548                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7549            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7550            if(StrStr(cps->variants, b) == NULL) { 
7551                // specific sized variant not known, check if general sizing allowed
7552                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7553                    if(StrStr(cps->variants, "boardsize") == NULL) {
7554                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7555                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7556                        DisplayFatalError(buf, 0, 1);
7557                        return;
7558                    }
7559                    /* [HGM] here we really should compare with the maximum supported board size */
7560                }
7561            }
7562       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7563       sprintf(buf, "variant %s\n", b);
7564       SendToProgram(buf, cps);
7565     }
7566     currentlyInitializedVariant = gameInfo.variant;
7567
7568     /* [HGM] send opening position in FRC to first engine */
7569     if(setup) {
7570           SendToProgram("force\n", cps);
7571           SendBoard(cps, 0);
7572           /* engine is now in force mode! Set flag to wake it up after first move. */
7573           setboardSpoiledMachineBlack = 1;
7574     }
7575
7576     if (cps->sendICS) {
7577       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7578       SendToProgram(buf, cps);
7579     }
7580     cps->maybeThinking = FALSE;
7581     cps->offeredDraw = 0;
7582     if (!appData.icsActive) {
7583         SendTimeControl(cps, movesPerSession, timeControl,
7584                         timeIncrement, appData.searchDepth,
7585                         searchTime);
7586     }
7587     if (appData.showThinking 
7588         // [HGM] thinking: four options require thinking output to be sent
7589         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7590                                 ) {
7591         SendToProgram("post\n", cps);
7592     }
7593     SendToProgram("hard\n", cps);
7594     if (!appData.ponderNextMove) {
7595         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7596            it without being sure what state we are in first.  "hard"
7597            is not a toggle, so that one is OK.
7598          */
7599         SendToProgram("easy\n", cps);
7600     }
7601     if (cps->usePing) {
7602       sprintf(buf, "ping %d\n", ++cps->lastPing);
7603       SendToProgram(buf, cps);
7604     }
7605     cps->initDone = TRUE;
7606 }   
7607
7608
7609 void
7610 StartChessProgram(cps)
7611      ChessProgramState *cps;
7612 {
7613     char buf[MSG_SIZ];
7614     int err;
7615
7616     if (appData.noChessProgram) return;
7617     cps->initDone = FALSE;
7618
7619     if (strcmp(cps->host, "localhost") == 0) {
7620         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7621     } else if (*appData.remoteShell == NULLCHAR) {
7622         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7623     } else {
7624         if (*appData.remoteUser == NULLCHAR) {
7625           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7626                     cps->program);
7627         } else {
7628           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7629                     cps->host, appData.remoteUser, cps->program);
7630         }
7631         err = StartChildProcess(buf, "", &cps->pr);
7632     }
7633     
7634     if (err != 0) {
7635         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7636         DisplayFatalError(buf, err, 1);
7637         cps->pr = NoProc;
7638         cps->isr = NULL;
7639         return;
7640     }
7641     
7642     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7643     if (cps->protocolVersion > 1) {
7644       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7645       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7646       cps->comboCnt = 0;  //                and values of combo boxes
7647       SendToProgram(buf, cps);
7648     } else {
7649       SendToProgram("xboard\n", cps);
7650     }
7651 }
7652
7653
7654 void
7655 TwoMachinesEventIfReady P((void))
7656 {
7657   if (first.lastPing != first.lastPong) {
7658     DisplayMessage("", _("Waiting for first chess program"));
7659     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7660     return;
7661   }
7662   if (second.lastPing != second.lastPong) {
7663     DisplayMessage("", _("Waiting for second chess program"));
7664     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7665     return;
7666   }
7667   ThawUI();
7668   TwoMachinesEvent();
7669 }
7670
7671 void
7672 NextMatchGame P((void))
7673 {
7674     int index; /* [HGM] autoinc: step lod index during match */
7675     Reset(FALSE, TRUE);
7676     if (*appData.loadGameFile != NULLCHAR) {
7677         index = appData.loadGameIndex;
7678         if(index < 0) { // [HGM] autoinc
7679             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7680             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7681         } 
7682         LoadGameFromFile(appData.loadGameFile,
7683                          index,
7684                          appData.loadGameFile, FALSE);
7685     } else if (*appData.loadPositionFile != NULLCHAR) {
7686         index = appData.loadPositionIndex;
7687         if(index < 0) { // [HGM] autoinc
7688             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7689             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7690         } 
7691         LoadPositionFromFile(appData.loadPositionFile,
7692                              index,
7693                              appData.loadPositionFile);
7694     }
7695     TwoMachinesEventIfReady();
7696 }
7697
7698 void UserAdjudicationEvent( int result )
7699 {
7700     ChessMove gameResult = GameIsDrawn;
7701
7702     if( result > 0 ) {
7703         gameResult = WhiteWins;
7704     }
7705     else if( result < 0 ) {
7706         gameResult = BlackWins;
7707     }
7708
7709     if( gameMode == TwoMachinesPlay ) {
7710         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7711     }
7712 }
7713
7714
7715 void
7716 GameEnds(result, resultDetails, whosays)
7717      ChessMove result;
7718      char *resultDetails;
7719      int whosays;
7720 {
7721     GameMode nextGameMode;
7722     int isIcsGame;
7723     char buf[MSG_SIZ];
7724
7725     if(endingGame) return; /* [HGM] crash: forbid recursion */
7726     endingGame = 1;
7727
7728     if (appData.debugMode) {
7729       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7730               result, resultDetails ? resultDetails : "(null)", whosays);
7731     }
7732
7733     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7734         /* If we are playing on ICS, the server decides when the
7735            game is over, but the engine can offer to draw, claim 
7736            a draw, or resign. 
7737          */
7738 #if ZIPPY
7739         if (appData.zippyPlay && first.initDone) {
7740             if (result == GameIsDrawn) {
7741                 /* In case draw still needs to be claimed */
7742                 SendToICS(ics_prefix);
7743                 SendToICS("draw\n");
7744             } else if (StrCaseStr(resultDetails, "resign")) {
7745                 SendToICS(ics_prefix);
7746                 SendToICS("resign\n");
7747             }
7748         }
7749 #endif
7750         endingGame = 0; /* [HGM] crash */
7751         return;
7752     }
7753
7754     /* If we're loading the game from a file, stop */
7755     if (whosays == GE_FILE) {
7756       (void) StopLoadGameTimer();
7757       gameFileFP = NULL;
7758     }
7759
7760     /* Cancel draw offers */
7761     first.offeredDraw = second.offeredDraw = 0;
7762
7763     /* If this is an ICS game, only ICS can really say it's done;
7764        if not, anyone can. */
7765     isIcsGame = (gameMode == IcsPlayingWhite || 
7766                  gameMode == IcsPlayingBlack || 
7767                  gameMode == IcsObserving    || 
7768                  gameMode == IcsExamining);
7769
7770     if (!isIcsGame || whosays == GE_ICS) {
7771         /* OK -- not an ICS game, or ICS said it was done */
7772         StopClocks();
7773         if (!isIcsGame && !appData.noChessProgram) 
7774           SetUserThinkingEnables();
7775
7776         /* [HGM] if a machine claims the game end we verify this claim */
7777         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7778             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7779                 char claimer;
7780                 ChessMove trueResult = (ChessMove) -1;
7781
7782                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7783                                             first.twoMachinesColor[0] :
7784                                             second.twoMachinesColor[0] ;
7785
7786                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7787                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7788                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7789                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7790                 } else
7791                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7792                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7793                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7794                 } else
7795                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7796                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7797                 }
7798
7799                 // now verify win claims, but not in drop games, as we don't understand those yet
7800                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7801                                                  || gameInfo.variant == VariantGreat) &&
7802                     (result == WhiteWins && claimer == 'w' ||
7803                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7804                       if (appData.debugMode) {
7805                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7806                                 result, epStatus[forwardMostMove], forwardMostMove);
7807                       }
7808                       if(result != trueResult) {
7809                               sprintf(buf, "False win claim: '%s'", resultDetails);
7810                               result = claimer == 'w' ? BlackWins : WhiteWins;
7811                               resultDetails = buf;
7812                       }
7813                 } else
7814                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7815                     && (forwardMostMove <= backwardMostMove ||
7816                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7817                         (claimer=='b')==(forwardMostMove&1))
7818                                                                                   ) {
7819                       /* [HGM] verify: draws that were not flagged are false claims */
7820                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7821                       result = claimer == 'w' ? BlackWins : WhiteWins;
7822                       resultDetails = buf;
7823                 }
7824                 /* (Claiming a loss is accepted no questions asked!) */
7825             }
7826
7827             /* [HGM] bare: don't allow bare King to win */
7828             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7829                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7830                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7831                && result != GameIsDrawn)
7832             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7833                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7834                         int p = (int)boards[forwardMostMove][i][j] - color;
7835                         if(p >= 0 && p <= (int)WhiteKing) k++;
7836                 }
7837                 if (appData.debugMode) {
7838                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7839                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7840                 }
7841                 if(k <= 1) {
7842                         result = GameIsDrawn;
7843                         sprintf(buf, "%s but bare king", resultDetails);
7844                         resultDetails = buf;
7845                 }
7846             }
7847         }
7848
7849         if(serverMoves != NULL && !loadFlag) { char c = '=';
7850             if(result==WhiteWins) c = '+';
7851             if(result==BlackWins) c = '-';
7852             if(resultDetails != NULL)
7853                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7854         }
7855         if (resultDetails != NULL) {
7856             gameInfo.result = result;
7857             gameInfo.resultDetails = StrSave(resultDetails);
7858
7859             /* display last move only if game was not loaded from file */
7860             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7861                 DisplayMove(currentMove - 1);
7862     
7863             if (forwardMostMove != 0) {
7864                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7865                     if (*appData.saveGameFile != NULLCHAR) {
7866                         SaveGameToFile(appData.saveGameFile, TRUE);
7867                     } else if (appData.autoSaveGames) {
7868                         AutoSaveGame();
7869                     }
7870                     if (*appData.savePositionFile != NULLCHAR) {
7871                         SavePositionToFile(appData.savePositionFile);
7872                     }
7873                 }
7874             }
7875
7876             /* Tell program how game ended in case it is learning */
7877             /* [HGM] Moved this to after saving the PGN, just in case */
7878             /* engine died and we got here through time loss. In that */
7879             /* case we will get a fatal error writing the pipe, which */
7880             /* would otherwise lose us the PGN.                       */
7881             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7882             /* output during GameEnds should never be fatal anymore   */
7883             if (gameMode == MachinePlaysWhite ||
7884                 gameMode == MachinePlaysBlack ||
7885                 gameMode == TwoMachinesPlay ||
7886                 gameMode == IcsPlayingWhite ||
7887                 gameMode == IcsPlayingBlack ||
7888                 gameMode == BeginningOfGame) {
7889                 char buf[MSG_SIZ];
7890                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7891                         resultDetails);
7892                 if (first.pr != NoProc) {
7893                     SendToProgram(buf, &first);
7894                 }
7895                 if (second.pr != NoProc &&
7896                     gameMode == TwoMachinesPlay) {
7897                     SendToProgram(buf, &second);
7898                 }
7899             }
7900         }
7901
7902         if (appData.icsActive) {
7903             if (appData.quietPlay &&
7904                 (gameMode == IcsPlayingWhite ||
7905                  gameMode == IcsPlayingBlack)) {
7906                 SendToICS(ics_prefix);
7907                 SendToICS("set shout 1\n");
7908             }
7909             nextGameMode = IcsIdle;
7910             ics_user_moved = FALSE;
7911             /* clean up premove.  It's ugly when the game has ended and the
7912              * premove highlights are still on the board.
7913              */
7914             if (gotPremove) {
7915               gotPremove = FALSE;
7916               ClearPremoveHighlights();
7917               DrawPosition(FALSE, boards[currentMove]);
7918             }
7919             if (whosays == GE_ICS) {
7920                 switch (result) {
7921                 case WhiteWins:
7922                     if (gameMode == IcsPlayingWhite)
7923                         PlayIcsWinSound();
7924                     else if(gameMode == IcsPlayingBlack)
7925                         PlayIcsLossSound();
7926                     break;
7927                 case BlackWins:
7928                     if (gameMode == IcsPlayingBlack)
7929                         PlayIcsWinSound();
7930                     else if(gameMode == IcsPlayingWhite)
7931                         PlayIcsLossSound();
7932                     break;
7933                 case GameIsDrawn:
7934                     PlayIcsDrawSound();
7935                     break;
7936                 default:
7937                     PlayIcsUnfinishedSound();
7938                 }
7939             }
7940         } else if (gameMode == EditGame ||
7941                    gameMode == PlayFromGameFile || 
7942                    gameMode == AnalyzeMode || 
7943                    gameMode == AnalyzeFile) {
7944             nextGameMode = gameMode;
7945         } else {
7946             nextGameMode = EndOfGame;
7947         }
7948         pausing = FALSE;
7949         ModeHighlight();
7950     } else {
7951         nextGameMode = gameMode;
7952     }
7953
7954     if (appData.noChessProgram) {
7955         gameMode = nextGameMode;
7956         ModeHighlight();
7957         endingGame = 0; /* [HGM] crash */
7958         return;
7959     }
7960
7961     if (first.reuse) {
7962         /* Put first chess program into idle state */
7963         if (first.pr != NoProc &&
7964             (gameMode == MachinePlaysWhite ||
7965              gameMode == MachinePlaysBlack ||
7966              gameMode == TwoMachinesPlay ||
7967              gameMode == IcsPlayingWhite ||
7968              gameMode == IcsPlayingBlack ||
7969              gameMode == BeginningOfGame)) {
7970             SendToProgram("force\n", &first);
7971             if (first.usePing) {
7972               char buf[MSG_SIZ];
7973               sprintf(buf, "ping %d\n", ++first.lastPing);
7974               SendToProgram(buf, &first);
7975             }
7976         }
7977     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7978         /* Kill off first chess program */
7979         if (first.isr != NULL)
7980           RemoveInputSource(first.isr);
7981         first.isr = NULL;
7982     
7983         if (first.pr != NoProc) {
7984             ExitAnalyzeMode();
7985             DoSleep( appData.delayBeforeQuit );
7986             SendToProgram("quit\n", &first);
7987             DoSleep( appData.delayAfterQuit );
7988             DestroyChildProcess(first.pr, first.useSigterm);
7989         }
7990         first.pr = NoProc;
7991     }
7992     if (second.reuse) {
7993         /* Put second chess program into idle state */
7994         if (second.pr != NoProc &&
7995             gameMode == TwoMachinesPlay) {
7996             SendToProgram("force\n", &second);
7997             if (second.usePing) {
7998               char buf[MSG_SIZ];
7999               sprintf(buf, "ping %d\n", ++second.lastPing);
8000               SendToProgram(buf, &second);
8001             }
8002         }
8003     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8004         /* Kill off second chess program */
8005         if (second.isr != NULL)
8006           RemoveInputSource(second.isr);
8007         second.isr = NULL;
8008     
8009         if (second.pr != NoProc) {
8010             DoSleep( appData.delayBeforeQuit );
8011             SendToProgram("quit\n", &second);
8012             DoSleep( appData.delayAfterQuit );
8013             DestroyChildProcess(second.pr, second.useSigterm);
8014         }
8015         second.pr = NoProc;
8016     }
8017
8018     if (matchMode && gameMode == TwoMachinesPlay) {
8019         switch (result) {
8020         case WhiteWins:
8021           if (first.twoMachinesColor[0] == 'w') {
8022             first.matchWins++;
8023           } else {
8024             second.matchWins++;
8025           }
8026           break;
8027         case BlackWins:
8028           if (first.twoMachinesColor[0] == 'b') {
8029             first.matchWins++;
8030           } else {
8031             second.matchWins++;
8032           }
8033           break;
8034         default:
8035           break;
8036         }
8037         if (matchGame < appData.matchGames) {
8038             char *tmp;
8039             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8040                 tmp = first.twoMachinesColor;
8041                 first.twoMachinesColor = second.twoMachinesColor;
8042                 second.twoMachinesColor = tmp;
8043             }
8044             gameMode = nextGameMode;
8045             matchGame++;
8046             if(appData.matchPause>10000 || appData.matchPause<10)
8047                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8048             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8049             endingGame = 0; /* [HGM] crash */
8050             return;
8051         } else {
8052             char buf[MSG_SIZ];
8053             gameMode = nextGameMode;
8054             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8055                     first.tidy, second.tidy,
8056                     first.matchWins, second.matchWins,
8057                     appData.matchGames - (first.matchWins + second.matchWins));
8058             DisplayFatalError(buf, 0, 0);
8059         }
8060     }
8061     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8062         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8063       ExitAnalyzeMode();
8064     gameMode = nextGameMode;
8065     ModeHighlight();
8066     endingGame = 0;  /* [HGM] crash */
8067 }
8068
8069 /* Assumes program was just initialized (initString sent).
8070    Leaves program in force mode. */
8071 void
8072 FeedMovesToProgram(cps, upto) 
8073      ChessProgramState *cps;
8074      int upto;
8075 {
8076     int i;
8077     
8078     if (appData.debugMode)
8079       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8080               startedFromSetupPosition ? "position and " : "",
8081               backwardMostMove, upto, cps->which);
8082     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8083         // [HGM] variantswitch: make engine aware of new variant
8084         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8085                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8086         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8087         SendToProgram(buf, cps);
8088         currentlyInitializedVariant = gameInfo.variant;
8089     }
8090     SendToProgram("force\n", cps);
8091     if (startedFromSetupPosition) {
8092         SendBoard(cps, backwardMostMove);
8093     if (appData.debugMode) {
8094         fprintf(debugFP, "feedMoves\n");
8095     }
8096     }
8097     for (i = backwardMostMove; i < upto; i++) {
8098         SendMoveToProgram(i, cps);
8099     }
8100 }
8101
8102
8103 void
8104 ResurrectChessProgram()
8105 {
8106      /* The chess program may have exited.
8107         If so, restart it and feed it all the moves made so far. */
8108
8109     if (appData.noChessProgram || first.pr != NoProc) return;
8110     
8111     StartChessProgram(&first);
8112     InitChessProgram(&first, FALSE);
8113     FeedMovesToProgram(&first, currentMove);
8114
8115     if (!first.sendTime) {
8116         /* can't tell gnuchess what its clock should read,
8117            so we bow to its notion. */
8118         ResetClocks();
8119         timeRemaining[0][currentMove] = whiteTimeRemaining;
8120         timeRemaining[1][currentMove] = blackTimeRemaining;
8121     }
8122
8123     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8124                 appData.icsEngineAnalyze) && first.analysisSupport) {
8125       SendToProgram("analyze\n", &first);
8126       first.analyzing = TRUE;
8127     }
8128 }
8129
8130 /*
8131  * Button procedures
8132  */
8133 void
8134 Reset(redraw, init)
8135      int redraw, init;
8136 {
8137     int i;
8138
8139     if (appData.debugMode) {
8140         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8141                 redraw, init, gameMode);
8142     }
8143     pausing = pauseExamInvalid = FALSE;
8144     startedFromSetupPosition = blackPlaysFirst = FALSE;
8145     firstMove = TRUE;
8146     whiteFlag = blackFlag = FALSE;
8147     userOfferedDraw = FALSE;
8148     hintRequested = bookRequested = FALSE;
8149     first.maybeThinking = FALSE;
8150     second.maybeThinking = FALSE;
8151     first.bookSuspend = FALSE; // [HGM] book
8152     second.bookSuspend = FALSE;
8153     thinkOutput[0] = NULLCHAR;
8154     lastHint[0] = NULLCHAR;
8155     ClearGameInfo(&gameInfo);
8156     gameInfo.variant = StringToVariant(appData.variant);
8157     ics_user_moved = ics_clock_paused = FALSE;
8158     ics_getting_history = H_FALSE;
8159     ics_gamenum = -1;
8160     white_holding[0] = black_holding[0] = NULLCHAR;
8161     ClearProgramStats();
8162     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8163     
8164     ResetFrontEnd();
8165     ClearHighlights();
8166     flipView = appData.flipView;
8167     ClearPremoveHighlights();
8168     gotPremove = FALSE;
8169     alarmSounded = FALSE;
8170
8171     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8172     if(appData.serverMovesName != NULL) {
8173         /* [HGM] prepare to make moves file for broadcasting */
8174         clock_t t = clock();
8175         if(serverMoves != NULL) fclose(serverMoves);
8176         serverMoves = fopen(appData.serverMovesName, "r");
8177         if(serverMoves != NULL) {
8178             fclose(serverMoves);
8179             /* delay 15 sec before overwriting, so all clients can see end */
8180             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8181         }
8182         serverMoves = fopen(appData.serverMovesName, "w");
8183     }
8184
8185     ExitAnalyzeMode();
8186     gameMode = BeginningOfGame;
8187     ModeHighlight();
8188
8189     if(appData.icsActive) gameInfo.variant = VariantNormal;
8190     InitPosition(redraw);
8191     for (i = 0; i < MAX_MOVES; i++) {
8192         if (commentList[i] != NULL) {
8193             free(commentList[i]);
8194             commentList[i] = NULL;
8195         }
8196     }
8197
8198     ResetClocks();
8199     timeRemaining[0][0] = whiteTimeRemaining;
8200     timeRemaining[1][0] = blackTimeRemaining;
8201     if (first.pr == NULL) {
8202         StartChessProgram(&first);
8203     }
8204     if (init) {
8205             InitChessProgram(&first, startedFromSetupPosition);
8206     }
8207     
8208     GUI_DisplayTitle("");
8209     DisplayMessage("", "");
8210     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8211
8212 }
8213
8214 void
8215 AutoPlayGameLoop()
8216 {
8217     for (;;) {
8218         if (!AutoPlayOneMove())
8219           return;
8220         if (matchMode || appData.timeDelay == 0)
8221           continue;
8222         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8223           return;
8224         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8225         break;
8226     }
8227 }
8228
8229
8230 int
8231 AutoPlayOneMove()
8232 {
8233     int fromX, fromY, toX, toY;
8234
8235     if (appData.debugMode) {
8236       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8237     }
8238
8239     if (gameMode != PlayFromGameFile)
8240       return FALSE;
8241
8242     if (currentMove >= forwardMostMove) {
8243       gameMode = EditGame;
8244       ModeHighlight();
8245
8246       /* [AS] Clear current move marker at the end of a game */
8247       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8248
8249       return FALSE;
8250     }
8251     
8252     toX = moveList[currentMove][2] - AAA;
8253     toY = moveList[currentMove][3] - ONE;
8254
8255     if (moveList[currentMove][1] == '@') {
8256         if (appData.highlightLastMove) {
8257             SetHighlights(-1, -1, toX, toY);
8258         }
8259     } else {
8260         fromX = moveList[currentMove][0] - AAA;
8261         fromY = moveList[currentMove][1] - ONE;
8262
8263         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8264
8265         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8266
8267         if (appData.highlightLastMove) {
8268             SetHighlights(fromX, fromY, toX, toY);
8269         }
8270     }
8271     DisplayMove(currentMove);
8272     SendMoveToProgram(currentMove++, &first);
8273     DisplayBothClocks();
8274     DrawPosition(FALSE, boards[currentMove]);
8275     // [HGM] PV info: always display, routine tests if empty
8276     DisplayComment(currentMove - 1, commentList[currentMove]);
8277     return TRUE;
8278 }
8279
8280
8281 int
8282 LoadGameOneMove(readAhead)
8283      ChessMove readAhead;
8284 {
8285     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8286     char promoChar = NULLCHAR;
8287     ChessMove moveType;
8288     char move[MSG_SIZ];
8289     char *p, *q;
8290     
8291     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8292         gameMode != AnalyzeMode && gameMode != Training) {
8293         gameFileFP = NULL;
8294         return FALSE;
8295     }
8296     
8297     yyboardindex = forwardMostMove;
8298     if (readAhead != (ChessMove)0) {
8299       moveType = readAhead;
8300     } else {
8301       if (gameFileFP == NULL)
8302           return FALSE;
8303       moveType = (ChessMove) yylex();
8304     }
8305     
8306     done = FALSE;
8307     switch (moveType) {
8308       case Comment:
8309         if (appData.debugMode) 
8310           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8311         p = yy_text;
8312         if (*p == '{' || *p == '[' || *p == '(') {
8313             p[strlen(p) - 1] = NULLCHAR;
8314             p++;
8315         }
8316
8317         /* append the comment but don't display it */
8318         while (*p == '\n') p++;
8319         AppendComment(currentMove, p);
8320         return TRUE;
8321
8322       case WhiteCapturesEnPassant:
8323       case BlackCapturesEnPassant:
8324       case WhitePromotionChancellor:
8325       case BlackPromotionChancellor:
8326       case WhitePromotionArchbishop:
8327       case BlackPromotionArchbishop:
8328       case WhitePromotionCentaur:
8329       case BlackPromotionCentaur:
8330       case WhitePromotionQueen:
8331       case BlackPromotionQueen:
8332       case WhitePromotionRook:
8333       case BlackPromotionRook:
8334       case WhitePromotionBishop:
8335       case BlackPromotionBishop:
8336       case WhitePromotionKnight:
8337       case BlackPromotionKnight:
8338       case WhitePromotionKing:
8339       case BlackPromotionKing:
8340       case NormalMove:
8341       case WhiteKingSideCastle:
8342       case WhiteQueenSideCastle:
8343       case BlackKingSideCastle:
8344       case BlackQueenSideCastle:
8345       case WhiteKingSideCastleWild:
8346       case WhiteQueenSideCastleWild:
8347       case BlackKingSideCastleWild:
8348       case BlackQueenSideCastleWild:
8349       /* PUSH Fabien */
8350       case WhiteHSideCastleFR:
8351       case WhiteASideCastleFR:
8352       case BlackHSideCastleFR:
8353       case BlackASideCastleFR:
8354       /* POP Fabien */
8355         if (appData.debugMode)
8356           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8357         fromX = currentMoveString[0] - AAA;
8358         fromY = currentMoveString[1] - ONE;
8359         toX = currentMoveString[2] - AAA;
8360         toY = currentMoveString[3] - ONE;
8361         promoChar = currentMoveString[4];
8362         break;
8363
8364       case WhiteDrop:
8365       case BlackDrop:
8366         if (appData.debugMode)
8367           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8368         fromX = moveType == WhiteDrop ?
8369           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8370         (int) CharToPiece(ToLower(currentMoveString[0]));
8371         fromY = DROP_RANK;
8372         toX = currentMoveString[2] - AAA;
8373         toY = currentMoveString[3] - ONE;
8374         break;
8375
8376       case WhiteWins:
8377       case BlackWins:
8378       case GameIsDrawn:
8379       case GameUnfinished:
8380         if (appData.debugMode)
8381           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8382         p = strchr(yy_text, '{');
8383         if (p == NULL) p = strchr(yy_text, '(');
8384         if (p == NULL) {
8385             p = yy_text;
8386             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8387         } else {
8388             q = strchr(p, *p == '{' ? '}' : ')');
8389             if (q != NULL) *q = NULLCHAR;
8390             p++;
8391         }
8392         GameEnds(moveType, p, GE_FILE);
8393         done = TRUE;
8394         if (cmailMsgLoaded) {
8395             ClearHighlights();
8396             flipView = WhiteOnMove(currentMove);
8397             if (moveType == GameUnfinished) flipView = !flipView;
8398             if (appData.debugMode)
8399               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8400         }
8401         break;
8402
8403       case (ChessMove) 0:       /* end of file */
8404         if (appData.debugMode)
8405           fprintf(debugFP, "Parser hit end of file\n");
8406         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8407                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8408           case MT_NONE:
8409           case MT_CHECK:
8410             break;
8411           case MT_CHECKMATE:
8412           case MT_STAINMATE:
8413             if (WhiteOnMove(currentMove)) {
8414                 GameEnds(BlackWins, "Black mates", GE_FILE);
8415             } else {
8416                 GameEnds(WhiteWins, "White mates", GE_FILE);
8417             }
8418             break;
8419           case MT_STALEMATE:
8420             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8421             break;
8422         }
8423         done = TRUE;
8424         break;
8425
8426       case MoveNumberOne:
8427         if (lastLoadGameStart == GNUChessGame) {
8428             /* GNUChessGames have numbers, but they aren't move numbers */
8429             if (appData.debugMode)
8430               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8431                       yy_text, (int) moveType);
8432             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8433         }
8434         /* else fall thru */
8435
8436       case XBoardGame:
8437       case GNUChessGame:
8438       case PGNTag:
8439         /* Reached start of next game in file */
8440         if (appData.debugMode)
8441           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8442         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8443                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8444           case MT_NONE:
8445           case MT_CHECK:
8446             break;
8447           case MT_CHECKMATE:
8448           case MT_STAINMATE:
8449             if (WhiteOnMove(currentMove)) {
8450                 GameEnds(BlackWins, "Black mates", GE_FILE);
8451             } else {
8452                 GameEnds(WhiteWins, "White mates", GE_FILE);
8453             }
8454             break;
8455           case MT_STALEMATE:
8456             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8457             break;
8458         }
8459         done = TRUE;
8460         break;
8461
8462       case PositionDiagram:     /* should not happen; ignore */
8463       case ElapsedTime:         /* ignore */
8464       case NAG:                 /* ignore */
8465         if (appData.debugMode)
8466           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8467                   yy_text, (int) moveType);
8468         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8469
8470       case IllegalMove:
8471         if (appData.testLegality) {
8472             if (appData.debugMode)
8473               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8474             sprintf(move, _("Illegal move: %d.%s%s"),
8475                     (forwardMostMove / 2) + 1,
8476                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8477             DisplayError(move, 0);
8478             done = TRUE;
8479         } else {
8480             if (appData.debugMode)
8481               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8482                       yy_text, currentMoveString);
8483             fromX = currentMoveString[0] - AAA;
8484             fromY = currentMoveString[1] - ONE;
8485             toX = currentMoveString[2] - AAA;
8486             toY = currentMoveString[3] - ONE;
8487             promoChar = currentMoveString[4];
8488         }
8489         break;
8490
8491       case AmbiguousMove:
8492         if (appData.debugMode)
8493           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8494         sprintf(move, _("Ambiguous move: %d.%s%s"),
8495                 (forwardMostMove / 2) + 1,
8496                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8497         DisplayError(move, 0);
8498         done = TRUE;
8499         break;
8500
8501       default:
8502       case ImpossibleMove:
8503         if (appData.debugMode)
8504           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8505         sprintf(move, _("Illegal move: %d.%s%s"),
8506                 (forwardMostMove / 2) + 1,
8507                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8508         DisplayError(move, 0);
8509         done = TRUE;
8510         break;
8511     }
8512
8513     if (done) {
8514         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8515             DrawPosition(FALSE, boards[currentMove]);
8516             DisplayBothClocks();
8517             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8518               DisplayComment(currentMove - 1, commentList[currentMove]);
8519         }
8520         (void) StopLoadGameTimer();
8521         gameFileFP = NULL;
8522         cmailOldMove = forwardMostMove;
8523         return FALSE;
8524     } else {
8525         /* currentMoveString is set as a side-effect of yylex */
8526         strcat(currentMoveString, "\n");
8527         strcpy(moveList[forwardMostMove], currentMoveString);
8528         
8529         thinkOutput[0] = NULLCHAR;
8530         MakeMove(fromX, fromY, toX, toY, promoChar);
8531         currentMove = forwardMostMove;
8532         return TRUE;
8533     }
8534 }
8535
8536 /* Load the nth game from the given file */
8537 int
8538 LoadGameFromFile(filename, n, title, useList)
8539      char *filename;
8540      int n;
8541      char *title;
8542      /*Boolean*/ int useList;
8543 {
8544     FILE *f;
8545     char buf[MSG_SIZ];
8546
8547     if (strcmp(filename, "-") == 0) {
8548         f = stdin;
8549         title = "stdin";
8550     } else {
8551         f = fopen(filename, "rb");
8552         if (f == NULL) {
8553           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8554             DisplayError(buf, errno);
8555             return FALSE;
8556         }
8557     }
8558     if (fseek(f, 0, 0) == -1) {
8559         /* f is not seekable; probably a pipe */
8560         useList = FALSE;
8561     }
8562     if (useList && n == 0) {
8563         int error = GameListBuild(f);
8564         if (error) {
8565             DisplayError(_("Cannot build game list"), error);
8566         } else if (!ListEmpty(&gameList) &&
8567                    ((ListGame *) gameList.tailPred)->number > 1) {
8568             GameListPopUp(f, title);
8569             return TRUE;
8570         }
8571         GameListDestroy();
8572         n = 1;
8573     }
8574     if (n == 0) n = 1;
8575     return LoadGame(f, n, title, FALSE);
8576 }
8577
8578
8579 void
8580 MakeRegisteredMove()
8581 {
8582     int fromX, fromY, toX, toY;
8583     char promoChar;
8584     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8585         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8586           case CMAIL_MOVE:
8587           case CMAIL_DRAW:
8588             if (appData.debugMode)
8589               fprintf(debugFP, "Restoring %s for game %d\n",
8590                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8591     
8592             thinkOutput[0] = NULLCHAR;
8593             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8594             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8595             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8596             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8597             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8598             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8599             MakeMove(fromX, fromY, toX, toY, promoChar);
8600             ShowMove(fromX, fromY, toX, toY);
8601               
8602             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8603                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8604               case MT_NONE:
8605               case MT_CHECK:
8606                 break;
8607                 
8608               case MT_CHECKMATE:
8609               case MT_STAINMATE:
8610                 if (WhiteOnMove(currentMove)) {
8611                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8612                 } else {
8613                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8614                 }
8615                 break;
8616                 
8617               case MT_STALEMATE:
8618                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8619                 break;
8620             }
8621
8622             break;
8623             
8624           case CMAIL_RESIGN:
8625             if (WhiteOnMove(currentMove)) {
8626                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8627             } else {
8628                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8629             }
8630             break;
8631             
8632           case CMAIL_ACCEPT:
8633             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8634             break;
8635               
8636           default:
8637             break;
8638         }
8639     }
8640
8641     return;
8642 }
8643
8644 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8645 int
8646 CmailLoadGame(f, gameNumber, title, useList)
8647      FILE *f;
8648      int gameNumber;
8649      char *title;
8650      int useList;
8651 {
8652     int retVal;
8653
8654     if (gameNumber > nCmailGames) {
8655         DisplayError(_("No more games in this message"), 0);
8656         return FALSE;
8657     }
8658     if (f == lastLoadGameFP) {
8659         int offset = gameNumber - lastLoadGameNumber;
8660         if (offset == 0) {
8661             cmailMsg[0] = NULLCHAR;
8662             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8663                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8664                 nCmailMovesRegistered--;
8665             }
8666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8667             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8668                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8669             }
8670         } else {
8671             if (! RegisterMove()) return FALSE;
8672         }
8673     }
8674
8675     retVal = LoadGame(f, gameNumber, title, useList);
8676
8677     /* Make move registered during previous look at this game, if any */
8678     MakeRegisteredMove();
8679
8680     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8681         commentList[currentMove]
8682           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8683         DisplayComment(currentMove - 1, commentList[currentMove]);
8684     }
8685
8686     return retVal;
8687 }
8688
8689 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8690 int
8691 ReloadGame(offset)
8692      int offset;
8693 {
8694     int gameNumber = lastLoadGameNumber + offset;
8695     if (lastLoadGameFP == NULL) {
8696         DisplayError(_("No game has been loaded yet"), 0);
8697         return FALSE;
8698     }
8699     if (gameNumber <= 0) {
8700         DisplayError(_("Can't back up any further"), 0);
8701         return FALSE;
8702     }
8703     if (cmailMsgLoaded) {
8704         return CmailLoadGame(lastLoadGameFP, gameNumber,
8705                              lastLoadGameTitle, lastLoadGameUseList);
8706     } else {
8707         return LoadGame(lastLoadGameFP, gameNumber,
8708                         lastLoadGameTitle, lastLoadGameUseList);
8709     }
8710 }
8711
8712
8713
8714 /* Load the nth game from open file f */
8715 int
8716 LoadGame(f, gameNumber, title, useList)
8717      FILE *f;
8718      int gameNumber;
8719      char *title;
8720      int useList;
8721 {
8722     ChessMove cm;
8723     char buf[MSG_SIZ];
8724     int gn = gameNumber;
8725     ListGame *lg = NULL;
8726     int numPGNTags = 0;
8727     int err;
8728     GameMode oldGameMode;
8729     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8730
8731     if (appData.debugMode) 
8732         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8733
8734     if (gameMode == Training )
8735         SetTrainingModeOff();
8736
8737     oldGameMode = gameMode;
8738     if (gameMode != BeginningOfGame) {
8739       Reset(FALSE, TRUE);
8740     }
8741
8742     gameFileFP = f;
8743     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8744         fclose(lastLoadGameFP);
8745     }
8746
8747     if (useList) {
8748         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8749         
8750         if (lg) {
8751             fseek(f, lg->offset, 0);
8752             GameListHighlight(gameNumber);
8753             gn = 1;
8754         }
8755         else {
8756             DisplayError(_("Game number out of range"), 0);
8757             return FALSE;
8758         }
8759     } else {
8760         GameListDestroy();
8761         if (fseek(f, 0, 0) == -1) {
8762             if (f == lastLoadGameFP ?
8763                 gameNumber == lastLoadGameNumber + 1 :
8764                 gameNumber == 1) {
8765                 gn = 1;
8766             } else {
8767                 DisplayError(_("Can't seek on game file"), 0);
8768                 return FALSE;
8769             }
8770         }
8771     }
8772     lastLoadGameFP = f;
8773     lastLoadGameNumber = gameNumber;
8774     strcpy(lastLoadGameTitle, title);
8775     lastLoadGameUseList = useList;
8776
8777     yynewfile(f);
8778
8779     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8780       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8781                 lg->gameInfo.black);
8782             DisplayTitle(buf);
8783     } else if (*title != NULLCHAR) {
8784         if (gameNumber > 1) {
8785             sprintf(buf, "%s %d", title, gameNumber);
8786             DisplayTitle(buf);
8787         } else {
8788             DisplayTitle(title);
8789         }
8790     }
8791
8792     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8793         gameMode = PlayFromGameFile;
8794         ModeHighlight();
8795     }
8796
8797     currentMove = forwardMostMove = backwardMostMove = 0;
8798     CopyBoard(boards[0], initialPosition);
8799     StopClocks();
8800
8801     /*
8802      * Skip the first gn-1 games in the file.
8803      * Also skip over anything that precedes an identifiable 
8804      * start of game marker, to avoid being confused by 
8805      * garbage at the start of the file.  Currently 
8806      * recognized start of game markers are the move number "1",
8807      * the pattern "gnuchess .* game", the pattern
8808      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8809      * A game that starts with one of the latter two patterns
8810      * will also have a move number 1, possibly
8811      * following a position diagram.
8812      * 5-4-02: Let's try being more lenient and allowing a game to
8813      * start with an unnumbered move.  Does that break anything?
8814      */
8815     cm = lastLoadGameStart = (ChessMove) 0;
8816     while (gn > 0) {
8817         yyboardindex = forwardMostMove;
8818         cm = (ChessMove) yylex();
8819         switch (cm) {
8820           case (ChessMove) 0:
8821             if (cmailMsgLoaded) {
8822                 nCmailGames = CMAIL_MAX_GAMES - gn;
8823             } else {
8824                 Reset(TRUE, TRUE);
8825                 DisplayError(_("Game not found in file"), 0);
8826             }
8827             return FALSE;
8828
8829           case GNUChessGame:
8830           case XBoardGame:
8831             gn--;
8832             lastLoadGameStart = cm;
8833             break;
8834             
8835           case MoveNumberOne:
8836             switch (lastLoadGameStart) {
8837               case GNUChessGame:
8838               case XBoardGame:
8839               case PGNTag:
8840                 break;
8841               case MoveNumberOne:
8842               case (ChessMove) 0:
8843                 gn--;           /* count this game */
8844                 lastLoadGameStart = cm;
8845                 break;
8846               default:
8847                 /* impossible */
8848                 break;
8849             }
8850             break;
8851
8852           case PGNTag:
8853             switch (lastLoadGameStart) {
8854               case GNUChessGame:
8855               case PGNTag:
8856               case MoveNumberOne:
8857               case (ChessMove) 0:
8858                 gn--;           /* count this game */
8859                 lastLoadGameStart = cm;
8860                 break;
8861               case XBoardGame:
8862                 lastLoadGameStart = cm; /* game counted already */
8863                 break;
8864               default:
8865                 /* impossible */
8866                 break;
8867             }
8868             if (gn > 0) {
8869                 do {
8870                     yyboardindex = forwardMostMove;
8871                     cm = (ChessMove) yylex();
8872                 } while (cm == PGNTag || cm == Comment);
8873             }
8874             break;
8875
8876           case WhiteWins:
8877           case BlackWins:
8878           case GameIsDrawn:
8879             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8880                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8881                     != CMAIL_OLD_RESULT) {
8882                     nCmailResults ++ ;
8883                     cmailResult[  CMAIL_MAX_GAMES
8884                                 - gn - 1] = CMAIL_OLD_RESULT;
8885                 }
8886             }
8887             break;
8888
8889           case NormalMove:
8890             /* Only a NormalMove can be at the start of a game
8891              * without a position diagram. */
8892             if (lastLoadGameStart == (ChessMove) 0) {
8893               gn--;
8894               lastLoadGameStart = MoveNumberOne;
8895             }
8896             break;
8897
8898           default:
8899             break;
8900         }
8901     }
8902     
8903     if (appData.debugMode)
8904       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8905
8906     if (cm == XBoardGame) {
8907         /* Skip any header junk before position diagram and/or move 1 */
8908         for (;;) {
8909             yyboardindex = forwardMostMove;
8910             cm = (ChessMove) yylex();
8911
8912             if (cm == (ChessMove) 0 ||
8913                 cm == GNUChessGame || cm == XBoardGame) {
8914                 /* Empty game; pretend end-of-file and handle later */
8915                 cm = (ChessMove) 0;
8916                 break;
8917             }
8918
8919             if (cm == MoveNumberOne || cm == PositionDiagram ||
8920                 cm == PGNTag || cm == Comment)
8921               break;
8922         }
8923     } else if (cm == GNUChessGame) {
8924         if (gameInfo.event != NULL) {
8925             free(gameInfo.event);
8926         }
8927         gameInfo.event = StrSave(yy_text);
8928     }   
8929
8930     startedFromSetupPosition = FALSE;
8931     while (cm == PGNTag) {
8932         if (appData.debugMode) 
8933           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8934         err = ParsePGNTag(yy_text, &gameInfo);
8935         if (!err) numPGNTags++;
8936
8937         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8938         if(gameInfo.variant != oldVariant) {
8939             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8940             InitPosition(TRUE);
8941             oldVariant = gameInfo.variant;
8942             if (appData.debugMode) 
8943               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8944         }
8945
8946
8947         if (gameInfo.fen != NULL) {
8948           Board initial_position;
8949           startedFromSetupPosition = TRUE;
8950           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8951             Reset(TRUE, TRUE);
8952             DisplayError(_("Bad FEN position in file"), 0);
8953             return FALSE;
8954           }
8955           CopyBoard(boards[0], initial_position);
8956           if (blackPlaysFirst) {
8957             currentMove = forwardMostMove = backwardMostMove = 1;
8958             CopyBoard(boards[1], initial_position);
8959             strcpy(moveList[0], "");
8960             strcpy(parseList[0], "");
8961             timeRemaining[0][1] = whiteTimeRemaining;
8962             timeRemaining[1][1] = blackTimeRemaining;
8963             if (commentList[0] != NULL) {
8964               commentList[1] = commentList[0];
8965               commentList[0] = NULL;
8966             }
8967           } else {
8968             currentMove = forwardMostMove = backwardMostMove = 0;
8969           }
8970           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8971           {   int i;
8972               initialRulePlies = FENrulePlies;
8973               epStatus[forwardMostMove] = FENepStatus;
8974               for( i=0; i< nrCastlingRights; i++ )
8975                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8976           }
8977           yyboardindex = forwardMostMove;
8978           free(gameInfo.fen);
8979           gameInfo.fen = NULL;
8980         }
8981
8982         yyboardindex = forwardMostMove;
8983         cm = (ChessMove) yylex();
8984
8985         /* Handle comments interspersed among the tags */
8986         while (cm == Comment) {
8987             char *p;
8988             if (appData.debugMode) 
8989               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8990             p = yy_text;
8991             if (*p == '{' || *p == '[' || *p == '(') {
8992                 p[strlen(p) - 1] = NULLCHAR;
8993                 p++;
8994             }
8995             while (*p == '\n') p++;
8996             AppendComment(currentMove, p);
8997             yyboardindex = forwardMostMove;
8998             cm = (ChessMove) yylex();
8999         }
9000     }
9001
9002     /* don't rely on existence of Event tag since if game was
9003      * pasted from clipboard the Event tag may not exist
9004      */
9005     if (numPGNTags > 0){
9006         char *tags;
9007         if (gameInfo.variant == VariantNormal) {
9008           gameInfo.variant = StringToVariant(gameInfo.event);
9009         }
9010         if (!matchMode) {
9011           if( appData.autoDisplayTags ) {
9012             tags = PGNTags(&gameInfo);
9013             TagsPopUp(tags, CmailMsg());
9014             free(tags);
9015           }
9016         }
9017     } else {
9018         /* Make something up, but don't display it now */
9019         SetGameInfo();
9020         TagsPopDown();
9021     }
9022
9023     if (cm == PositionDiagram) {
9024         int i, j;
9025         char *p;
9026         Board initial_position;
9027
9028         if (appData.debugMode)
9029           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9030
9031         if (!startedFromSetupPosition) {
9032             p = yy_text;
9033             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9034               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9035                 switch (*p) {
9036                   case '[':
9037                   case '-':
9038                   case ' ':
9039                   case '\t':
9040                   case '\n':
9041                   case '\r':
9042                     break;
9043                   default:
9044                     initial_position[i][j++] = CharToPiece(*p);
9045                     break;
9046                 }
9047             while (*p == ' ' || *p == '\t' ||
9048                    *p == '\n' || *p == '\r') p++;
9049         
9050             if (strncmp(p, "black", strlen("black"))==0)
9051               blackPlaysFirst = TRUE;
9052             else
9053               blackPlaysFirst = FALSE;
9054             startedFromSetupPosition = TRUE;
9055         
9056             CopyBoard(boards[0], initial_position);
9057             if (blackPlaysFirst) {
9058                 currentMove = forwardMostMove = backwardMostMove = 1;
9059                 CopyBoard(boards[1], initial_position);
9060                 strcpy(moveList[0], "");
9061                 strcpy(parseList[0], "");
9062                 timeRemaining[0][1] = whiteTimeRemaining;
9063                 timeRemaining[1][1] = blackTimeRemaining;
9064                 if (commentList[0] != NULL) {
9065                     commentList[1] = commentList[0];
9066                     commentList[0] = NULL;
9067                 }
9068             } else {
9069                 currentMove = forwardMostMove = backwardMostMove = 0;
9070             }
9071         }
9072         yyboardindex = forwardMostMove;
9073         cm = (ChessMove) yylex();
9074     }
9075
9076     if (first.pr == NoProc) {
9077         StartChessProgram(&first);
9078     }
9079     InitChessProgram(&first, FALSE);
9080     SendToProgram("force\n", &first);
9081     if (startedFromSetupPosition) {
9082         SendBoard(&first, forwardMostMove);
9083     if (appData.debugMode) {
9084         fprintf(debugFP, "Load Game\n");
9085     }
9086         DisplayBothClocks();
9087     }      
9088
9089     /* [HGM] server: flag to write setup moves in broadcast file as one */
9090     loadFlag = appData.suppressLoadMoves;
9091
9092     while (cm == Comment) {
9093         char *p;
9094         if (appData.debugMode) 
9095           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9096         p = yy_text;
9097         if (*p == '{' || *p == '[' || *p == '(') {
9098             p[strlen(p) - 1] = NULLCHAR;
9099             p++;
9100         }
9101         while (*p == '\n') p++;
9102         AppendComment(currentMove, p);
9103         yyboardindex = forwardMostMove;
9104         cm = (ChessMove) yylex();
9105     }
9106
9107     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9108         cm == WhiteWins || cm == BlackWins ||
9109         cm == GameIsDrawn || cm == GameUnfinished) {
9110         DisplayMessage("", _("No moves in game"));
9111         if (cmailMsgLoaded) {
9112             if (appData.debugMode)
9113               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9114             ClearHighlights();
9115             flipView = FALSE;
9116         }
9117         DrawPosition(FALSE, boards[currentMove]);
9118         DisplayBothClocks();
9119         gameMode = EditGame;
9120         ModeHighlight();
9121         gameFileFP = NULL;
9122         cmailOldMove = 0;
9123         return TRUE;
9124     }
9125
9126     // [HGM] PV info: routine tests if comment empty
9127     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9128         DisplayComment(currentMove - 1, commentList[currentMove]);
9129     }
9130     if (!matchMode && appData.timeDelay != 0) 
9131       DrawPosition(FALSE, boards[currentMove]);
9132
9133     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9134       programStats.ok_to_send = 1;
9135     }
9136
9137     /* if the first token after the PGN tags is a move
9138      * and not move number 1, retrieve it from the parser 
9139      */
9140     if (cm != MoveNumberOne)
9141         LoadGameOneMove(cm);
9142
9143     /* load the remaining moves from the file */
9144     while (LoadGameOneMove((ChessMove)0)) {
9145       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9146       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9147     }
9148
9149     /* rewind to the start of the game */
9150     currentMove = backwardMostMove;
9151
9152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9153
9154     if (oldGameMode == AnalyzeFile ||
9155         oldGameMode == AnalyzeMode) {
9156       AnalyzeFileEvent();
9157     }
9158
9159     if (matchMode || appData.timeDelay == 0) {
9160       ToEndEvent();
9161       gameMode = EditGame;
9162       ModeHighlight();
9163     } else if (appData.timeDelay > 0) {
9164       AutoPlayGameLoop();
9165     }
9166
9167     if (appData.debugMode) 
9168         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9169
9170     loadFlag = 0; /* [HGM] true game starts */
9171     return TRUE;
9172 }
9173
9174 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9175 int
9176 ReloadPosition(offset)
9177      int offset;
9178 {
9179     int positionNumber = lastLoadPositionNumber + offset;
9180     if (lastLoadPositionFP == NULL) {
9181         DisplayError(_("No position has been loaded yet"), 0);
9182         return FALSE;
9183     }
9184     if (positionNumber <= 0) {
9185         DisplayError(_("Can't back up any further"), 0);
9186         return FALSE;
9187     }
9188     return LoadPosition(lastLoadPositionFP, positionNumber,
9189                         lastLoadPositionTitle);
9190 }
9191
9192 /* Load the nth position from the given file */
9193 int
9194 LoadPositionFromFile(filename, n, title)
9195      char *filename;
9196      int n;
9197      char *title;
9198 {
9199     FILE *f;
9200     char buf[MSG_SIZ];
9201
9202     if (strcmp(filename, "-") == 0) {
9203         return LoadPosition(stdin, n, "stdin");
9204     } else {
9205         f = fopen(filename, "rb");
9206         if (f == NULL) {
9207             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9208             DisplayError(buf, errno);
9209             return FALSE;
9210         } else {
9211             return LoadPosition(f, n, title);
9212         }
9213     }
9214 }
9215
9216 /* Load the nth position from the given open file, and close it */
9217 int
9218 LoadPosition(f, positionNumber, title)
9219      FILE *f;
9220      int positionNumber;
9221      char *title;
9222 {
9223     char *p, line[MSG_SIZ];
9224     Board initial_position;
9225     int i, j, fenMode, pn;
9226     
9227     if (gameMode == Training )
9228         SetTrainingModeOff();
9229
9230     if (gameMode != BeginningOfGame) {
9231         Reset(FALSE, TRUE);
9232     }
9233     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9234         fclose(lastLoadPositionFP);
9235     }
9236     if (positionNumber == 0) positionNumber = 1;
9237     lastLoadPositionFP = f;
9238     lastLoadPositionNumber = positionNumber;
9239     strcpy(lastLoadPositionTitle, title);
9240     if (first.pr == NoProc) {
9241       StartChessProgram(&first);
9242       InitChessProgram(&first, FALSE);
9243     }    
9244     pn = positionNumber;
9245     if (positionNumber < 0) {
9246         /* Negative position number means to seek to that byte offset */
9247         if (fseek(f, -positionNumber, 0) == -1) {
9248             DisplayError(_("Can't seek on position file"), 0);
9249             return FALSE;
9250         };
9251         pn = 1;
9252     } else {
9253         if (fseek(f, 0, 0) == -1) {
9254             if (f == lastLoadPositionFP ?
9255                 positionNumber == lastLoadPositionNumber + 1 :
9256                 positionNumber == 1) {
9257                 pn = 1;
9258             } else {
9259                 DisplayError(_("Can't seek on position file"), 0);
9260                 return FALSE;
9261             }
9262         }
9263     }
9264     /* See if this file is FEN or old-style xboard */
9265     if (fgets(line, MSG_SIZ, f) == NULL) {
9266         DisplayError(_("Position not found in file"), 0);
9267         return FALSE;
9268     }
9269 #if 0
9270     switch (line[0]) {
9271       case '#':  case 'x':
9272       default:
9273         fenMode = FALSE;
9274         break;
9275       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9276       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9277       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9278       case '7':  case '8':  case '9':
9279       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9280       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9281       case 'C':  case 'W':             case 'c':  case 'w': 
9282         fenMode = TRUE;
9283         break;
9284     }
9285 #else
9286     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9287     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9288 #endif
9289
9290     if (pn >= 2) {
9291         if (fenMode || line[0] == '#') pn--;
9292         while (pn > 0) {
9293             /* skip positions before number pn */
9294             if (fgets(line, MSG_SIZ, f) == NULL) {
9295                 Reset(TRUE, TRUE);
9296                 DisplayError(_("Position not found in file"), 0);
9297                 return FALSE;
9298             }
9299             if (fenMode || line[0] == '#') pn--;
9300         }
9301     }
9302
9303     if (fenMode) {
9304         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9305             DisplayError(_("Bad FEN position in file"), 0);
9306             return FALSE;
9307         }
9308     } else {
9309         (void) fgets(line, MSG_SIZ, f);
9310         (void) fgets(line, MSG_SIZ, f);
9311     
9312         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9313             (void) fgets(line, MSG_SIZ, f);
9314             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9315                 if (*p == ' ')
9316                   continue;
9317                 initial_position[i][j++] = CharToPiece(*p);
9318             }
9319         }
9320     
9321         blackPlaysFirst = FALSE;
9322         if (!feof(f)) {
9323             (void) fgets(line, MSG_SIZ, f);
9324             if (strncmp(line, "black", strlen("black"))==0)
9325               blackPlaysFirst = TRUE;
9326         }
9327     }
9328     startedFromSetupPosition = TRUE;
9329     
9330     SendToProgram("force\n", &first);
9331     CopyBoard(boards[0], initial_position);
9332     if (blackPlaysFirst) {
9333         currentMove = forwardMostMove = backwardMostMove = 1;
9334         strcpy(moveList[0], "");
9335         strcpy(parseList[0], "");
9336         CopyBoard(boards[1], initial_position);
9337         DisplayMessage("", _("Black to play"));
9338     } else {
9339         currentMove = forwardMostMove = backwardMostMove = 0;
9340         DisplayMessage("", _("White to play"));
9341     }
9342           /* [HGM] copy FEN attributes as well */
9343           {   int i;
9344               initialRulePlies = FENrulePlies;
9345               epStatus[forwardMostMove] = FENepStatus;
9346               for( i=0; i< nrCastlingRights; i++ )
9347                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9348           }
9349     SendBoard(&first, forwardMostMove);
9350     if (appData.debugMode) {
9351 int i, j;
9352   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9353   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9354         fprintf(debugFP, "Load Position\n");
9355     }
9356
9357     if (positionNumber > 1) {
9358         sprintf(line, "%s %d", title, positionNumber);
9359         DisplayTitle(line);
9360     } else {
9361         DisplayTitle(title);
9362     }
9363     gameMode = EditGame;
9364     ModeHighlight();
9365     ResetClocks();
9366     timeRemaining[0][1] = whiteTimeRemaining;
9367     timeRemaining[1][1] = blackTimeRemaining;
9368     DrawPosition(FALSE, boards[currentMove]);
9369    
9370     return TRUE;
9371 }
9372
9373
9374 void
9375 CopyPlayerNameIntoFileName(dest, src)
9376      char **dest, *src;
9377 {
9378     while (*src != NULLCHAR && *src != ',') {
9379         if (*src == ' ') {
9380             *(*dest)++ = '_';
9381             src++;
9382         } else {
9383             *(*dest)++ = *src++;
9384         }
9385     }
9386 }
9387
9388 char *DefaultFileName(ext)
9389      char *ext;
9390 {
9391     static char def[MSG_SIZ];
9392     char *p;
9393
9394     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9395         p = def;
9396         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9397         *p++ = '-';
9398         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9399         *p++ = '.';
9400         strcpy(p, ext);
9401     } else {
9402         def[0] = NULLCHAR;
9403     }
9404     return def;
9405 }
9406
9407 /* Save the current game to the given file */
9408 int
9409 SaveGameToFile(filename, append)
9410      char *filename;
9411      int append;
9412 {
9413     FILE *f;
9414     char buf[MSG_SIZ];
9415
9416     if (strcmp(filename, "-") == 0) {
9417         return SaveGame(stdout, 0, NULL);
9418     } else {
9419         f = fopen(filename, append ? "a" : "w");
9420         if (f == NULL) {
9421             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9422             DisplayError(buf, errno);
9423             return FALSE;
9424         } else {
9425             return SaveGame(f, 0, NULL);
9426         }
9427     }
9428 }
9429
9430 char *
9431 SavePart(str)
9432      char *str;
9433 {
9434     static char buf[MSG_SIZ];
9435     char *p;
9436     
9437     p = strchr(str, ' ');
9438     if (p == NULL) return str;
9439     strncpy(buf, str, p - str);
9440     buf[p - str] = NULLCHAR;
9441     return buf;
9442 }
9443
9444 #define PGN_MAX_LINE 75
9445
9446 #define PGN_SIDE_WHITE  0
9447 #define PGN_SIDE_BLACK  1
9448
9449 /* [AS] */
9450 static int FindFirstMoveOutOfBook( int side )
9451 {
9452     int result = -1;
9453
9454     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9455         int index = backwardMostMove;
9456         int has_book_hit = 0;
9457
9458         if( (index % 2) != side ) {
9459             index++;
9460         }
9461
9462         while( index < forwardMostMove ) {
9463             /* Check to see if engine is in book */
9464             int depth = pvInfoList[index].depth;
9465             int score = pvInfoList[index].score;
9466             int in_book = 0;
9467
9468             if( depth <= 2 ) {
9469                 in_book = 1;
9470             }
9471             else if( score == 0 && depth == 63 ) {
9472                 in_book = 1; /* Zappa */
9473             }
9474             else if( score == 2 && depth == 99 ) {
9475                 in_book = 1; /* Abrok */
9476             }
9477
9478             has_book_hit += in_book;
9479
9480             if( ! in_book ) {
9481                 result = index;
9482
9483                 break;
9484             }
9485
9486             index += 2;
9487         }
9488     }
9489
9490     return result;
9491 }
9492
9493 /* [AS] */
9494 void GetOutOfBookInfo( char * buf )
9495 {
9496     int oob[2];
9497     int i;
9498     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9499
9500     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9501     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9502
9503     *buf = '\0';
9504
9505     if( oob[0] >= 0 || oob[1] >= 0 ) {
9506         for( i=0; i<2; i++ ) {
9507             int idx = oob[i];
9508
9509             if( idx >= 0 ) {
9510                 if( i > 0 && oob[0] >= 0 ) {
9511                     strcat( buf, "   " );
9512                 }
9513
9514                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9515                 sprintf( buf+strlen(buf), "%s%.2f", 
9516                     pvInfoList[idx].score >= 0 ? "+" : "",
9517                     pvInfoList[idx].score / 100.0 );
9518             }
9519         }
9520     }
9521 }
9522
9523 /* Save game in PGN style and close the file */
9524 int
9525 SaveGamePGN(f)
9526      FILE *f;
9527 {
9528     int i, offset, linelen, newblock;
9529     time_t tm;
9530 //    char *movetext;
9531     char numtext[32];
9532     int movelen, numlen, blank;
9533     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9534
9535     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9536     
9537     tm = time((time_t *) NULL);
9538     
9539     PrintPGNTags(f, &gameInfo);
9540     
9541     if (backwardMostMove > 0 || startedFromSetupPosition) {
9542         char *fen = PositionToFEN(backwardMostMove, NULL);
9543         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9544         fprintf(f, "\n{--------------\n");
9545         PrintPosition(f, backwardMostMove);
9546         fprintf(f, "--------------}\n");
9547         free(fen);
9548     }
9549     else {
9550         /* [AS] Out of book annotation */
9551         if( appData.saveOutOfBookInfo ) {
9552             char buf[64];
9553
9554             GetOutOfBookInfo( buf );
9555
9556             if( buf[0] != '\0' ) {
9557                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9558             }
9559         }
9560
9561         fprintf(f, "\n");
9562     }
9563
9564     i = backwardMostMove;
9565     linelen = 0;
9566     newblock = TRUE;
9567
9568     while (i < forwardMostMove) {
9569         /* Print comments preceding this move */
9570         if (commentList[i] != NULL) {
9571             if (linelen > 0) fprintf(f, "\n");
9572             fprintf(f, "{\n%s}\n", commentList[i]);
9573             linelen = 0;
9574             newblock = TRUE;
9575         }
9576
9577         /* Format move number */
9578         if ((i % 2) == 0) {
9579             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9580         } else {
9581             if (newblock) {
9582                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9583             } else {
9584                 numtext[0] = NULLCHAR;
9585             }
9586         }
9587         numlen = strlen(numtext);
9588         newblock = FALSE;
9589
9590         /* Print move number */
9591         blank = linelen > 0 && numlen > 0;
9592         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9593             fprintf(f, "\n");
9594             linelen = 0;
9595             blank = 0;
9596         }
9597         if (blank) {
9598             fprintf(f, " ");
9599             linelen++;
9600         }
9601         fprintf(f, numtext);
9602         linelen += numlen;
9603
9604         /* Get move */
9605         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9606         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9607         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9608                 int p = movelen - 1;
9609                 if(move_buffer[p] == ' ') p--;
9610                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9611                     while(p && move_buffer[--p] != '(');
9612                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9613                 }
9614         }
9615
9616         /* Print move */
9617         blank = linelen > 0 && movelen > 0;
9618         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9619             fprintf(f, "\n");
9620             linelen = 0;
9621             blank = 0;
9622         }
9623         if (blank) {
9624             fprintf(f, " ");
9625             linelen++;
9626         }
9627         fprintf(f, move_buffer);
9628         linelen += movelen;
9629
9630         /* [AS] Add PV info if present */
9631         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9632             /* [HGM] add time */
9633             char buf[MSG_SIZ]; int seconds = 0;
9634
9635 #if 1
9636             if(i >= backwardMostMove) {
9637                 if(WhiteOnMove(i))
9638                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9639                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9640                 else
9641                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9642                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9643             }
9644             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9645 #else
9646             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9647 #endif
9648
9649             if( seconds <= 0) buf[0] = 0; else
9650             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9651                 seconds = (seconds + 4)/10; // round to full seconds
9652                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9653                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9654             }
9655
9656             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9657                 pvInfoList[i].score >= 0 ? "+" : "",
9658                 pvInfoList[i].score / 100.0,
9659                 pvInfoList[i].depth,
9660                 buf );
9661
9662             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9663
9664             /* Print score/depth */
9665             blank = linelen > 0 && movelen > 0;
9666             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9667                 fprintf(f, "\n");
9668                 linelen = 0;
9669                 blank = 0;
9670             }
9671             if (blank) {
9672                 fprintf(f, " ");
9673                 linelen++;
9674             }
9675             fprintf(f, move_buffer);
9676             linelen += movelen;
9677         }
9678
9679         i++;
9680     }
9681     
9682     /* Start a new line */
9683     if (linelen > 0) fprintf(f, "\n");
9684
9685     /* Print comments after last move */
9686     if (commentList[i] != NULL) {
9687         fprintf(f, "{\n%s}\n", commentList[i]);
9688     }
9689
9690     /* Print result */
9691     if (gameInfo.resultDetails != NULL &&
9692         gameInfo.resultDetails[0] != NULLCHAR) {
9693         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9694                 PGNResult(gameInfo.result));
9695     } else {
9696         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9697     }
9698
9699     fclose(f);
9700     return TRUE;
9701 }
9702
9703 /* Save game in old style and close the file */
9704 int
9705 SaveGameOldStyle(f)
9706      FILE *f;
9707 {
9708     int i, offset;
9709     time_t tm;
9710     
9711     tm = time((time_t *) NULL);
9712     
9713     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9714     PrintOpponents(f);
9715     
9716     if (backwardMostMove > 0 || startedFromSetupPosition) {
9717         fprintf(f, "\n[--------------\n");
9718         PrintPosition(f, backwardMostMove);
9719         fprintf(f, "--------------]\n");
9720     } else {
9721         fprintf(f, "\n");
9722     }
9723
9724     i = backwardMostMove;
9725     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9726
9727     while (i < forwardMostMove) {
9728         if (commentList[i] != NULL) {
9729             fprintf(f, "[%s]\n", commentList[i]);
9730         }
9731
9732         if ((i % 2) == 1) {
9733             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9734             i++;
9735         } else {
9736             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9737             i++;
9738             if (commentList[i] != NULL) {
9739                 fprintf(f, "\n");
9740                 continue;
9741             }
9742             if (i >= forwardMostMove) {
9743                 fprintf(f, "\n");
9744                 break;
9745             }
9746             fprintf(f, "%s\n", parseList[i]);
9747             i++;
9748         }
9749     }
9750     
9751     if (commentList[i] != NULL) {
9752         fprintf(f, "[%s]\n", commentList[i]);
9753     }
9754
9755     /* This isn't really the old style, but it's close enough */
9756     if (gameInfo.resultDetails != NULL &&
9757         gameInfo.resultDetails[0] != NULLCHAR) {
9758         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9759                 gameInfo.resultDetails);
9760     } else {
9761         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9762     }
9763
9764     fclose(f);
9765     return TRUE;
9766 }
9767
9768 /* Save the current game to open file f and close the file */
9769 int
9770 SaveGame(f, dummy, dummy2)
9771      FILE *f;
9772      int dummy;
9773      char *dummy2;
9774 {
9775     if (gameMode == EditPosition) EditPositionDone();
9776     if (appData.oldSaveStyle)
9777       return SaveGameOldStyle(f);
9778     else
9779       return SaveGamePGN(f);
9780 }
9781
9782 /* Save the current position to the given file */
9783 int
9784 SavePositionToFile(filename)
9785      char *filename;
9786 {
9787     FILE *f;
9788     char buf[MSG_SIZ];
9789
9790     if (strcmp(filename, "-") == 0) {
9791         return SavePosition(stdout, 0, NULL);
9792     } else {
9793         f = fopen(filename, "a");
9794         if (f == NULL) {
9795             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9796             DisplayError(buf, errno);
9797             return FALSE;
9798         } else {
9799             SavePosition(f, 0, NULL);
9800             return TRUE;
9801         }
9802     }
9803 }
9804
9805 /* Save the current position to the given open file and close the file */
9806 int
9807 SavePosition(f, dummy, dummy2)
9808      FILE *f;
9809      int dummy;
9810      char *dummy2;
9811 {
9812     time_t tm;
9813     char *fen;
9814     
9815     if (appData.oldSaveStyle) {
9816         tm = time((time_t *) NULL);
9817     
9818         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9819         PrintOpponents(f);
9820         fprintf(f, "[--------------\n");
9821         PrintPosition(f, currentMove);
9822         fprintf(f, "--------------]\n");
9823     } else {
9824         fen = PositionToFEN(currentMove, NULL);
9825         fprintf(f, "%s\n", fen);
9826         free(fen);
9827     }
9828     fclose(f);
9829     return TRUE;
9830 }
9831
9832 void
9833 ReloadCmailMsgEvent(unregister)
9834      int unregister;
9835 {
9836 #if !WIN32
9837     static char *inFilename = NULL;
9838     static char *outFilename;
9839     int i;
9840     struct stat inbuf, outbuf;
9841     int status;
9842     
9843     /* Any registered moves are unregistered if unregister is set, */
9844     /* i.e. invoked by the signal handler */
9845     if (unregister) {
9846         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9847             cmailMoveRegistered[i] = FALSE;
9848             if (cmailCommentList[i] != NULL) {
9849                 free(cmailCommentList[i]);
9850                 cmailCommentList[i] = NULL;
9851             }
9852         }
9853         nCmailMovesRegistered = 0;
9854     }
9855
9856     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9857         cmailResult[i] = CMAIL_NOT_RESULT;
9858     }
9859     nCmailResults = 0;
9860
9861     if (inFilename == NULL) {
9862         /* Because the filenames are static they only get malloced once  */
9863         /* and they never get freed                                      */
9864         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9865         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9866
9867         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9868         sprintf(outFilename, "%s.out", appData.cmailGameName);
9869     }
9870     
9871     status = stat(outFilename, &outbuf);
9872     if (status < 0) {
9873         cmailMailedMove = FALSE;
9874     } else {
9875         status = stat(inFilename, &inbuf);
9876         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9877     }
9878     
9879     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9880        counts the games, notes how each one terminated, etc.
9881        
9882        It would be nice to remove this kludge and instead gather all
9883        the information while building the game list.  (And to keep it
9884        in the game list nodes instead of having a bunch of fixed-size
9885        parallel arrays.)  Note this will require getting each game's
9886        termination from the PGN tags, as the game list builder does
9887        not process the game moves.  --mann
9888        */
9889     cmailMsgLoaded = TRUE;
9890     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9891     
9892     /* Load first game in the file or popup game menu */
9893     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9894
9895 #endif /* !WIN32 */
9896     return;
9897 }
9898
9899 int
9900 RegisterMove()
9901 {
9902     FILE *f;
9903     char string[MSG_SIZ];
9904
9905     if (   cmailMailedMove
9906         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9907         return TRUE;            /* Allow free viewing  */
9908     }
9909
9910     /* Unregister move to ensure that we don't leave RegisterMove        */
9911     /* with the move registered when the conditions for registering no   */
9912     /* longer hold                                                       */
9913     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9914         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9915         nCmailMovesRegistered --;
9916
9917         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9918           {
9919               free(cmailCommentList[lastLoadGameNumber - 1]);
9920               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9921           }
9922     }
9923
9924     if (cmailOldMove == -1) {
9925         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9926         return FALSE;
9927     }
9928
9929     if (currentMove > cmailOldMove + 1) {
9930         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9931         return FALSE;
9932     }
9933
9934     if (currentMove < cmailOldMove) {
9935         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9936         return FALSE;
9937     }
9938
9939     if (forwardMostMove > currentMove) {
9940         /* Silently truncate extra moves */
9941         TruncateGame();
9942     }
9943
9944     if (   (currentMove == cmailOldMove + 1)
9945         || (   (currentMove == cmailOldMove)
9946             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9947                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9948         if (gameInfo.result != GameUnfinished) {
9949             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9950         }
9951
9952         if (commentList[currentMove] != NULL) {
9953             cmailCommentList[lastLoadGameNumber - 1]
9954               = StrSave(commentList[currentMove]);
9955         }
9956         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9957
9958         if (appData.debugMode)
9959           fprintf(debugFP, "Saving %s for game %d\n",
9960                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9961
9962         sprintf(string,
9963                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9964         
9965         f = fopen(string, "w");
9966         if (appData.oldSaveStyle) {
9967             SaveGameOldStyle(f); /* also closes the file */
9968             
9969             sprintf(string, "%s.pos.out", appData.cmailGameName);
9970             f = fopen(string, "w");
9971             SavePosition(f, 0, NULL); /* also closes the file */
9972         } else {
9973             fprintf(f, "{--------------\n");
9974             PrintPosition(f, currentMove);
9975             fprintf(f, "--------------}\n\n");
9976             
9977             SaveGame(f, 0, NULL); /* also closes the file*/
9978         }
9979         
9980         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9981         nCmailMovesRegistered ++;
9982     } else if (nCmailGames == 1) {
9983         DisplayError(_("You have not made a move yet"), 0);
9984         return FALSE;
9985     }
9986
9987     return TRUE;
9988 }
9989
9990 void
9991 MailMoveEvent()
9992 {
9993 #if !WIN32
9994     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9995     FILE *commandOutput;
9996     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9997     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9998     int nBuffers;
9999     int i;
10000     int archived;
10001     char *arcDir;
10002
10003     if (! cmailMsgLoaded) {
10004         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10005         return;
10006     }
10007
10008     if (nCmailGames == nCmailResults) {
10009         DisplayError(_("No unfinished games"), 0);
10010         return;
10011     }
10012
10013 #if CMAIL_PROHIBIT_REMAIL
10014     if (cmailMailedMove) {
10015         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);
10016         DisplayError(msg, 0);
10017         return;
10018     }
10019 #endif
10020
10021     if (! (cmailMailedMove || RegisterMove())) return;
10022     
10023     if (   cmailMailedMove
10024         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10025         sprintf(string, partCommandString,
10026                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10027         commandOutput = popen(string, "r");
10028
10029         if (commandOutput == NULL) {
10030             DisplayError(_("Failed to invoke cmail"), 0);
10031         } else {
10032             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10033                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10034             }
10035             if (nBuffers > 1) {
10036                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10037                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10038                 nBytes = MSG_SIZ - 1;
10039             } else {
10040                 (void) memcpy(msg, buffer, nBytes);
10041             }
10042             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10043
10044             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10045                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10046
10047                 archived = TRUE;
10048                 for (i = 0; i < nCmailGames; i ++) {
10049                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10050                         archived = FALSE;
10051                     }
10052                 }
10053                 if (   archived
10054                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10055                         != NULL)) {
10056                     sprintf(buffer, "%s/%s.%s.archive",
10057                             arcDir,
10058                             appData.cmailGameName,
10059                             gameInfo.date);
10060                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10061                     cmailMsgLoaded = FALSE;
10062                 }
10063             }
10064
10065             DisplayInformation(msg);
10066             pclose(commandOutput);
10067         }
10068     } else {
10069         if ((*cmailMsg) != '\0') {
10070             DisplayInformation(cmailMsg);
10071         }
10072     }
10073
10074     return;
10075 #endif /* !WIN32 */
10076 }
10077
10078 char *
10079 CmailMsg()
10080 {
10081 #if WIN32
10082     return NULL;
10083 #else
10084     int  prependComma = 0;
10085     char number[5];
10086     char string[MSG_SIZ];       /* Space for game-list */
10087     int  i;
10088     
10089     if (!cmailMsgLoaded) return "";
10090
10091     if (cmailMailedMove) {
10092         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10093     } else {
10094         /* Create a list of games left */
10095         sprintf(string, "[");
10096         for (i = 0; i < nCmailGames; i ++) {
10097             if (! (   cmailMoveRegistered[i]
10098                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10099                 if (prependComma) {
10100                     sprintf(number, ",%d", i + 1);
10101                 } else {
10102                     sprintf(number, "%d", i + 1);
10103                     prependComma = 1;
10104                 }
10105                 
10106                 strcat(string, number);
10107             }
10108         }
10109         strcat(string, "]");
10110
10111         if (nCmailMovesRegistered + nCmailResults == 0) {
10112             switch (nCmailGames) {
10113               case 1:
10114                 sprintf(cmailMsg,
10115                         _("Still need to make move for game\n"));
10116                 break;
10117                 
10118               case 2:
10119                 sprintf(cmailMsg,
10120                         _("Still need to make moves for both games\n"));
10121                 break;
10122                 
10123               default:
10124                 sprintf(cmailMsg,
10125                         _("Still need to make moves for all %d games\n"),
10126                         nCmailGames);
10127                 break;
10128             }
10129         } else {
10130             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10131               case 1:
10132                 sprintf(cmailMsg,
10133                         _("Still need to make a move for game %s\n"),
10134                         string);
10135                 break;
10136                 
10137               case 0:
10138                 if (nCmailResults == nCmailGames) {
10139                     sprintf(cmailMsg, _("No unfinished games\n"));
10140                 } else {
10141                     sprintf(cmailMsg, _("Ready to send mail\n"));
10142                 }
10143                 break;
10144                 
10145               default:
10146                 sprintf(cmailMsg,
10147                         _("Still need to make moves for games %s\n"),
10148                         string);
10149             }
10150         }
10151     }
10152     return cmailMsg;
10153 #endif /* WIN32 */
10154 }
10155
10156 void
10157 ResetGameEvent()
10158 {
10159     if (gameMode == Training)
10160       SetTrainingModeOff();
10161
10162     Reset(TRUE, TRUE);
10163     cmailMsgLoaded = FALSE;
10164     if (appData.icsActive) {
10165       SendToICS(ics_prefix);
10166       SendToICS("refresh\n");
10167     }
10168 }
10169
10170 void
10171 ExitEvent(status)
10172      int status;
10173 {
10174     exiting++;
10175     if (exiting > 2) {
10176       /* Give up on clean exit */
10177       exit(status);
10178     }
10179     if (exiting > 1) {
10180       /* Keep trying for clean exit */
10181       return;
10182     }
10183
10184     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10185
10186     if (telnetISR != NULL) {
10187       RemoveInputSource(telnetISR);
10188     }
10189     if (icsPR != NoProc) {
10190       DestroyChildProcess(icsPR, TRUE);
10191     }
10192 #if 0
10193     /* Save game if resource set and not already saved by GameEnds() */
10194     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10195                              && forwardMostMove > 0) {
10196       if (*appData.saveGameFile != NULLCHAR) {
10197         SaveGameToFile(appData.saveGameFile, TRUE);
10198       } else if (appData.autoSaveGames) {
10199         AutoSaveGame();
10200       }
10201       if (*appData.savePositionFile != NULLCHAR) {
10202         SavePositionToFile(appData.savePositionFile);
10203       }
10204     }
10205     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10206 #else
10207     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10208     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10209 #endif
10210     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10211     /* make sure this other one finishes before killing it!                  */
10212     if(endingGame) { int count = 0;
10213         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10214         while(endingGame && count++ < 10) DoSleep(1);
10215         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10216     }
10217
10218     /* Kill off chess programs */
10219     if (first.pr != NoProc) {
10220         ExitAnalyzeMode();
10221         
10222         DoSleep( appData.delayBeforeQuit );
10223         SendToProgram("quit\n", &first);
10224         DoSleep( appData.delayAfterQuit );
10225         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10226     }
10227     if (second.pr != NoProc) {
10228         DoSleep( appData.delayBeforeQuit );
10229         SendToProgram("quit\n", &second);
10230         DoSleep( appData.delayAfterQuit );
10231         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10232     }
10233     if (first.isr != NULL) {
10234         RemoveInputSource(first.isr);
10235     }
10236     if (second.isr != NULL) {
10237         RemoveInputSource(second.isr);
10238     }
10239
10240     ShutDownFrontEnd();
10241     exit(status);
10242 }
10243
10244 void
10245 PauseEvent()
10246 {
10247     if (appData.debugMode)
10248         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10249     if (pausing) {
10250         pausing = FALSE;
10251         ModeHighlight();
10252         if (gameMode == MachinePlaysWhite ||
10253             gameMode == MachinePlaysBlack) {
10254             StartClocks();
10255         } else {
10256             DisplayBothClocks();
10257         }
10258         if (gameMode == PlayFromGameFile) {
10259             if (appData.timeDelay >= 0) 
10260                 AutoPlayGameLoop();
10261         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10262             Reset(FALSE, TRUE);
10263             SendToICS(ics_prefix);
10264             SendToICS("refresh\n");
10265         } else if (currentMove < forwardMostMove) {
10266             ForwardInner(forwardMostMove);
10267         }
10268         pauseExamInvalid = FALSE;
10269     } else {
10270         switch (gameMode) {
10271           default:
10272             return;
10273           case IcsExamining:
10274             pauseExamForwardMostMove = forwardMostMove;
10275             pauseExamInvalid = FALSE;
10276             /* fall through */
10277           case IcsObserving:
10278           case IcsPlayingWhite:
10279           case IcsPlayingBlack:
10280             pausing = TRUE;
10281             ModeHighlight();
10282             return;
10283           case PlayFromGameFile:
10284             (void) StopLoadGameTimer();
10285             pausing = TRUE;
10286             ModeHighlight();
10287             break;
10288           case BeginningOfGame:
10289             if (appData.icsActive) return;
10290             /* else fall through */
10291           case MachinePlaysWhite:
10292           case MachinePlaysBlack:
10293           case TwoMachinesPlay:
10294             if (forwardMostMove == 0)
10295               return;           /* don't pause if no one has moved */
10296             if ((gameMode == MachinePlaysWhite &&
10297                  !WhiteOnMove(forwardMostMove)) ||
10298                 (gameMode == MachinePlaysBlack &&
10299                  WhiteOnMove(forwardMostMove))) {
10300                 StopClocks();
10301             }
10302             pausing = TRUE;
10303             ModeHighlight();
10304             break;
10305         }
10306     }
10307 }
10308
10309 void
10310 EditCommentEvent()
10311 {
10312     char title[MSG_SIZ];
10313
10314     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10315         strcpy(title, _("Edit comment"));
10316     } else {
10317         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10318                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10319                 parseList[currentMove - 1]);
10320     }
10321
10322     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10323 }
10324
10325
10326 void
10327 EditTagsEvent()
10328 {
10329     char *tags = PGNTags(&gameInfo);
10330     EditTagsPopUp(tags);
10331     free(tags);
10332 }
10333
10334 void
10335 AnalyzeModeEvent()
10336 {
10337     if (appData.noChessProgram || gameMode == AnalyzeMode)
10338       return;
10339
10340     if (gameMode != AnalyzeFile) {
10341         if (!appData.icsEngineAnalyze) {
10342                EditGameEvent();
10343                if (gameMode != EditGame) return;
10344         }
10345         ResurrectChessProgram();
10346         SendToProgram("analyze\n", &first);
10347         first.analyzing = TRUE;
10348         /*first.maybeThinking = TRUE;*/
10349         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10350         AnalysisPopUp(_("Analysis"),
10351                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10352     }
10353     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10354     pausing = FALSE;
10355     ModeHighlight();
10356     SetGameInfo();
10357
10358     StartAnalysisClock();
10359     GetTimeMark(&lastNodeCountTime);
10360     lastNodeCount = 0;
10361 }
10362
10363 void
10364 AnalyzeFileEvent()
10365 {
10366     if (appData.noChessProgram || gameMode == AnalyzeFile)
10367       return;
10368
10369     if (gameMode != AnalyzeMode) {
10370         EditGameEvent();
10371         if (gameMode != EditGame) return;
10372         ResurrectChessProgram();
10373         SendToProgram("analyze\n", &first);
10374         first.analyzing = TRUE;
10375         /*first.maybeThinking = TRUE;*/
10376         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10377         AnalysisPopUp(_("Analysis"),
10378                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10379     }
10380     gameMode = AnalyzeFile;
10381     pausing = FALSE;
10382     ModeHighlight();
10383     SetGameInfo();
10384
10385     StartAnalysisClock();
10386     GetTimeMark(&lastNodeCountTime);
10387     lastNodeCount = 0;
10388 }
10389
10390 void
10391 MachineWhiteEvent()
10392 {
10393     char buf[MSG_SIZ];
10394     char *bookHit = NULL;
10395
10396     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10397       return;
10398
10399
10400     if (gameMode == PlayFromGameFile || 
10401         gameMode == TwoMachinesPlay  || 
10402         gameMode == Training         || 
10403         gameMode == AnalyzeMode      || 
10404         gameMode == EndOfGame)
10405         EditGameEvent();
10406
10407     if (gameMode == EditPosition) 
10408         EditPositionDone();
10409
10410     if (!WhiteOnMove(currentMove)) {
10411         DisplayError(_("It is not White's turn"), 0);
10412         return;
10413     }
10414   
10415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10416       ExitAnalyzeMode();
10417
10418     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10419         gameMode == AnalyzeFile)
10420         TruncateGame();
10421
10422     ResurrectChessProgram();    /* in case it isn't running */
10423     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10424         gameMode = MachinePlaysWhite;
10425         ResetClocks();
10426     } else
10427     gameMode = MachinePlaysWhite;
10428     pausing = FALSE;
10429     ModeHighlight();
10430     SetGameInfo();
10431     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10432     DisplayTitle(buf);
10433     if (first.sendName) {
10434       sprintf(buf, "name %s\n", gameInfo.black);
10435       SendToProgram(buf, &first);
10436     }
10437     if (first.sendTime) {
10438       if (first.useColors) {
10439         SendToProgram("black\n", &first); /*gnu kludge*/
10440       }
10441       SendTimeRemaining(&first, TRUE);
10442     }
10443     if (first.useColors) {
10444       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10445     }
10446     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10447     SetMachineThinkingEnables();
10448     first.maybeThinking = TRUE;
10449     StartClocks();
10450
10451     if (appData.autoFlipView && !flipView) {
10452       flipView = !flipView;
10453       DrawPosition(FALSE, NULL);
10454       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10455     }
10456
10457     if(bookHit) { // [HGM] book: simulate book reply
10458         static char bookMove[MSG_SIZ]; // a bit generous?
10459
10460         programStats.nodes = programStats.depth = programStats.time = 
10461         programStats.score = programStats.got_only_move = 0;
10462         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10463
10464         strcpy(bookMove, "move ");
10465         strcat(bookMove, bookHit);
10466         HandleMachineMove(bookMove, &first);
10467     }
10468 }
10469
10470 void
10471 MachineBlackEvent()
10472 {
10473     char buf[MSG_SIZ];
10474    char *bookHit = NULL;
10475
10476     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10477         return;
10478
10479
10480     if (gameMode == PlayFromGameFile || 
10481         gameMode == TwoMachinesPlay  || 
10482         gameMode == Training         || 
10483         gameMode == AnalyzeMode      || 
10484         gameMode == EndOfGame)
10485         EditGameEvent();
10486
10487     if (gameMode == EditPosition) 
10488         EditPositionDone();
10489
10490     if (WhiteOnMove(currentMove)) {
10491         DisplayError(_("It is not Black's turn"), 0);
10492         return;
10493     }
10494     
10495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10496       ExitAnalyzeMode();
10497
10498     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10499         gameMode == AnalyzeFile)
10500         TruncateGame();
10501
10502     ResurrectChessProgram();    /* in case it isn't running */
10503     gameMode = MachinePlaysBlack;
10504     pausing = FALSE;
10505     ModeHighlight();
10506     SetGameInfo();
10507     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10508     DisplayTitle(buf);
10509     if (first.sendName) {
10510       sprintf(buf, "name %s\n", gameInfo.white);
10511       SendToProgram(buf, &first);
10512     }
10513     if (first.sendTime) {
10514       if (first.useColors) {
10515         SendToProgram("white\n", &first); /*gnu kludge*/
10516       }
10517       SendTimeRemaining(&first, FALSE);
10518     }
10519     if (first.useColors) {
10520       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10521     }
10522     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10523     SetMachineThinkingEnables();
10524     first.maybeThinking = TRUE;
10525     StartClocks();
10526
10527     if (appData.autoFlipView && flipView) {
10528       flipView = !flipView;
10529       DrawPosition(FALSE, NULL);
10530       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10531     }
10532     if(bookHit) { // [HGM] book: simulate book reply
10533         static char bookMove[MSG_SIZ]; // a bit generous?
10534
10535         programStats.nodes = programStats.depth = programStats.time = 
10536         programStats.score = programStats.got_only_move = 0;
10537         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10538
10539         strcpy(bookMove, "move ");
10540         strcat(bookMove, bookHit);
10541         HandleMachineMove(bookMove, &first);
10542     }
10543 }
10544
10545
10546 void
10547 DisplayTwoMachinesTitle()
10548 {
10549     char buf[MSG_SIZ];
10550     if (appData.matchGames > 0) {
10551         if (first.twoMachinesColor[0] == 'w') {
10552             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10553                     gameInfo.white, gameInfo.black,
10554                     first.matchWins, second.matchWins,
10555                     matchGame - 1 - (first.matchWins + second.matchWins));
10556         } else {
10557             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10558                     gameInfo.white, gameInfo.black,
10559                     second.matchWins, first.matchWins,
10560                     matchGame - 1 - (first.matchWins + second.matchWins));
10561         }
10562     } else {
10563         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10564     }
10565     DisplayTitle(buf);
10566 }
10567
10568 void
10569 TwoMachinesEvent P((void))
10570 {
10571     int i;
10572     char buf[MSG_SIZ];
10573     ChessProgramState *onmove;
10574     char *bookHit = NULL;
10575     
10576     if (appData.noChessProgram) return;
10577
10578     switch (gameMode) {
10579       case TwoMachinesPlay:
10580         return;
10581       case MachinePlaysWhite:
10582       case MachinePlaysBlack:
10583         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10584             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10585             return;
10586         }
10587         /* fall through */
10588       case BeginningOfGame:
10589       case PlayFromGameFile:
10590       case EndOfGame:
10591         EditGameEvent();
10592         if (gameMode != EditGame) return;
10593         break;
10594       case EditPosition:
10595         EditPositionDone();
10596         break;
10597       case AnalyzeMode:
10598       case AnalyzeFile:
10599         ExitAnalyzeMode();
10600         break;
10601       case EditGame:
10602       default:
10603         break;
10604     }
10605
10606     forwardMostMove = currentMove;
10607     ResurrectChessProgram();    /* in case first program isn't running */
10608
10609     if (second.pr == NULL) {
10610         StartChessProgram(&second);
10611         if (second.protocolVersion == 1) {
10612           TwoMachinesEventIfReady();
10613         } else {
10614           /* kludge: allow timeout for initial "feature" command */
10615           FreezeUI();
10616           DisplayMessage("", _("Starting second chess program"));
10617           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10618         }
10619         return;
10620     }
10621     DisplayMessage("", "");
10622     InitChessProgram(&second, FALSE);
10623     SendToProgram("force\n", &second);
10624     if (startedFromSetupPosition) {
10625         SendBoard(&second, backwardMostMove);
10626     if (appData.debugMode) {
10627         fprintf(debugFP, "Two Machines\n");
10628     }
10629     }
10630     for (i = backwardMostMove; i < forwardMostMove; i++) {
10631         SendMoveToProgram(i, &second);
10632     }
10633
10634     gameMode = TwoMachinesPlay;
10635     pausing = FALSE;
10636     ModeHighlight();
10637     SetGameInfo();
10638     DisplayTwoMachinesTitle();
10639     firstMove = TRUE;
10640     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10641         onmove = &first;
10642     } else {
10643         onmove = &second;
10644     }
10645
10646     SendToProgram(first.computerString, &first);
10647     if (first.sendName) {
10648       sprintf(buf, "name %s\n", second.tidy);
10649       SendToProgram(buf, &first);
10650     }
10651     SendToProgram(second.computerString, &second);
10652     if (second.sendName) {
10653       sprintf(buf, "name %s\n", first.tidy);
10654       SendToProgram(buf, &second);
10655     }
10656
10657     ResetClocks();
10658     if (!first.sendTime || !second.sendTime) {
10659         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10660         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10661     }
10662     if (onmove->sendTime) {
10663       if (onmove->useColors) {
10664         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10665       }
10666       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10667     }
10668     if (onmove->useColors) {
10669       SendToProgram(onmove->twoMachinesColor, onmove);
10670     }
10671     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10672 //    SendToProgram("go\n", onmove);
10673     onmove->maybeThinking = TRUE;
10674     SetMachineThinkingEnables();
10675
10676     StartClocks();
10677
10678     if(bookHit) { // [HGM] book: simulate book reply
10679         static char bookMove[MSG_SIZ]; // a bit generous?
10680
10681         programStats.nodes = programStats.depth = programStats.time = 
10682         programStats.score = programStats.got_only_move = 0;
10683         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10684
10685         strcpy(bookMove, "move ");
10686         strcat(bookMove, bookHit);
10687         HandleMachineMove(bookMove, &first);
10688     }
10689 }
10690
10691 void
10692 TrainingEvent()
10693 {
10694     if (gameMode == Training) {
10695       SetTrainingModeOff();
10696       gameMode = PlayFromGameFile;
10697       DisplayMessage("", _("Training mode off"));
10698     } else {
10699       gameMode = Training;
10700       animateTraining = appData.animate;
10701
10702       /* make sure we are not already at the end of the game */
10703       if (currentMove < forwardMostMove) {
10704         SetTrainingModeOn();
10705         DisplayMessage("", _("Training mode on"));
10706       } else {
10707         gameMode = PlayFromGameFile;
10708         DisplayError(_("Already at end of game"), 0);
10709       }
10710     }
10711     ModeHighlight();
10712 }
10713
10714 void
10715 IcsClientEvent()
10716 {
10717     if (!appData.icsActive) return;
10718     switch (gameMode) {
10719       case IcsPlayingWhite:
10720       case IcsPlayingBlack:
10721       case IcsObserving:
10722       case IcsIdle:
10723       case BeginningOfGame:
10724       case IcsExamining:
10725         return;
10726
10727       case EditGame:
10728         break;
10729
10730       case EditPosition:
10731         EditPositionDone();
10732         break;
10733
10734       case AnalyzeMode:
10735       case AnalyzeFile:
10736         ExitAnalyzeMode();
10737         break;
10738         
10739       default:
10740         EditGameEvent();
10741         break;
10742     }
10743
10744     gameMode = IcsIdle;
10745     ModeHighlight();
10746     return;
10747 }
10748
10749
10750 void
10751 EditGameEvent()
10752 {
10753     int i;
10754
10755     switch (gameMode) {
10756       case Training:
10757         SetTrainingModeOff();
10758         break;
10759       case MachinePlaysWhite:
10760       case MachinePlaysBlack:
10761       case BeginningOfGame:
10762         SendToProgram("force\n", &first);
10763         SetUserThinkingEnables();
10764         break;
10765       case PlayFromGameFile:
10766         (void) StopLoadGameTimer();
10767         if (gameFileFP != NULL) {
10768             gameFileFP = NULL;
10769         }
10770         break;
10771       case EditPosition:
10772         EditPositionDone();
10773         break;
10774       case AnalyzeMode:
10775       case AnalyzeFile:
10776         ExitAnalyzeMode();
10777         SendToProgram("force\n", &first);
10778         break;
10779       case TwoMachinesPlay:
10780         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10781         ResurrectChessProgram();
10782         SetUserThinkingEnables();
10783         break;
10784       case EndOfGame:
10785         ResurrectChessProgram();
10786         break;
10787       case IcsPlayingBlack:
10788       case IcsPlayingWhite:
10789         DisplayError(_("Warning: You are still playing a game"), 0);
10790         break;
10791       case IcsObserving:
10792         DisplayError(_("Warning: You are still observing a game"), 0);
10793         break;
10794       case IcsExamining:
10795         DisplayError(_("Warning: You are still examining a game"), 0);
10796         break;
10797       case IcsIdle:
10798         break;
10799       case EditGame:
10800       default:
10801         return;
10802     }
10803     
10804     pausing = FALSE;
10805     StopClocks();
10806     first.offeredDraw = second.offeredDraw = 0;
10807
10808     if (gameMode == PlayFromGameFile) {
10809         whiteTimeRemaining = timeRemaining[0][currentMove];
10810         blackTimeRemaining = timeRemaining[1][currentMove];
10811         DisplayTitle("");
10812     }
10813
10814     if (gameMode == MachinePlaysWhite ||
10815         gameMode == MachinePlaysBlack ||
10816         gameMode == TwoMachinesPlay ||
10817         gameMode == EndOfGame) {
10818         i = forwardMostMove;
10819         while (i > currentMove) {
10820             SendToProgram("undo\n", &first);
10821             i--;
10822         }
10823         whiteTimeRemaining = timeRemaining[0][currentMove];
10824         blackTimeRemaining = timeRemaining[1][currentMove];
10825         DisplayBothClocks();
10826         if (whiteFlag || blackFlag) {
10827             whiteFlag = blackFlag = 0;
10828         }
10829         DisplayTitle("");
10830     }           
10831     
10832     gameMode = EditGame;
10833     ModeHighlight();
10834     SetGameInfo();
10835 }
10836
10837
10838 void
10839 EditPositionEvent()
10840 {
10841     if (gameMode == EditPosition) {
10842         EditGameEvent();
10843         return;
10844     }
10845     
10846     EditGameEvent();
10847     if (gameMode != EditGame) return;
10848     
10849     gameMode = EditPosition;
10850     ModeHighlight();
10851     SetGameInfo();
10852     if (currentMove > 0)
10853       CopyBoard(boards[0], boards[currentMove]);
10854     
10855     blackPlaysFirst = !WhiteOnMove(currentMove);
10856     ResetClocks();
10857     currentMove = forwardMostMove = backwardMostMove = 0;
10858     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10859     DisplayMove(-1);
10860 }
10861
10862 void
10863 ExitAnalyzeMode()
10864 {
10865     /* [DM] icsEngineAnalyze - possible call from other functions */
10866     if (appData.icsEngineAnalyze) {
10867         appData.icsEngineAnalyze = FALSE;
10868
10869         DisplayMessage("",_("Close ICS engine analyze..."));
10870     }
10871     if (first.analysisSupport && first.analyzing) {
10872       SendToProgram("exit\n", &first);
10873       first.analyzing = FALSE;
10874     }
10875     AnalysisPopDown();
10876     thinkOutput[0] = NULLCHAR;
10877 }
10878
10879 void
10880 EditPositionDone()
10881 {
10882     startedFromSetupPosition = TRUE;
10883     InitChessProgram(&first, FALSE);
10884     SendToProgram("force\n", &first);
10885     if (blackPlaysFirst) {
10886         strcpy(moveList[0], "");
10887         strcpy(parseList[0], "");
10888         currentMove = forwardMostMove = backwardMostMove = 1;
10889         CopyBoard(boards[1], boards[0]);
10890         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10891         { int i;
10892           epStatus[1] = epStatus[0];
10893           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10894         }
10895     } else {
10896         currentMove = forwardMostMove = backwardMostMove = 0;
10897     }
10898     SendBoard(&first, forwardMostMove);
10899     if (appData.debugMode) {
10900         fprintf(debugFP, "EditPosDone\n");
10901     }
10902     DisplayTitle("");
10903     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10904     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10905     gameMode = EditGame;
10906     ModeHighlight();
10907     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10908     ClearHighlights(); /* [AS] */
10909 }
10910
10911 /* Pause for `ms' milliseconds */
10912 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10913 void
10914 TimeDelay(ms)
10915      long ms;
10916 {
10917     TimeMark m1, m2;
10918
10919     GetTimeMark(&m1);
10920     do {
10921         GetTimeMark(&m2);
10922     } while (SubtractTimeMarks(&m2, &m1) < ms);
10923 }
10924
10925 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10926 void
10927 SendMultiLineToICS(buf)
10928      char *buf;
10929 {
10930     char temp[MSG_SIZ+1], *p;
10931     int len;
10932
10933     len = strlen(buf);
10934     if (len > MSG_SIZ)
10935       len = MSG_SIZ;
10936   
10937     strncpy(temp, buf, len);
10938     temp[len] = 0;
10939
10940     p = temp;
10941     while (*p) {
10942         if (*p == '\n' || *p == '\r')
10943           *p = ' ';
10944         ++p;
10945     }
10946
10947     strcat(temp, "\n");
10948     SendToICS(temp);
10949     SendToPlayer(temp, strlen(temp));
10950 }
10951
10952 void
10953 SetWhiteToPlayEvent()
10954 {
10955     if (gameMode == EditPosition) {
10956         blackPlaysFirst = FALSE;
10957         DisplayBothClocks();    /* works because currentMove is 0 */
10958     } else if (gameMode == IcsExamining) {
10959         SendToICS(ics_prefix);
10960         SendToICS("tomove white\n");
10961     }
10962 }
10963
10964 void
10965 SetBlackToPlayEvent()
10966 {
10967     if (gameMode == EditPosition) {
10968         blackPlaysFirst = TRUE;
10969         currentMove = 1;        /* kludge */
10970         DisplayBothClocks();
10971         currentMove = 0;
10972     } else if (gameMode == IcsExamining) {
10973         SendToICS(ics_prefix);
10974         SendToICS("tomove black\n");
10975     }
10976 }
10977
10978 void
10979 EditPositionMenuEvent(selection, x, y)
10980      ChessSquare selection;
10981      int x, y;
10982 {
10983     char buf[MSG_SIZ];
10984     ChessSquare piece = boards[0][y][x];
10985
10986     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10987
10988     switch (selection) {
10989       case ClearBoard:
10990         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10991             SendToICS(ics_prefix);
10992             SendToICS("bsetup clear\n");
10993         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10994             SendToICS(ics_prefix);
10995             SendToICS("clearboard\n");
10996         } else {
10997             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10998                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10999                 for (y = 0; y < BOARD_HEIGHT; y++) {
11000                     if (gameMode == IcsExamining) {
11001                         if (boards[currentMove][y][x] != EmptySquare) {
11002                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11003                                     AAA + x, ONE + y);
11004                             SendToICS(buf);
11005                         }
11006                     } else {
11007                         boards[0][y][x] = p;
11008                     }
11009                 }
11010             }
11011         }
11012         if (gameMode == EditPosition) {
11013             DrawPosition(FALSE, boards[0]);
11014         }
11015         break;
11016
11017       case WhitePlay:
11018         SetWhiteToPlayEvent();
11019         break;
11020
11021       case BlackPlay:
11022         SetBlackToPlayEvent();
11023         break;
11024
11025       case EmptySquare:
11026         if (gameMode == IcsExamining) {
11027             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11028             SendToICS(buf);
11029         } else {
11030             boards[0][y][x] = EmptySquare;
11031             DrawPosition(FALSE, boards[0]);
11032         }
11033         break;
11034
11035       case PromotePiece:
11036         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11037            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11038             selection = (ChessSquare) (PROMOTED piece);
11039         } else if(piece == EmptySquare) selection = WhiteSilver;
11040         else selection = (ChessSquare)((int)piece - 1);
11041         goto defaultlabel;
11042
11043       case DemotePiece:
11044         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11045            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11046             selection = (ChessSquare) (DEMOTED piece);
11047         } else if(piece == EmptySquare) selection = BlackSilver;
11048         else selection = (ChessSquare)((int)piece + 1);       
11049         goto defaultlabel;
11050
11051       case WhiteQueen:
11052       case BlackQueen:
11053         if(gameInfo.variant == VariantShatranj ||
11054            gameInfo.variant == VariantXiangqi  ||
11055            gameInfo.variant == VariantCourier    )
11056             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11057         goto defaultlabel;
11058
11059       case WhiteKing:
11060       case BlackKing:
11061         if(gameInfo.variant == VariantXiangqi)
11062             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11063         if(gameInfo.variant == VariantKnightmate)
11064             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11065       default:
11066         defaultlabel:
11067         if (gameMode == IcsExamining) {
11068             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11069                     PieceToChar(selection), AAA + x, ONE + y);
11070             SendToICS(buf);
11071         } else {
11072             boards[0][y][x] = selection;
11073             DrawPosition(FALSE, boards[0]);
11074         }
11075         break;
11076     }
11077 }
11078
11079
11080 void
11081 DropMenuEvent(selection, x, y)
11082      ChessSquare selection;
11083      int x, y;
11084 {
11085     ChessMove moveType;
11086
11087     switch (gameMode) {
11088       case IcsPlayingWhite:
11089       case MachinePlaysBlack:
11090         if (!WhiteOnMove(currentMove)) {
11091             DisplayMoveError(_("It is Black's turn"));
11092             return;
11093         }
11094         moveType = WhiteDrop;
11095         break;
11096       case IcsPlayingBlack:
11097       case MachinePlaysWhite:
11098         if (WhiteOnMove(currentMove)) {
11099             DisplayMoveError(_("It is White's turn"));
11100             return;
11101         }
11102         moveType = BlackDrop;
11103         break;
11104       case EditGame:
11105         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11106         break;
11107       default:
11108         return;
11109     }
11110
11111     if (moveType == BlackDrop && selection < BlackPawn) {
11112       selection = (ChessSquare) ((int) selection
11113                                  + (int) BlackPawn - (int) WhitePawn);
11114     }
11115     if (boards[currentMove][y][x] != EmptySquare) {
11116         DisplayMoveError(_("That square is occupied"));
11117         return;
11118     }
11119
11120     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11121 }
11122
11123 void
11124 AcceptEvent()
11125 {
11126     /* Accept a pending offer of any kind from opponent */
11127     
11128     if (appData.icsActive) {
11129         SendToICS(ics_prefix);
11130         SendToICS("accept\n");
11131     } else if (cmailMsgLoaded) {
11132         if (currentMove == cmailOldMove &&
11133             commentList[cmailOldMove] != NULL &&
11134             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11135                    "Black offers a draw" : "White offers a draw")) {
11136             TruncateGame();
11137             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11139         } else {
11140             DisplayError(_("There is no pending offer on this move"), 0);
11141             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11142         }
11143     } else {
11144         /* Not used for offers from chess program */
11145     }
11146 }
11147
11148 void
11149 DeclineEvent()
11150 {
11151     /* Decline a pending offer of any kind from opponent */
11152     
11153     if (appData.icsActive) {
11154         SendToICS(ics_prefix);
11155         SendToICS("decline\n");
11156     } else if (cmailMsgLoaded) {
11157         if (currentMove == cmailOldMove &&
11158             commentList[cmailOldMove] != NULL &&
11159             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11160                    "Black offers a draw" : "White offers a draw")) {
11161 #ifdef NOTDEF
11162             AppendComment(cmailOldMove, "Draw declined");
11163             DisplayComment(cmailOldMove - 1, "Draw declined");
11164 #endif /*NOTDEF*/
11165         } else {
11166             DisplayError(_("There is no pending offer on this move"), 0);
11167         }
11168     } else {
11169         /* Not used for offers from chess program */
11170     }
11171 }
11172
11173 void
11174 RematchEvent()
11175 {
11176     /* Issue ICS rematch command */
11177     if (appData.icsActive) {
11178         SendToICS(ics_prefix);
11179         SendToICS("rematch\n");
11180     }
11181 }
11182
11183 void
11184 CallFlagEvent()
11185 {
11186     /* Call your opponent's flag (claim a win on time) */
11187     if (appData.icsActive) {
11188         SendToICS(ics_prefix);
11189         SendToICS("flag\n");
11190     } else {
11191         switch (gameMode) {
11192           default:
11193             return;
11194           case MachinePlaysWhite:
11195             if (whiteFlag) {
11196                 if (blackFlag)
11197                   GameEnds(GameIsDrawn, "Both players ran out of time",
11198                            GE_PLAYER);
11199                 else
11200                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11201             } else {
11202                 DisplayError(_("Your opponent is not out of time"), 0);
11203             }
11204             break;
11205           case MachinePlaysBlack:
11206             if (blackFlag) {
11207                 if (whiteFlag)
11208                   GameEnds(GameIsDrawn, "Both players ran out of time",
11209                            GE_PLAYER);
11210                 else
11211                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11212             } else {
11213                 DisplayError(_("Your opponent is not out of time"), 0);
11214             }
11215             break;
11216         }
11217     }
11218 }
11219
11220 void
11221 DrawEvent()
11222 {
11223     /* Offer draw or accept pending draw offer from opponent */
11224     
11225     if (appData.icsActive) {
11226         /* Note: tournament rules require draw offers to be
11227            made after you make your move but before you punch
11228            your clock.  Currently ICS doesn't let you do that;
11229            instead, you immediately punch your clock after making
11230            a move, but you can offer a draw at any time. */
11231         
11232         SendToICS(ics_prefix);
11233         SendToICS("draw\n");
11234     } else if (cmailMsgLoaded) {
11235         if (currentMove == cmailOldMove &&
11236             commentList[cmailOldMove] != NULL &&
11237             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11238                    "Black offers a draw" : "White offers a draw")) {
11239             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11241         } else if (currentMove == cmailOldMove + 1) {
11242             char *offer = WhiteOnMove(cmailOldMove) ?
11243               "White offers a draw" : "Black offers a draw";
11244             AppendComment(currentMove, offer);
11245             DisplayComment(currentMove - 1, offer);
11246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11247         } else {
11248             DisplayError(_("You must make your move before offering a draw"), 0);
11249             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11250         }
11251     } else if (first.offeredDraw) {
11252         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11253     } else {
11254         if (first.sendDrawOffers) {
11255             SendToProgram("draw\n", &first);
11256             userOfferedDraw = TRUE;
11257         }
11258     }
11259 }
11260
11261 void
11262 AdjournEvent()
11263 {
11264     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11265     
11266     if (appData.icsActive) {
11267         SendToICS(ics_prefix);
11268         SendToICS("adjourn\n");
11269     } else {
11270         /* Currently GNU Chess doesn't offer or accept Adjourns */
11271     }
11272 }
11273
11274
11275 void
11276 AbortEvent()
11277 {
11278     /* Offer Abort or accept pending Abort offer from opponent */
11279     
11280     if (appData.icsActive) {
11281         SendToICS(ics_prefix);
11282         SendToICS("abort\n");
11283     } else {
11284         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11285     }
11286 }
11287
11288 void
11289 ResignEvent()
11290 {
11291     /* Resign.  You can do this even if it's not your turn. */
11292     
11293     if (appData.icsActive) {
11294         SendToICS(ics_prefix);
11295         SendToICS("resign\n");
11296     } else {
11297         switch (gameMode) {
11298           case MachinePlaysWhite:
11299             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11300             break;
11301           case MachinePlaysBlack:
11302             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11303             break;
11304           case EditGame:
11305             if (cmailMsgLoaded) {
11306                 TruncateGame();
11307                 if (WhiteOnMove(cmailOldMove)) {
11308                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11309                 } else {
11310                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11311                 }
11312                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11313             }
11314             break;
11315           default:
11316             break;
11317         }
11318     }
11319 }
11320
11321
11322 void
11323 StopObservingEvent()
11324 {
11325     /* Stop observing current games */
11326     SendToICS(ics_prefix);
11327     SendToICS("unobserve\n");
11328 }
11329
11330 void
11331 StopExaminingEvent()
11332 {
11333     /* Stop observing current game */
11334     SendToICS(ics_prefix);
11335     SendToICS("unexamine\n");
11336 }
11337
11338 void
11339 ForwardInner(target)
11340      int target;
11341 {
11342     int limit;
11343
11344     if (appData.debugMode)
11345         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11346                 target, currentMove, forwardMostMove);
11347
11348     if (gameMode == EditPosition)
11349       return;
11350
11351     if (gameMode == PlayFromGameFile && !pausing)
11352       PauseEvent();
11353     
11354     if (gameMode == IcsExamining && pausing)
11355       limit = pauseExamForwardMostMove;
11356     else
11357       limit = forwardMostMove;
11358     
11359     if (target > limit) target = limit;
11360
11361     if (target > 0 && moveList[target - 1][0]) {
11362         int fromX, fromY, toX, toY;
11363         toX = moveList[target - 1][2] - AAA;
11364         toY = moveList[target - 1][3] - ONE;
11365         if (moveList[target - 1][1] == '@') {
11366             if (appData.highlightLastMove) {
11367                 SetHighlights(-1, -1, toX, toY);
11368             }
11369         } else {
11370             fromX = moveList[target - 1][0] - AAA;
11371             fromY = moveList[target - 1][1] - ONE;
11372             if (target == currentMove + 1) {
11373                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11374             }
11375             if (appData.highlightLastMove) {
11376                 SetHighlights(fromX, fromY, toX, toY);
11377             }
11378         }
11379     }
11380     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11381         gameMode == Training || gameMode == PlayFromGameFile || 
11382         gameMode == AnalyzeFile) {
11383         while (currentMove < target) {
11384             SendMoveToProgram(currentMove++, &first);
11385         }
11386     } else {
11387         currentMove = target;
11388     }
11389     
11390     if (gameMode == EditGame || gameMode == EndOfGame) {
11391         whiteTimeRemaining = timeRemaining[0][currentMove];
11392         blackTimeRemaining = timeRemaining[1][currentMove];
11393     }
11394     DisplayBothClocks();
11395     DisplayMove(currentMove - 1);
11396     DrawPosition(FALSE, boards[currentMove]);
11397     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11398     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11399         DisplayComment(currentMove - 1, commentList[currentMove]);
11400     }
11401 }
11402
11403
11404 void
11405 ForwardEvent()
11406 {
11407     if (gameMode == IcsExamining && !pausing) {
11408         SendToICS(ics_prefix);
11409         SendToICS("forward\n");
11410     } else {
11411         ForwardInner(currentMove + 1);
11412     }
11413 }
11414
11415 void
11416 ToEndEvent()
11417 {
11418     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11419         /* to optimze, we temporarily turn off analysis mode while we feed
11420          * the remaining moves to the engine. Otherwise we get analysis output
11421          * after each move.
11422          */ 
11423         if (first.analysisSupport) {
11424           SendToProgram("exit\nforce\n", &first);
11425           first.analyzing = FALSE;
11426         }
11427     }
11428         
11429     if (gameMode == IcsExamining && !pausing) {
11430         SendToICS(ics_prefix);
11431         SendToICS("forward 999999\n");
11432     } else {
11433         ForwardInner(forwardMostMove);
11434     }
11435
11436     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11437         /* we have fed all the moves, so reactivate analysis mode */
11438         SendToProgram("analyze\n", &first);
11439         first.analyzing = TRUE;
11440         /*first.maybeThinking = TRUE;*/
11441         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11442     }
11443 }
11444
11445 void
11446 BackwardInner(target)
11447      int target;
11448 {
11449     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11450
11451     if (appData.debugMode)
11452         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11453                 target, currentMove, forwardMostMove);
11454
11455     if (gameMode == EditPosition) return;
11456     if (currentMove <= backwardMostMove) {
11457         ClearHighlights();
11458         DrawPosition(full_redraw, boards[currentMove]);
11459         return;
11460     }
11461     if (gameMode == PlayFromGameFile && !pausing)
11462       PauseEvent();
11463     
11464     if (moveList[target][0]) {
11465         int fromX, fromY, toX, toY;
11466         toX = moveList[target][2] - AAA;
11467         toY = moveList[target][3] - ONE;
11468         if (moveList[target][1] == '@') {
11469             if (appData.highlightLastMove) {
11470                 SetHighlights(-1, -1, toX, toY);
11471             }
11472         } else {
11473             fromX = moveList[target][0] - AAA;
11474             fromY = moveList[target][1] - ONE;
11475             if (target == currentMove - 1) {
11476                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11477             }
11478             if (appData.highlightLastMove) {
11479                 SetHighlights(fromX, fromY, toX, toY);
11480             }
11481         }
11482     }
11483     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11484         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11485         while (currentMove > target) {
11486             SendToProgram("undo\n", &first);
11487             currentMove--;
11488         }
11489     } else {
11490         currentMove = target;
11491     }
11492     
11493     if (gameMode == EditGame || gameMode == EndOfGame) {
11494         whiteTimeRemaining = timeRemaining[0][currentMove];
11495         blackTimeRemaining = timeRemaining[1][currentMove];
11496     }
11497     DisplayBothClocks();
11498     DisplayMove(currentMove - 1);
11499     DrawPosition(full_redraw, boards[currentMove]);
11500     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11501     // [HGM] PV info: routine tests if comment empty
11502     DisplayComment(currentMove - 1, commentList[currentMove]);
11503 }
11504
11505 void
11506 BackwardEvent()
11507 {
11508     if (gameMode == IcsExamining && !pausing) {
11509         SendToICS(ics_prefix);
11510         SendToICS("backward\n");
11511     } else {
11512         BackwardInner(currentMove - 1);
11513     }
11514 }
11515
11516 void
11517 ToStartEvent()
11518 {
11519     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11520         /* to optimze, we temporarily turn off analysis mode while we undo
11521          * all the moves. Otherwise we get analysis output after each undo.
11522          */ 
11523         if (first.analysisSupport) {
11524           SendToProgram("exit\nforce\n", &first);
11525           first.analyzing = FALSE;
11526         }
11527     }
11528
11529     if (gameMode == IcsExamining && !pausing) {
11530         SendToICS(ics_prefix);
11531         SendToICS("backward 999999\n");
11532     } else {
11533         BackwardInner(backwardMostMove);
11534     }
11535
11536     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11537         /* we have fed all the moves, so reactivate analysis mode */
11538         SendToProgram("analyze\n", &first);
11539         first.analyzing = TRUE;
11540         /*first.maybeThinking = TRUE;*/
11541         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11542     }
11543 }
11544
11545 void
11546 ToNrEvent(int to)
11547 {
11548   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11549   if (to >= forwardMostMove) to = forwardMostMove;
11550   if (to <= backwardMostMove) to = backwardMostMove;
11551   if (to < currentMove) {
11552     BackwardInner(to);
11553   } else {
11554     ForwardInner(to);
11555   }
11556 }
11557
11558 void
11559 RevertEvent()
11560 {
11561     if (gameMode != IcsExamining) {
11562         DisplayError(_("You are not examining a game"), 0);
11563         return;
11564     }
11565     if (pausing) {
11566         DisplayError(_("You can't revert while pausing"), 0);
11567         return;
11568     }
11569     SendToICS(ics_prefix);
11570     SendToICS("revert\n");
11571 }
11572
11573 void
11574 RetractMoveEvent()
11575 {
11576     switch (gameMode) {
11577       case MachinePlaysWhite:
11578       case MachinePlaysBlack:
11579         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11580             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11581             return;
11582         }
11583         if (forwardMostMove < 2) return;
11584         currentMove = forwardMostMove = forwardMostMove - 2;
11585         whiteTimeRemaining = timeRemaining[0][currentMove];
11586         blackTimeRemaining = timeRemaining[1][currentMove];
11587         DisplayBothClocks();
11588         DisplayMove(currentMove - 1);
11589         ClearHighlights();/*!! could figure this out*/
11590         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11591         SendToProgram("remove\n", &first);
11592         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11593         break;
11594
11595       case BeginningOfGame:
11596       default:
11597         break;
11598
11599       case IcsPlayingWhite:
11600       case IcsPlayingBlack:
11601         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11602             SendToICS(ics_prefix);
11603             SendToICS("takeback 2\n");
11604         } else {
11605             SendToICS(ics_prefix);
11606             SendToICS("takeback 1\n");
11607         }
11608         break;
11609     }
11610 }
11611
11612 void
11613 MoveNowEvent()
11614 {
11615     ChessProgramState *cps;
11616
11617     switch (gameMode) {
11618       case MachinePlaysWhite:
11619         if (!WhiteOnMove(forwardMostMove)) {
11620             DisplayError(_("It is your turn"), 0);
11621             return;
11622         }
11623         cps = &first;
11624         break;
11625       case MachinePlaysBlack:
11626         if (WhiteOnMove(forwardMostMove)) {
11627             DisplayError(_("It is your turn"), 0);
11628             return;
11629         }
11630         cps = &first;
11631         break;
11632       case TwoMachinesPlay:
11633         if (WhiteOnMove(forwardMostMove) ==
11634             (first.twoMachinesColor[0] == 'w')) {
11635             cps = &first;
11636         } else {
11637             cps = &second;
11638         }
11639         break;
11640       case BeginningOfGame:
11641       default:
11642         return;
11643     }
11644     SendToProgram("?\n", cps);
11645 }
11646
11647 void
11648 TruncateGameEvent()
11649 {
11650     EditGameEvent();
11651     if (gameMode != EditGame) return;
11652     TruncateGame();
11653 }
11654
11655 void
11656 TruncateGame()
11657 {
11658     if (forwardMostMove > currentMove) {
11659         if (gameInfo.resultDetails != NULL) {
11660             free(gameInfo.resultDetails);
11661             gameInfo.resultDetails = NULL;
11662             gameInfo.result = GameUnfinished;
11663         }
11664         forwardMostMove = currentMove;
11665         HistorySet(parseList, backwardMostMove, forwardMostMove,
11666                    currentMove-1);
11667     }
11668 }
11669
11670 void
11671 HintEvent()
11672 {
11673     if (appData.noChessProgram) return;
11674     switch (gameMode) {
11675       case MachinePlaysWhite:
11676         if (WhiteOnMove(forwardMostMove)) {
11677             DisplayError(_("Wait until your turn"), 0);
11678             return;
11679         }
11680         break;
11681       case BeginningOfGame:
11682       case MachinePlaysBlack:
11683         if (!WhiteOnMove(forwardMostMove)) {
11684             DisplayError(_("Wait until your turn"), 0);
11685             return;
11686         }
11687         break;
11688       default:
11689         DisplayError(_("No hint available"), 0);
11690         return;
11691     }
11692     SendToProgram("hint\n", &first);
11693     hintRequested = TRUE;
11694 }
11695
11696 void
11697 BookEvent()
11698 {
11699     if (appData.noChessProgram) return;
11700     switch (gameMode) {
11701       case MachinePlaysWhite:
11702         if (WhiteOnMove(forwardMostMove)) {
11703             DisplayError(_("Wait until your turn"), 0);
11704             return;
11705         }
11706         break;
11707       case BeginningOfGame:
11708       case MachinePlaysBlack:
11709         if (!WhiteOnMove(forwardMostMove)) {
11710             DisplayError(_("Wait until your turn"), 0);
11711             return;
11712         }
11713         break;
11714       case EditPosition:
11715         EditPositionDone();
11716         break;
11717       case TwoMachinesPlay:
11718         return;
11719       default:
11720         break;
11721     }
11722     SendToProgram("bk\n", &first);
11723     bookOutput[0] = NULLCHAR;
11724     bookRequested = TRUE;
11725 }
11726
11727 void
11728 AboutGameEvent()
11729 {
11730     char *tags = PGNTags(&gameInfo);
11731     TagsPopUp(tags, CmailMsg());
11732     free(tags);
11733 }
11734
11735 /* end button procedures */
11736
11737 void
11738 PrintPosition(fp, move)
11739      FILE *fp;
11740      int move;
11741 {
11742     int i, j;
11743     
11744     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11745         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11746             char c = PieceToChar(boards[move][i][j]);
11747             fputc(c == 'x' ? '.' : c, fp);
11748             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11749         }
11750     }
11751     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11752       fprintf(fp, "white to play\n");
11753     else
11754       fprintf(fp, "black to play\n");
11755 }
11756
11757 void
11758 PrintOpponents(fp)
11759      FILE *fp;
11760 {
11761     if (gameInfo.white != NULL) {
11762         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11763     } else {
11764         fprintf(fp, "\n");
11765     }
11766 }
11767
11768 /* Find last component of program's own name, using some heuristics */
11769 void
11770 TidyProgramName(prog, host, buf)
11771      char *prog, *host, buf[MSG_SIZ];
11772 {
11773     char *p, *q;
11774     int local = (strcmp(host, "localhost") == 0);
11775     while (!local && (p = strchr(prog, ';')) != NULL) {
11776         p++;
11777         while (*p == ' ') p++;
11778         prog = p;
11779     }
11780     if (*prog == '"' || *prog == '\'') {
11781         q = strchr(prog + 1, *prog);
11782     } else {
11783         q = strchr(prog, ' ');
11784     }
11785     if (q == NULL) q = prog + strlen(prog);
11786     p = q;
11787     while (p >= prog && *p != '/' && *p != '\\') p--;
11788     p++;
11789     if(p == prog && *p == '"') p++;
11790     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11791     memcpy(buf, p, q - p);
11792     buf[q - p] = NULLCHAR;
11793     if (!local) {
11794         strcat(buf, "@");
11795         strcat(buf, host);
11796     }
11797 }
11798
11799 char *
11800 TimeControlTagValue()
11801 {
11802     char buf[MSG_SIZ];
11803     if (!appData.clockMode) {
11804         strcpy(buf, "-");
11805     } else if (movesPerSession > 0) {
11806         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11807     } else if (timeIncrement == 0) {
11808         sprintf(buf, "%ld", timeControl/1000);
11809     } else {
11810         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11811     }
11812     return StrSave(buf);
11813 }
11814
11815 void
11816 SetGameInfo()
11817 {
11818     /* This routine is used only for certain modes */
11819     VariantClass v = gameInfo.variant;
11820     ClearGameInfo(&gameInfo);
11821     gameInfo.variant = v;
11822
11823     switch (gameMode) {
11824       case MachinePlaysWhite:
11825         gameInfo.event = StrSave( appData.pgnEventHeader );
11826         gameInfo.site = StrSave(HostName());
11827         gameInfo.date = PGNDate();
11828         gameInfo.round = StrSave("-");
11829         gameInfo.white = StrSave(first.tidy);
11830         gameInfo.black = StrSave(UserName());
11831         gameInfo.timeControl = TimeControlTagValue();
11832         break;
11833
11834       case MachinePlaysBlack:
11835         gameInfo.event = StrSave( appData.pgnEventHeader );
11836         gameInfo.site = StrSave(HostName());
11837         gameInfo.date = PGNDate();
11838         gameInfo.round = StrSave("-");
11839         gameInfo.white = StrSave(UserName());
11840         gameInfo.black = StrSave(first.tidy);
11841         gameInfo.timeControl = TimeControlTagValue();
11842         break;
11843
11844       case TwoMachinesPlay:
11845         gameInfo.event = StrSave( appData.pgnEventHeader );
11846         gameInfo.site = StrSave(HostName());
11847         gameInfo.date = PGNDate();
11848         if (matchGame > 0) {
11849             char buf[MSG_SIZ];
11850             sprintf(buf, "%d", matchGame);
11851             gameInfo.round = StrSave(buf);
11852         } else {
11853             gameInfo.round = StrSave("-");
11854         }
11855         if (first.twoMachinesColor[0] == 'w') {
11856             gameInfo.white = StrSave(first.tidy);
11857             gameInfo.black = StrSave(second.tidy);
11858         } else {
11859             gameInfo.white = StrSave(second.tidy);
11860             gameInfo.black = StrSave(first.tidy);
11861         }
11862         gameInfo.timeControl = TimeControlTagValue();
11863         break;
11864
11865       case EditGame:
11866         gameInfo.event = StrSave("Edited game");
11867         gameInfo.site = StrSave(HostName());
11868         gameInfo.date = PGNDate();
11869         gameInfo.round = StrSave("-");
11870         gameInfo.white = StrSave("-");
11871         gameInfo.black = StrSave("-");
11872         break;
11873
11874       case EditPosition:
11875         gameInfo.event = StrSave("Edited position");
11876         gameInfo.site = StrSave(HostName());
11877         gameInfo.date = PGNDate();
11878         gameInfo.round = StrSave("-");
11879         gameInfo.white = StrSave("-");
11880         gameInfo.black = StrSave("-");
11881         break;
11882
11883       case IcsPlayingWhite:
11884       case IcsPlayingBlack:
11885       case IcsObserving:
11886       case IcsExamining:
11887         break;
11888
11889       case PlayFromGameFile:
11890         gameInfo.event = StrSave("Game from non-PGN file");
11891         gameInfo.site = StrSave(HostName());
11892         gameInfo.date = PGNDate();
11893         gameInfo.round = StrSave("-");
11894         gameInfo.white = StrSave("?");
11895         gameInfo.black = StrSave("?");
11896         break;
11897
11898       default:
11899         break;
11900     }
11901 }
11902
11903 void
11904 ReplaceComment(index, text)
11905      int index;
11906      char *text;
11907 {
11908     int len;
11909
11910     while (*text == '\n') text++;
11911     len = strlen(text);
11912     while (len > 0 && text[len - 1] == '\n') len--;
11913
11914     if (commentList[index] != NULL)
11915       free(commentList[index]);
11916
11917     if (len == 0) {
11918         commentList[index] = NULL;
11919         return;
11920     }
11921     commentList[index] = (char *) malloc(len + 2);
11922     strncpy(commentList[index], text, len);
11923     commentList[index][len] = '\n';
11924     commentList[index][len + 1] = NULLCHAR;
11925 }
11926
11927 void
11928 CrushCRs(text)
11929      char *text;
11930 {
11931   char *p = text;
11932   char *q = text;
11933   char ch;
11934
11935   do {
11936     ch = *p++;
11937     if (ch == '\r') continue;
11938     *q++ = ch;
11939   } while (ch != '\0');
11940 }
11941
11942 void
11943 AppendComment(index, text)
11944      int index;
11945      char *text;
11946 {
11947     int oldlen, len;
11948     char *old;
11949
11950     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11951
11952     CrushCRs(text);
11953     while (*text == '\n') text++;
11954     len = strlen(text);
11955     while (len > 0 && text[len - 1] == '\n') len--;
11956
11957     if (len == 0) return;
11958
11959     if (commentList[index] != NULL) {
11960         old = commentList[index];
11961         oldlen = strlen(old);
11962         commentList[index] = (char *) malloc(oldlen + len + 2);
11963         strcpy(commentList[index], old);
11964         free(old);
11965         strncpy(&commentList[index][oldlen], text, len);
11966         commentList[index][oldlen + len] = '\n';
11967         commentList[index][oldlen + len + 1] = NULLCHAR;
11968     } else {
11969         commentList[index] = (char *) malloc(len + 2);
11970         strncpy(commentList[index], text, len);
11971         commentList[index][len] = '\n';
11972         commentList[index][len + 1] = NULLCHAR;
11973     }
11974 }
11975
11976 static char * FindStr( char * text, char * sub_text )
11977 {
11978     char * result = strstr( text, sub_text );
11979
11980     if( result != NULL ) {
11981         result += strlen( sub_text );
11982     }
11983
11984     return result;
11985 }
11986
11987 /* [AS] Try to extract PV info from PGN comment */
11988 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11989 char *GetInfoFromComment( int index, char * text )
11990 {
11991     char * sep = text;
11992
11993     if( text != NULL && index > 0 ) {
11994         int score = 0;
11995         int depth = 0;
11996         int time = -1, sec = 0, deci;
11997         char * s_eval = FindStr( text, "[%eval " );
11998         char * s_emt = FindStr( text, "[%emt " );
11999
12000         if( s_eval != NULL || s_emt != NULL ) {
12001             /* New style */
12002             char delim;
12003
12004             if( s_eval != NULL ) {
12005                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12006                     return text;
12007                 }
12008
12009                 if( delim != ']' ) {
12010                     return text;
12011                 }
12012             }
12013
12014             if( s_emt != NULL ) {
12015             }
12016         }
12017         else {
12018             /* We expect something like: [+|-]nnn.nn/dd */
12019             int score_lo = 0;
12020
12021             sep = strchr( text, '/' );
12022             if( sep == NULL || sep < (text+4) ) {
12023                 return text;
12024             }
12025
12026             time = -1; sec = -1; deci = -1;
12027             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12028                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12029                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12030                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12031                 return text;
12032             }
12033
12034             if( score_lo < 0 || score_lo >= 100 ) {
12035                 return text;
12036             }
12037
12038             if(sec >= 0) time = 600*time + 10*sec; else
12039             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12040
12041             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12042
12043             /* [HGM] PV time: now locate end of PV info */
12044             while( *++sep >= '0' && *sep <= '9'); // strip depth
12045             if(time >= 0)
12046             while( *++sep >= '0' && *sep <= '9'); // strip time
12047             if(sec >= 0)
12048             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12049             if(deci >= 0)
12050             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12051             while(*sep == ' ') sep++;
12052         }
12053
12054         if( depth <= 0 ) {
12055             return text;
12056         }
12057
12058         if( time < 0 ) {
12059             time = -1;
12060         }
12061
12062         pvInfoList[index-1].depth = depth;
12063         pvInfoList[index-1].score = score;
12064         pvInfoList[index-1].time  = 10*time; // centi-sec
12065     }
12066     return sep;
12067 }
12068
12069 void
12070 SendToProgram(message, cps)
12071      char *message;
12072      ChessProgramState *cps;
12073 {
12074     int count, outCount, error;
12075     char buf[MSG_SIZ];
12076
12077     if (cps->pr == NULL) return;
12078     Attention(cps);
12079     
12080     if (appData.debugMode) {
12081         TimeMark now;
12082         GetTimeMark(&now);
12083         fprintf(debugFP, "%ld >%-6s: %s", 
12084                 SubtractTimeMarks(&now, &programStartTime),
12085                 cps->which, message);
12086     }
12087     
12088     count = strlen(message);
12089     outCount = OutputToProcess(cps->pr, message, count, &error);
12090     if (outCount < count && !exiting 
12091                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12092         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12093         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12094             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12095                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12096                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12097             } else {
12098                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12099             }
12100             gameInfo.resultDetails = buf;
12101         }
12102         DisplayFatalError(buf, error, 1);
12103     }
12104 }
12105
12106 void
12107 ReceiveFromProgram(isr, closure, message, count, error)
12108      InputSourceRef isr;
12109      VOIDSTAR closure;
12110      char *message;
12111      int count;
12112      int error;
12113 {
12114     char *end_str;
12115     char buf[MSG_SIZ];
12116     ChessProgramState *cps = (ChessProgramState *)closure;
12117
12118     if (isr != cps->isr) return; /* Killed intentionally */
12119     if (count <= 0) {
12120         if (count == 0) {
12121             sprintf(buf,
12122                     _("Error: %s chess program (%s) exited unexpectedly"),
12123                     cps->which, cps->program);
12124         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12125                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12126                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12127                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12128                 } else {
12129                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12130                 }
12131                 gameInfo.resultDetails = buf;
12132             }
12133             RemoveInputSource(cps->isr);
12134             DisplayFatalError(buf, 0, 1);
12135         } else {
12136             sprintf(buf,
12137                     _("Error reading from %s chess program (%s)"),
12138                     cps->which, cps->program);
12139             RemoveInputSource(cps->isr);
12140
12141             /* [AS] Program is misbehaving badly... kill it */
12142             if( count == -2 ) {
12143                 DestroyChildProcess( cps->pr, 9 );
12144                 cps->pr = NoProc;
12145             }
12146
12147             DisplayFatalError(buf, error, 1);
12148         }
12149         return;
12150     }
12151     
12152     if ((end_str = strchr(message, '\r')) != NULL)
12153       *end_str = NULLCHAR;
12154     if ((end_str = strchr(message, '\n')) != NULL)
12155       *end_str = NULLCHAR;
12156     
12157     if (appData.debugMode) {
12158         TimeMark now; int print = 1;
12159         char *quote = ""; char c; int i;
12160
12161         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12162                 char start = message[0];
12163                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12164                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12165                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12166                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12167                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12168                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12169                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12170                         { quote = "# "; print = (appData.engineComments == 2); }
12171                 message[0] = start; // restore original message
12172         }
12173         if(print) {
12174                 GetTimeMark(&now);
12175                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12176                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12177                         quote,
12178                         message);
12179         }
12180     }
12181
12182     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12183     if (appData.icsEngineAnalyze) {
12184         if (strstr(message, "whisper") != NULL ||
12185              strstr(message, "kibitz") != NULL || 
12186             strstr(message, "tellics") != NULL) return;
12187     }
12188
12189     HandleMachineMove(message, cps);
12190 }
12191
12192
12193 void
12194 SendTimeControl(cps, mps, tc, inc, sd, st)
12195      ChessProgramState *cps;
12196      int mps, inc, sd, st;
12197      long tc;
12198 {
12199     char buf[MSG_SIZ];
12200     int seconds;
12201
12202     if( timeControl_2 > 0 ) {
12203         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12204             tc = timeControl_2;
12205         }
12206     }
12207     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12208     inc /= cps->timeOdds;
12209     st  /= cps->timeOdds;
12210
12211     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12212
12213     if (st > 0) {
12214       /* Set exact time per move, normally using st command */
12215       if (cps->stKludge) {
12216         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12217         seconds = st % 60;
12218         if (seconds == 0) {
12219           sprintf(buf, "level 1 %d\n", st/60);
12220         } else {
12221           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12222         }
12223       } else {
12224         sprintf(buf, "st %d\n", st);
12225       }
12226     } else {
12227       /* Set conventional or incremental time control, using level command */
12228       if (seconds == 0) {
12229         /* Note old gnuchess bug -- minutes:seconds used to not work.
12230            Fixed in later versions, but still avoid :seconds
12231            when seconds is 0. */
12232         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12233       } else {
12234         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12235                 seconds, inc/1000);
12236       }
12237     }
12238     SendToProgram(buf, cps);
12239
12240     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12241     /* Orthogonally, limit search to given depth */
12242     if (sd > 0) {
12243       if (cps->sdKludge) {
12244         sprintf(buf, "depth\n%d\n", sd);
12245       } else {
12246         sprintf(buf, "sd %d\n", sd);
12247       }
12248       SendToProgram(buf, cps);
12249     }
12250
12251     if(cps->nps > 0) { /* [HGM] nps */
12252         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12253         else {
12254                 sprintf(buf, "nps %d\n", cps->nps);
12255               SendToProgram(buf, cps);
12256         }
12257     }
12258 }
12259
12260 ChessProgramState *WhitePlayer()
12261 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12262 {
12263     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12264        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12265         return &second;
12266     return &first;
12267 }
12268
12269 void
12270 SendTimeRemaining(cps, machineWhite)
12271      ChessProgramState *cps;
12272      int /*boolean*/ machineWhite;
12273 {
12274     char message[MSG_SIZ];
12275     long time, otime;
12276
12277     /* Note: this routine must be called when the clocks are stopped
12278        or when they have *just* been set or switched; otherwise
12279        it will be off by the time since the current tick started.
12280     */
12281     if (machineWhite) {
12282         time = whiteTimeRemaining / 10;
12283         otime = blackTimeRemaining / 10;
12284     } else {
12285         time = blackTimeRemaining / 10;
12286         otime = whiteTimeRemaining / 10;
12287     }
12288     /* [HGM] translate opponent's time by time-odds factor */
12289     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12290     if (appData.debugMode) {
12291         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12292     }
12293
12294     if (time <= 0) time = 1;
12295     if (otime <= 0) otime = 1;
12296     
12297     sprintf(message, "time %ld\n", time);
12298     SendToProgram(message, cps);
12299
12300     sprintf(message, "otim %ld\n", otime);
12301     SendToProgram(message, cps);
12302 }
12303
12304 int
12305 BoolFeature(p, name, loc, cps)
12306      char **p;
12307      char *name;
12308      int *loc;
12309      ChessProgramState *cps;
12310 {
12311   char buf[MSG_SIZ];
12312   int len = strlen(name);
12313   int val;
12314   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12315     (*p) += len + 1;
12316     sscanf(*p, "%d", &val);
12317     *loc = (val != 0);
12318     while (**p && **p != ' ') (*p)++;
12319     sprintf(buf, "accepted %s\n", name);
12320     SendToProgram(buf, cps);
12321     return TRUE;
12322   }
12323   return FALSE;
12324 }
12325
12326 int
12327 IntFeature(p, name, loc, cps)
12328      char **p;
12329      char *name;
12330      int *loc;
12331      ChessProgramState *cps;
12332 {
12333   char buf[MSG_SIZ];
12334   int len = strlen(name);
12335   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12336     (*p) += len + 1;
12337     sscanf(*p, "%d", loc);
12338     while (**p && **p != ' ') (*p)++;
12339     sprintf(buf, "accepted %s\n", name);
12340     SendToProgram(buf, cps);
12341     return TRUE;
12342   }
12343   return FALSE;
12344 }
12345
12346 int
12347 StringFeature(p, name, loc, cps)
12348      char **p;
12349      char *name;
12350      char loc[];
12351      ChessProgramState *cps;
12352 {
12353   char buf[MSG_SIZ];
12354   int len = strlen(name);
12355   if (strncmp((*p), name, len) == 0
12356       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12357     (*p) += len + 2;
12358     sscanf(*p, "%[^\"]", loc);
12359     while (**p && **p != '\"') (*p)++;
12360     if (**p == '\"') (*p)++;
12361     sprintf(buf, "accepted %s\n", name);
12362     SendToProgram(buf, cps);
12363     return TRUE;
12364   }
12365   return FALSE;
12366 }
12367
12368 int 
12369 ParseOption(Option *opt, ChessProgramState *cps)
12370 // [HGM] options: process the string that defines an engine option, and determine
12371 // name, type, default value, and allowed value range
12372 {
12373         char *p, *q, buf[MSG_SIZ];
12374         int n, min = (-1)<<31, max = 1<<31, def;
12375
12376         if(p = strstr(opt->name, " -spin ")) {
12377             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12378             if(max < min) max = min; // enforce consistency
12379             if(def < min) def = min;
12380             if(def > max) def = max;
12381             opt->value = def;
12382             opt->min = min;
12383             opt->max = max;
12384             opt->type = Spin;
12385         } else if(p = strstr(opt->name, " -string ")) {
12386             opt->textValue = p+9;
12387             opt->type = TextBox;
12388         } else if(p = strstr(opt->name, " -check ")) {
12389             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12390             opt->value = (def != 0);
12391             opt->type = CheckBox;
12392         } else if(p = strstr(opt->name, " -combo ")) {
12393             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12394             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12395             opt->value = n = 0;
12396             while(q = StrStr(q, " /// ")) {
12397                 n++; *q = 0;    // count choices, and null-terminate each of them
12398                 q += 5;
12399                 if(*q == '*') { // remember default, which is marked with * prefix
12400                     q++;
12401                     opt->value = n;
12402                 }
12403                 cps->comboList[cps->comboCnt++] = q;
12404             }
12405             cps->comboList[cps->comboCnt++] = NULL;
12406             opt->max = n + 1;
12407             opt->type = ComboBox;
12408         } else if(p = strstr(opt->name, " -button")) {
12409             opt->type = Button;
12410         } else if(p = strstr(opt->name, " -save")) {
12411             opt->type = SaveButton;
12412         } else return FALSE;
12413         *p = 0; // terminate option name
12414         // now look if the command-line options define a setting for this engine option.
12415         if(cps->optionSettings && cps->optionSettings[0])
12416             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12417         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12418                 sprintf(buf, "option %s", p);
12419                 if(p = strstr(buf, ",")) *p = 0;
12420                 strcat(buf, "\n");
12421                 SendToProgram(buf, cps);
12422         }
12423         return TRUE;
12424 }
12425
12426 void
12427 FeatureDone(cps, val)
12428      ChessProgramState* cps;
12429      int val;
12430 {
12431   DelayedEventCallback cb = GetDelayedEvent();
12432   if ((cb == InitBackEnd3 && cps == &first) ||
12433       (cb == TwoMachinesEventIfReady && cps == &second)) {
12434     CancelDelayedEvent();
12435     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12436   }
12437   cps->initDone = val;
12438 }
12439
12440 /* Parse feature command from engine */
12441 void
12442 ParseFeatures(args, cps)
12443      char* args;
12444      ChessProgramState *cps;  
12445 {
12446   char *p = args;
12447   char *q;
12448   int val;
12449   char buf[MSG_SIZ];
12450
12451   for (;;) {
12452     while (*p == ' ') p++;
12453     if (*p == NULLCHAR) return;
12454
12455     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12456     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12457     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12458     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12459     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12460     if (BoolFeature(&p, "reuse", &val, cps)) {
12461       /* Engine can disable reuse, but can't enable it if user said no */
12462       if (!val) cps->reuse = FALSE;
12463       continue;
12464     }
12465     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12466     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12467       if (gameMode == TwoMachinesPlay) {
12468         DisplayTwoMachinesTitle();
12469       } else {
12470         DisplayTitle("");
12471       }
12472       continue;
12473     }
12474     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12475     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12476     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12477     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12478     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12479     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12480     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12481     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12482     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12483     if (IntFeature(&p, "done", &val, cps)) {
12484       FeatureDone(cps, val);
12485       continue;
12486     }
12487     /* Added by Tord: */
12488     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12489     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12490     /* End of additions by Tord */
12491
12492     /* [HGM] added features: */
12493     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12494     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12495     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12496     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12497     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12498     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12499     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12500         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12501         if(cps->nrOptions >= MAX_OPTIONS) {
12502             cps->nrOptions--;
12503             sprintf(buf, "%s engine has too many options\n", cps->which);
12504             DisplayError(buf, 0);
12505         }
12506         continue;
12507     }
12508     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12509     /* End of additions by HGM */
12510
12511     /* unknown feature: complain and skip */
12512     q = p;
12513     while (*q && *q != '=') q++;
12514     sprintf(buf, "rejected %.*s\n", q-p, p);
12515     SendToProgram(buf, cps);
12516     p = q;
12517     if (*p == '=') {
12518       p++;
12519       if (*p == '\"') {
12520         p++;
12521         while (*p && *p != '\"') p++;
12522         if (*p == '\"') p++;
12523       } else {
12524         while (*p && *p != ' ') p++;
12525       }
12526     }
12527   }
12528
12529 }
12530
12531 void
12532 PeriodicUpdatesEvent(newState)
12533      int newState;
12534 {
12535     if (newState == appData.periodicUpdates)
12536       return;
12537
12538     appData.periodicUpdates=newState;
12539
12540     /* Display type changes, so update it now */
12541     DisplayAnalysis();
12542
12543     /* Get the ball rolling again... */
12544     if (newState) {
12545         AnalysisPeriodicEvent(1);
12546         StartAnalysisClock();
12547     }
12548 }
12549
12550 void
12551 PonderNextMoveEvent(newState)
12552      int newState;
12553 {
12554     if (newState == appData.ponderNextMove) return;
12555     if (gameMode == EditPosition) EditPositionDone();
12556     if (newState) {
12557         SendToProgram("hard\n", &first);
12558         if (gameMode == TwoMachinesPlay) {
12559             SendToProgram("hard\n", &second);
12560         }
12561     } else {
12562         SendToProgram("easy\n", &first);
12563         thinkOutput[0] = NULLCHAR;
12564         if (gameMode == TwoMachinesPlay) {
12565             SendToProgram("easy\n", &second);
12566         }
12567     }
12568     appData.ponderNextMove = newState;
12569 }
12570
12571 void
12572 NewSettingEvent(option, command, value)
12573      char *command;
12574      int option, value;
12575 {
12576     char buf[MSG_SIZ];
12577
12578     if (gameMode == EditPosition) EditPositionDone();
12579     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12580     SendToProgram(buf, &first);
12581     if (gameMode == TwoMachinesPlay) {
12582         SendToProgram(buf, &second);
12583     }
12584 }
12585
12586 void
12587 ShowThinkingEvent()
12588 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12589 {
12590     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12591     int newState = appData.showThinking
12592         // [HGM] thinking: other features now need thinking output as well
12593         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12594     
12595     if (oldState == newState) return;
12596     oldState = newState;
12597     if (gameMode == EditPosition) EditPositionDone();
12598     if (oldState) {
12599         SendToProgram("post\n", &first);
12600         if (gameMode == TwoMachinesPlay) {
12601             SendToProgram("post\n", &second);
12602         }
12603     } else {
12604         SendToProgram("nopost\n", &first);
12605         thinkOutput[0] = NULLCHAR;
12606         if (gameMode == TwoMachinesPlay) {
12607             SendToProgram("nopost\n", &second);
12608         }
12609     }
12610 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12611 }
12612
12613 void
12614 AskQuestionEvent(title, question, replyPrefix, which)
12615      char *title; char *question; char *replyPrefix; char *which;
12616 {
12617   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12618   if (pr == NoProc) return;
12619   AskQuestion(title, question, replyPrefix, pr);
12620 }
12621
12622 void
12623 DisplayMove(moveNumber)
12624      int moveNumber;
12625 {
12626     char message[MSG_SIZ];
12627     char res[MSG_SIZ];
12628     char cpThinkOutput[MSG_SIZ];
12629
12630     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12631     
12632     if (moveNumber == forwardMostMove - 1 || 
12633         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12634
12635         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12636
12637         if (strchr(cpThinkOutput, '\n')) {
12638             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12639         }
12640     } else {
12641         *cpThinkOutput = NULLCHAR;
12642     }
12643
12644     /* [AS] Hide thinking from human user */
12645     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12646         *cpThinkOutput = NULLCHAR;
12647         if( thinkOutput[0] != NULLCHAR ) {
12648             int i;
12649
12650             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12651                 cpThinkOutput[i] = '.';
12652             }
12653             cpThinkOutput[i] = NULLCHAR;
12654             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12655         }
12656     }
12657
12658     if (moveNumber == forwardMostMove - 1 &&
12659         gameInfo.resultDetails != NULL) {
12660         if (gameInfo.resultDetails[0] == NULLCHAR) {
12661             sprintf(res, " %s", PGNResult(gameInfo.result));
12662         } else {
12663             sprintf(res, " {%s} %s",
12664                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12665         }
12666     } else {
12667         res[0] = NULLCHAR;
12668     }
12669
12670     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12671         DisplayMessage(res, cpThinkOutput);
12672     } else {
12673         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12674                 WhiteOnMove(moveNumber) ? " " : ".. ",
12675                 parseList[moveNumber], res);
12676         DisplayMessage(message, cpThinkOutput);
12677     }
12678 }
12679
12680 void
12681 DisplayAnalysisText(text)
12682      char *text;
12683 {
12684     char buf[MSG_SIZ];
12685
12686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12687                || appData.icsEngineAnalyze) {
12688         sprintf(buf, "Analysis (%s)", first.tidy);
12689         AnalysisPopUp(buf, text);
12690     }
12691 }
12692
12693 static int
12694 only_one_move(str)
12695      char *str;
12696 {
12697     while (*str && isspace(*str)) ++str;
12698     while (*str && !isspace(*str)) ++str;
12699     if (!*str) return 1;
12700     while (*str && isspace(*str)) ++str;
12701     if (!*str) return 1;
12702     return 0;
12703 }
12704
12705 void
12706 DisplayAnalysis()
12707 {
12708     char buf[MSG_SIZ];
12709     char lst[MSG_SIZ / 2];
12710     double nps;
12711     static char *xtra[] = { "", " (--)", " (++)" };
12712     int h, m, s, cs;
12713   
12714     if (programStats.time == 0) {
12715         programStats.time = 1;
12716     }
12717   
12718     if (programStats.got_only_move) {
12719         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12720     } else {
12721         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12722
12723         nps = (u64ToDouble(programStats.nodes) /
12724              ((double)programStats.time /100.0));
12725
12726         cs = programStats.time % 100;
12727         s = programStats.time / 100;
12728         h = (s / (60*60));
12729         s = s - h*60*60;
12730         m = (s/60);
12731         s = s - m*60;
12732
12733         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12734           if (programStats.move_name[0] != NULLCHAR) {
12735             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12736                     programStats.depth,
12737                     programStats.nr_moves-programStats.moves_left,
12738                     programStats.nr_moves, programStats.move_name,
12739                     ((float)programStats.score)/100.0, lst,
12740                     only_one_move(lst)?
12741                     xtra[programStats.got_fail] : "",
12742                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12743           } else {
12744             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12745                     programStats.depth,
12746                     programStats.nr_moves-programStats.moves_left,
12747                     programStats.nr_moves, ((float)programStats.score)/100.0,
12748                     lst,
12749                     only_one_move(lst)?
12750                     xtra[programStats.got_fail] : "",
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12752           }
12753         } else {
12754             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12755                     programStats.depth,
12756                     ((float)programStats.score)/100.0,
12757                     lst,
12758                     only_one_move(lst)?
12759                     xtra[programStats.got_fail] : "",
12760                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12761         }
12762     }
12763     DisplayAnalysisText(buf);
12764 }
12765
12766 void
12767 DisplayComment(moveNumber, text)
12768      int moveNumber;
12769      char *text;
12770 {
12771     char title[MSG_SIZ];
12772     char buf[8000]; // comment can be long!
12773     int score, depth;
12774
12775     if( appData.autoDisplayComment ) {
12776         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12777             strcpy(title, "Comment");
12778         } else {
12779             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12780                     WhiteOnMove(moveNumber) ? " " : ".. ",
12781                     parseList[moveNumber]);
12782         }
12783         // [HGM] PV info: display PV info together with (or as) comment
12784         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12785             if(text == NULL) text = "";                                           
12786             score = pvInfoList[moveNumber].score;
12787             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12788                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12789             text = buf;
12790         }
12791     } else title[0] = 0;
12792
12793     if (text != NULL)
12794         CommentPopUp(title, text);
12795 }
12796
12797 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12798  * might be busy thinking or pondering.  It can be omitted if your
12799  * gnuchess is configured to stop thinking immediately on any user
12800  * input.  However, that gnuchess feature depends on the FIONREAD
12801  * ioctl, which does not work properly on some flavors of Unix.
12802  */
12803 void
12804 Attention(cps)
12805      ChessProgramState *cps;
12806 {
12807 #if ATTENTION
12808     if (!cps->useSigint) return;
12809     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12810     switch (gameMode) {
12811       case MachinePlaysWhite:
12812       case MachinePlaysBlack:
12813       case TwoMachinesPlay:
12814       case IcsPlayingWhite:
12815       case IcsPlayingBlack:
12816       case AnalyzeMode:
12817       case AnalyzeFile:
12818         /* Skip if we know it isn't thinking */
12819         if (!cps->maybeThinking) return;
12820         if (appData.debugMode)
12821           fprintf(debugFP, "Interrupting %s\n", cps->which);
12822         InterruptChildProcess(cps->pr);
12823         cps->maybeThinking = FALSE;
12824         break;
12825       default:
12826         break;
12827     }
12828 #endif /*ATTENTION*/
12829 }
12830
12831 int
12832 CheckFlags()
12833 {
12834     if (whiteTimeRemaining <= 0) {
12835         if (!whiteFlag) {
12836             whiteFlag = TRUE;
12837             if (appData.icsActive) {
12838                 if (appData.autoCallFlag &&
12839                     gameMode == IcsPlayingBlack && !blackFlag) {
12840                   SendToICS(ics_prefix);
12841                   SendToICS("flag\n");
12842                 }
12843             } else {
12844                 if (blackFlag) {
12845                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12846                 } else {
12847                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12848                     if (appData.autoCallFlag) {
12849                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12850                         return TRUE;
12851                     }
12852                 }
12853             }
12854         }
12855     }
12856     if (blackTimeRemaining <= 0) {
12857         if (!blackFlag) {
12858             blackFlag = TRUE;
12859             if (appData.icsActive) {
12860                 if (appData.autoCallFlag &&
12861                     gameMode == IcsPlayingWhite && !whiteFlag) {
12862                   SendToICS(ics_prefix);
12863                   SendToICS("flag\n");
12864                 }
12865             } else {
12866                 if (whiteFlag) {
12867                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12868                 } else {
12869                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12870                     if (appData.autoCallFlag) {
12871                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12872                         return TRUE;
12873                     }
12874                 }
12875             }
12876         }
12877     }
12878     return FALSE;
12879 }
12880
12881 void
12882 CheckTimeControl()
12883 {
12884     if (!appData.clockMode || appData.icsActive ||
12885         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12886
12887     /*
12888      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12889      */
12890     if ( !WhiteOnMove(forwardMostMove) )
12891         /* White made time control */
12892         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12893         /* [HGM] time odds: correct new time quota for time odds! */
12894                                             / WhitePlayer()->timeOdds;
12895       else
12896         /* Black made time control */
12897         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12898                                             / WhitePlayer()->other->timeOdds;
12899 }
12900
12901 void
12902 DisplayBothClocks()
12903 {
12904     int wom = gameMode == EditPosition ?
12905       !blackPlaysFirst : WhiteOnMove(currentMove);
12906     DisplayWhiteClock(whiteTimeRemaining, wom);
12907     DisplayBlackClock(blackTimeRemaining, !wom);
12908 }
12909
12910
12911 /* Timekeeping seems to be a portability nightmare.  I think everyone
12912    has ftime(), but I'm really not sure, so I'm including some ifdefs
12913    to use other calls if you don't.  Clocks will be less accurate if
12914    you have neither ftime nor gettimeofday.
12915 */
12916
12917 /* VS 2008 requires the #include outside of the function */
12918 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12919 #include <sys/timeb.h>
12920 #endif
12921
12922 /* Get the current time as a TimeMark */
12923 void
12924 GetTimeMark(tm)
12925      TimeMark *tm;
12926 {
12927 #if HAVE_GETTIMEOFDAY
12928
12929     struct timeval timeVal;
12930     struct timezone timeZone;
12931
12932     gettimeofday(&timeVal, &timeZone);
12933     tm->sec = (long) timeVal.tv_sec; 
12934     tm->ms = (int) (timeVal.tv_usec / 1000L);
12935
12936 #else /*!HAVE_GETTIMEOFDAY*/
12937 #if HAVE_FTIME
12938
12939 // include <sys/timeb.h> / moved to just above start of function
12940     struct timeb timeB;
12941
12942     ftime(&timeB);
12943     tm->sec = (long) timeB.time;
12944     tm->ms = (int) timeB.millitm;
12945
12946 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12947     tm->sec = (long) time(NULL);
12948     tm->ms = 0;
12949 #endif
12950 #endif
12951 }
12952
12953 /* Return the difference in milliseconds between two
12954    time marks.  We assume the difference will fit in a long!
12955 */
12956 long
12957 SubtractTimeMarks(tm2, tm1)
12958      TimeMark *tm2, *tm1;
12959 {
12960     return 1000L*(tm2->sec - tm1->sec) +
12961            (long) (tm2->ms - tm1->ms);
12962 }
12963
12964
12965 /*
12966  * Code to manage the game clocks.
12967  *
12968  * In tournament play, black starts the clock and then white makes a move.
12969  * We give the human user a slight advantage if he is playing white---the
12970  * clocks don't run until he makes his first move, so it takes zero time.
12971  * Also, we don't account for network lag, so we could get out of sync
12972  * with GNU Chess's clock -- but then, referees are always right.  
12973  */
12974
12975 static TimeMark tickStartTM;
12976 static long intendedTickLength;
12977
12978 long
12979 NextTickLength(timeRemaining)
12980      long timeRemaining;
12981 {
12982     long nominalTickLength, nextTickLength;
12983
12984     if (timeRemaining > 0L && timeRemaining <= 10000L)
12985       nominalTickLength = 100L;
12986     else
12987       nominalTickLength = 1000L;
12988     nextTickLength = timeRemaining % nominalTickLength;
12989     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12990
12991     return nextTickLength;
12992 }
12993
12994 /* Adjust clock one minute up or down */
12995 void
12996 AdjustClock(Boolean which, int dir)
12997 {
12998     if(which) blackTimeRemaining += 60000*dir;
12999     else      whiteTimeRemaining += 60000*dir;
13000     DisplayBothClocks();
13001 }
13002
13003 /* Stop clocks and reset to a fresh time control */
13004 void
13005 ResetClocks() 
13006 {
13007     (void) StopClockTimer();
13008     if (appData.icsActive) {
13009         whiteTimeRemaining = blackTimeRemaining = 0;
13010     } else { /* [HGM] correct new time quote for time odds */
13011         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13012         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13013     }
13014     if (whiteFlag || blackFlag) {
13015         DisplayTitle("");
13016         whiteFlag = blackFlag = FALSE;
13017     }
13018     DisplayBothClocks();
13019 }
13020
13021 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13022
13023 /* Decrement running clock by amount of time that has passed */
13024 void
13025 DecrementClocks()
13026 {
13027     long timeRemaining;
13028     long lastTickLength, fudge;
13029     TimeMark now;
13030
13031     if (!appData.clockMode) return;
13032     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13033         
13034     GetTimeMark(&now);
13035
13036     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13037
13038     /* Fudge if we woke up a little too soon */
13039     fudge = intendedTickLength - lastTickLength;
13040     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13041
13042     if (WhiteOnMove(forwardMostMove)) {
13043         if(whiteNPS >= 0) lastTickLength = 0;
13044         timeRemaining = whiteTimeRemaining -= lastTickLength;
13045         DisplayWhiteClock(whiteTimeRemaining - fudge,
13046                           WhiteOnMove(currentMove));
13047     } else {
13048         if(blackNPS >= 0) lastTickLength = 0;
13049         timeRemaining = blackTimeRemaining -= lastTickLength;
13050         DisplayBlackClock(blackTimeRemaining - fudge,
13051                           !WhiteOnMove(currentMove));
13052     }
13053
13054     if (CheckFlags()) return;
13055         
13056     tickStartTM = now;
13057     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13058     StartClockTimer(intendedTickLength);
13059
13060     /* if the time remaining has fallen below the alarm threshold, sound the
13061      * alarm. if the alarm has sounded and (due to a takeback or time control
13062      * with increment) the time remaining has increased to a level above the
13063      * threshold, reset the alarm so it can sound again. 
13064      */
13065     
13066     if (appData.icsActive && appData.icsAlarm) {
13067
13068         /* make sure we are dealing with the user's clock */
13069         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13070                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13071            )) return;
13072
13073         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13074             alarmSounded = FALSE;
13075         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13076             PlayAlarmSound();
13077             alarmSounded = TRUE;
13078         }
13079     }
13080 }
13081
13082
13083 /* A player has just moved, so stop the previously running
13084    clock and (if in clock mode) start the other one.
13085    We redisplay both clocks in case we're in ICS mode, because
13086    ICS gives us an update to both clocks after every move.
13087    Note that this routine is called *after* forwardMostMove
13088    is updated, so the last fractional tick must be subtracted
13089    from the color that is *not* on move now.
13090 */
13091 void
13092 SwitchClocks()
13093 {
13094     long lastTickLength;
13095     TimeMark now;
13096     int flagged = FALSE;
13097
13098     GetTimeMark(&now);
13099
13100     if (StopClockTimer() && appData.clockMode) {
13101         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13102         if (WhiteOnMove(forwardMostMove)) {
13103             if(blackNPS >= 0) lastTickLength = 0;
13104             blackTimeRemaining -= lastTickLength;
13105            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13106 //         if(pvInfoList[forwardMostMove-1].time == -1)
13107                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13108                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13109         } else {
13110            if(whiteNPS >= 0) lastTickLength = 0;
13111            whiteTimeRemaining -= lastTickLength;
13112            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13113 //         if(pvInfoList[forwardMostMove-1].time == -1)
13114                  pvInfoList[forwardMostMove-1].time = 
13115                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13116         }
13117         flagged = CheckFlags();
13118     }
13119     CheckTimeControl();
13120
13121     if (flagged || !appData.clockMode) return;
13122
13123     switch (gameMode) {
13124       case MachinePlaysBlack:
13125       case MachinePlaysWhite:
13126       case BeginningOfGame:
13127         if (pausing) return;
13128         break;
13129
13130       case EditGame:
13131       case PlayFromGameFile:
13132       case IcsExamining:
13133         return;
13134
13135       default:
13136         break;
13137     }
13138
13139     tickStartTM = now;
13140     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13141       whiteTimeRemaining : blackTimeRemaining);
13142     StartClockTimer(intendedTickLength);
13143 }
13144         
13145
13146 /* Stop both clocks */
13147 void
13148 StopClocks()
13149 {       
13150     long lastTickLength;
13151     TimeMark now;
13152
13153     if (!StopClockTimer()) return;
13154     if (!appData.clockMode) return;
13155
13156     printf("Debug: in stop clocks\n");
13157
13158     GetTimeMark(&now);
13159
13160     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13161     if (WhiteOnMove(forwardMostMove)) {
13162         if(whiteNPS >= 0) lastTickLength = 0;
13163         whiteTimeRemaining -= lastTickLength;
13164         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13165     } else {
13166         if(blackNPS >= 0) lastTickLength = 0;
13167         blackTimeRemaining -= lastTickLength;
13168         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13169     }
13170     CheckFlags();
13171     printf("Debug: end stop clocks\n");
13172 }
13173         
13174 /* Start clock of player on move.  Time may have been reset, so
13175    if clock is already running, stop and restart it. */
13176 void
13177 StartClocks()
13178 {
13179     (void) StopClockTimer(); /* in case it was running already */
13180     DisplayBothClocks();
13181     if (CheckFlags()) return;
13182
13183     if (!appData.clockMode) return;
13184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13185
13186     GetTimeMark(&tickStartTM);
13187     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13188       whiteTimeRemaining : blackTimeRemaining);
13189
13190    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13191     whiteNPS = blackNPS = -1; 
13192     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13193        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13194         whiteNPS = first.nps;
13195     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13196        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13197         blackNPS = first.nps;
13198     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13199         whiteNPS = second.nps;
13200     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13201         blackNPS = second.nps;
13202     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13203
13204     StartClockTimer(intendedTickLength);
13205 }
13206
13207 char *
13208 TimeString(ms)
13209      long ms;
13210 {
13211     long second, minute, hour, day;
13212     char *sign = "";
13213     static char buf[32];
13214     
13215     if (ms > 0 && ms <= 9900) {
13216       /* convert milliseconds to tenths, rounding up */
13217       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13218
13219       sprintf(buf, " %03.1f ", tenths/10.0);
13220       return buf;
13221     }
13222
13223     /* convert milliseconds to seconds, rounding up */
13224     /* use floating point to avoid strangeness of integer division
13225        with negative dividends on many machines */
13226     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13227
13228     if (second < 0) {
13229         sign = "-";
13230         second = -second;
13231     }
13232     
13233     day = second / (60 * 60 * 24);
13234     second = second % (60 * 60 * 24);
13235     hour = second / (60 * 60);
13236     second = second % (60 * 60);
13237     minute = second / 60;
13238     second = second % 60;
13239     
13240     if (day > 0)
13241       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13242               sign, day, hour, minute, second);
13243     else if (hour > 0)
13244       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13245     else
13246       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13247     
13248     return buf;
13249 }
13250
13251
13252 /*
13253  * This is necessary because some C libraries aren't ANSI C compliant yet.
13254  */
13255 char *
13256 StrStr(string, match)
13257      char *string, *match;
13258 {
13259     int i, length;
13260     
13261     length = strlen(match);
13262     
13263     for (i = strlen(string) - length; i >= 0; i--, string++)
13264       if (!strncmp(match, string, length))
13265         return string;
13266     
13267     return NULL;
13268 }
13269
13270 char *
13271 StrCaseStr(string, match)
13272      char *string, *match;
13273 {
13274     int i, j, length;
13275     
13276     length = strlen(match);
13277     
13278     for (i = strlen(string) - length; i >= 0; i--, string++) {
13279         for (j = 0; j < length; j++) {
13280             if (ToLower(match[j]) != ToLower(string[j]))
13281               break;
13282         }
13283         if (j == length) return string;
13284     }
13285
13286     return NULL;
13287 }
13288
13289 #ifndef _amigados
13290 int
13291 StrCaseCmp(s1, s2)
13292      char *s1, *s2;
13293 {
13294     char c1, c2;
13295     
13296     for (;;) {
13297         c1 = ToLower(*s1++);
13298         c2 = ToLower(*s2++);
13299         if (c1 > c2) return 1;
13300         if (c1 < c2) return -1;
13301         if (c1 == NULLCHAR) return 0;
13302     }
13303 }
13304
13305
13306 int
13307 ToLower(c)
13308      int c;
13309 {
13310     return isupper(c) ? tolower(c) : c;
13311 }
13312
13313
13314 int
13315 ToUpper(c)
13316      int c;
13317 {
13318     return islower(c) ? toupper(c) : c;
13319 }
13320 #endif /* !_amigados    */
13321
13322 char *
13323 StrSave(s)
13324      char *s;
13325 {
13326     char *ret;
13327
13328     if ((ret = (char *) malloc(strlen(s) + 1))) {
13329         strcpy(ret, s);
13330     }
13331     return ret;
13332 }
13333
13334 char *
13335 StrSavePtr(s, savePtr)
13336      char *s, **savePtr;
13337 {
13338     if (*savePtr) {
13339         free(*savePtr);
13340     }
13341     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13342         strcpy(*savePtr, s);
13343     }
13344     return(*savePtr);
13345 }
13346
13347 char *
13348 PGNDate()
13349 {
13350     time_t clock;
13351     struct tm *tm;
13352     char buf[MSG_SIZ];
13353
13354     clock = time((time_t *)NULL);
13355     tm = localtime(&clock);
13356     sprintf(buf, "%04d.%02d.%02d",
13357             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13358     return StrSave(buf);
13359 }
13360
13361
13362 char *
13363 PositionToFEN(move, overrideCastling)
13364      int move;
13365      char *overrideCastling;
13366 {
13367     int i, j, fromX, fromY, toX, toY;
13368     int whiteToPlay;
13369     char buf[128];
13370     char *p, *q;
13371     int emptycount;
13372     ChessSquare piece;
13373
13374     whiteToPlay = (gameMode == EditPosition) ?
13375       !blackPlaysFirst : (move % 2 == 0);
13376     p = buf;
13377
13378     /* Piece placement data */
13379     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13380         emptycount = 0;
13381         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13382             if (boards[move][i][j] == EmptySquare) {
13383                 emptycount++;
13384             } else { ChessSquare piece = boards[move][i][j];
13385                 if (emptycount > 0) {
13386                     if(emptycount<10) /* [HGM] can be >= 10 */
13387                         *p++ = '0' + emptycount;
13388                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13389                     emptycount = 0;
13390                 }
13391                 if(PieceToChar(piece) == '+') {
13392                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13393                     *p++ = '+';
13394                     piece = (ChessSquare)(DEMOTED piece);
13395                 } 
13396                 *p++ = PieceToChar(piece);
13397                 if(p[-1] == '~') {
13398                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13399                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13400                     *p++ = '~';
13401                 }
13402             }
13403         }
13404         if (emptycount > 0) {
13405             if(emptycount<10) /* [HGM] can be >= 10 */
13406                 *p++ = '0' + emptycount;
13407             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13408             emptycount = 0;
13409         }
13410         *p++ = '/';
13411     }
13412     *(p - 1) = ' ';
13413
13414     /* [HGM] print Crazyhouse or Shogi holdings */
13415     if( gameInfo.holdingsWidth ) {
13416         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13417         q = p;
13418         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13419             piece = boards[move][i][BOARD_WIDTH-1];
13420             if( piece != EmptySquare )
13421               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13422                   *p++ = PieceToChar(piece);
13423         }
13424         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13425             piece = boards[move][BOARD_HEIGHT-i-1][0];
13426             if( piece != EmptySquare )
13427               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13428                   *p++ = PieceToChar(piece);
13429         }
13430
13431         if( q == p ) *p++ = '-';
13432         *p++ = ']';
13433         *p++ = ' ';
13434     }
13435
13436     /* Active color */
13437     *p++ = whiteToPlay ? 'w' : 'b';
13438     *p++ = ' ';
13439
13440   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13441     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13442   } else {
13443   if(nrCastlingRights) {
13444      q = p;
13445      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13446        /* [HGM] write directly from rights */
13447            if(castlingRights[move][2] >= 0 &&
13448               castlingRights[move][0] >= 0   )
13449                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13450            if(castlingRights[move][2] >= 0 &&
13451               castlingRights[move][1] >= 0   )
13452                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13453            if(castlingRights[move][5] >= 0 &&
13454               castlingRights[move][3] >= 0   )
13455                 *p++ = castlingRights[move][3] + AAA;
13456            if(castlingRights[move][5] >= 0 &&
13457               castlingRights[move][4] >= 0   )
13458                 *p++ = castlingRights[move][4] + AAA;
13459      } else {
13460
13461         /* [HGM] write true castling rights */
13462         if( nrCastlingRights == 6 ) {
13463             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13464                castlingRights[move][2] >= 0  ) *p++ = 'K';
13465             if(castlingRights[move][1] == BOARD_LEFT &&
13466                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13467             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13468                castlingRights[move][5] >= 0  ) *p++ = 'k';
13469             if(castlingRights[move][4] == BOARD_LEFT &&
13470                castlingRights[move][5] >= 0  ) *p++ = 'q';
13471         }
13472      }
13473      if (q == p) *p++ = '-'; /* No castling rights */
13474      *p++ = ' ';
13475   }
13476
13477   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13478      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13479     /* En passant target square */
13480     if (move > backwardMostMove) {
13481         fromX = moveList[move - 1][0] - AAA;
13482         fromY = moveList[move - 1][1] - ONE;
13483         toX = moveList[move - 1][2] - AAA;
13484         toY = moveList[move - 1][3] - ONE;
13485         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13486             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13487             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13488             fromX == toX) {
13489             /* 2-square pawn move just happened */
13490             *p++ = toX + AAA;
13491             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13492         } else {
13493             *p++ = '-';
13494         }
13495     } else {
13496         *p++ = '-';
13497     }
13498     *p++ = ' ';
13499   }
13500   }
13501
13502     /* [HGM] find reversible plies */
13503     {   int i = 0, j=move;
13504
13505         if (appData.debugMode) { int k;
13506             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13507             for(k=backwardMostMove; k<=forwardMostMove; k++)
13508                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13509
13510         }
13511
13512         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13513         if( j == backwardMostMove ) i += initialRulePlies;
13514         sprintf(p, "%d ", i);
13515         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13516     }
13517     /* Fullmove number */
13518     sprintf(p, "%d", (move / 2) + 1);
13519     
13520     return StrSave(buf);
13521 }
13522
13523 Boolean
13524 ParseFEN(board, blackPlaysFirst, fen)
13525     Board board;
13526      int *blackPlaysFirst;
13527      char *fen;
13528 {
13529     int i, j;
13530     char *p;
13531     int emptycount;
13532     ChessSquare piece;
13533
13534     p = fen;
13535
13536     /* [HGM] by default clear Crazyhouse holdings, if present */
13537     if(gameInfo.holdingsWidth) {
13538        for(i=0; i<BOARD_HEIGHT; i++) {
13539            board[i][0]             = EmptySquare; /* black holdings */
13540            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13541            board[i][1]             = (ChessSquare) 0; /* black counts */
13542            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13543        }
13544     }
13545
13546     /* Piece placement data */
13547     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13548         j = 0;
13549         for (;;) {
13550             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13551                 if (*p == '/') p++;
13552                 emptycount = gameInfo.boardWidth - j;
13553                 while (emptycount--)
13554                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13555                 break;
13556 #if(BOARD_SIZE >= 10)
13557             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13558                 p++; emptycount=10;
13559                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13560                 while (emptycount--)
13561                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13562 #endif
13563             } else if (isdigit(*p)) {
13564                 emptycount = *p++ - '0';
13565                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13566                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13567                 while (emptycount--)
13568                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13569             } else if (*p == '+' || isalpha(*p)) {
13570                 if (j >= gameInfo.boardWidth) return FALSE;
13571                 if(*p=='+') {
13572                     piece = CharToPiece(*++p);
13573                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13574                     piece = (ChessSquare) (PROMOTED piece ); p++;
13575                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13576                 } else piece = CharToPiece(*p++);
13577
13578                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13579                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13580                     piece = (ChessSquare) (PROMOTED piece);
13581                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13582                     p++;
13583                 }
13584                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13585             } else {
13586                 return FALSE;
13587             }
13588         }
13589     }
13590     while (*p == '/' || *p == ' ') p++;
13591
13592     /* [HGM] look for Crazyhouse holdings here */
13593     while(*p==' ') p++;
13594     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13595         if(*p == '[') p++;
13596         if(*p == '-' ) *p++; /* empty holdings */ else {
13597             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13598             /* if we would allow FEN reading to set board size, we would   */
13599             /* have to add holdings and shift the board read so far here   */
13600             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13601                 *p++;
13602                 if((int) piece >= (int) BlackPawn ) {
13603                     i = (int)piece - (int)BlackPawn;
13604                     i = PieceToNumber((ChessSquare)i);
13605                     if( i >= gameInfo.holdingsSize ) return FALSE;
13606                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13607                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13608                 } else {
13609                     i = (int)piece - (int)WhitePawn;
13610                     i = PieceToNumber((ChessSquare)i);
13611                     if( i >= gameInfo.holdingsSize ) return FALSE;
13612                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13613                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13614                 }
13615             }
13616         }
13617         if(*p == ']') *p++;
13618     }
13619
13620     while(*p == ' ') p++;
13621
13622     /* Active color */
13623     switch (*p++) {
13624       case 'w':
13625         *blackPlaysFirst = FALSE;
13626         break;
13627       case 'b': 
13628         *blackPlaysFirst = TRUE;
13629         break;
13630       default:
13631         return FALSE;
13632     }
13633
13634     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13635     /* return the extra info in global variiables             */
13636
13637     /* set defaults in case FEN is incomplete */
13638     FENepStatus = EP_UNKNOWN;
13639     for(i=0; i<nrCastlingRights; i++ ) {
13640         FENcastlingRights[i] =
13641             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13642     }   /* assume possible unless obviously impossible */
13643     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13644     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13645     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13646     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13647     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13648     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13649     FENrulePlies = 0;
13650
13651     while(*p==' ') p++;
13652     if(nrCastlingRights) {
13653       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13654           /* castling indicator present, so default becomes no castlings */
13655           for(i=0; i<nrCastlingRights; i++ ) {
13656                  FENcastlingRights[i] = -1;
13657           }
13658       }
13659       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13660              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13661              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13662              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13663         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13664
13665         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13666             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13667             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13668         }
13669         switch(c) {
13670           case'K':
13671               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13672               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13673               FENcastlingRights[2] = whiteKingFile;
13674               break;
13675           case'Q':
13676               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13677               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13678               FENcastlingRights[2] = whiteKingFile;
13679               break;
13680           case'k':
13681               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13682               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13683               FENcastlingRights[5] = blackKingFile;
13684               break;
13685           case'q':
13686               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13687               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13688               FENcastlingRights[5] = blackKingFile;
13689           case '-':
13690               break;
13691           default: /* FRC castlings */
13692               if(c >= 'a') { /* black rights */
13693                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13694                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13695                   if(i == BOARD_RGHT) break;
13696                   FENcastlingRights[5] = i;
13697                   c -= AAA;
13698                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13699                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13700                   if(c > i)
13701                       FENcastlingRights[3] = c;
13702                   else
13703                       FENcastlingRights[4] = c;
13704               } else { /* white rights */
13705                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13706                     if(board[0][i] == WhiteKing) break;
13707                   if(i == BOARD_RGHT) break;
13708                   FENcastlingRights[2] = i;
13709                   c -= AAA - 'a' + 'A';
13710                   if(board[0][c] >= WhiteKing) break;
13711                   if(c > i)
13712                       FENcastlingRights[0] = c;
13713                   else
13714                       FENcastlingRights[1] = c;
13715               }
13716         }
13717       }
13718     if (appData.debugMode) {
13719         fprintf(debugFP, "FEN castling rights:");
13720         for(i=0; i<nrCastlingRights; i++)
13721         fprintf(debugFP, " %d", FENcastlingRights[i]);
13722         fprintf(debugFP, "\n");
13723     }
13724
13725       while(*p==' ') p++;
13726     }
13727
13728     /* read e.p. field in games that know e.p. capture */
13729     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13730        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13731       if(*p=='-') {
13732         p++; FENepStatus = EP_NONE;
13733       } else {
13734          char c = *p++ - AAA;
13735
13736          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13737          if(*p >= '0' && *p <='9') *p++;
13738          FENepStatus = c;
13739       }
13740     }
13741
13742
13743     if(sscanf(p, "%d", &i) == 1) {
13744         FENrulePlies = i; /* 50-move ply counter */
13745         /* (The move number is still ignored)    */
13746     }
13747
13748     return TRUE;
13749 }
13750       
13751 void
13752 EditPositionPasteFEN(char *fen)
13753 {
13754   if (fen != NULL) {
13755     Board initial_position;
13756
13757     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13758       DisplayError(_("Bad FEN position in clipboard"), 0);
13759       return ;
13760     } else {
13761       int savedBlackPlaysFirst = blackPlaysFirst;
13762       EditPositionEvent();
13763       blackPlaysFirst = savedBlackPlaysFirst;
13764       CopyBoard(boards[0], initial_position);
13765           /* [HGM] copy FEN attributes as well */
13766           {   int i;
13767               initialRulePlies = FENrulePlies;
13768               epStatus[0] = FENepStatus;
13769               for( i=0; i<nrCastlingRights; i++ )
13770                   castlingRights[0][i] = FENcastlingRights[i];
13771           }
13772       EditPositionDone();
13773       DisplayBothClocks();
13774       DrawPosition(FALSE, boards[currentMove]);
13775     }
13776   }
13777 }