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