updated xboard.texi: added warning that pixmaps are not build for all variants
[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                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2115         }
2116
2117         buf[buf_len] = NULLCHAR;
2118         next_out = leftover_len;
2119         leftover_start = 0;
2120         
2121         i = 0;
2122         while (i < buf_len) {
2123             /* Deal with part of the TELNET option negotiation
2124                protocol.  We refuse to do anything beyond the
2125                defaults, except that we allow the WILL ECHO option,
2126                which ICS uses to turn off password echoing when we are
2127                directly connected to it.  We reject this option
2128                if localLineEditing mode is on (always on in xboard)
2129                and we are talking to port 23, which might be a real
2130                telnet server that will try to keep WILL ECHO on permanently.
2131              */
2132             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2133                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2134                 unsigned char option;
2135                 oldi = i;
2136                 switch ((unsigned char) buf[++i]) {
2137                   case TN_WILL:
2138                     if (appData.debugMode)
2139                       fprintf(debugFP, "\n<WILL ");
2140                     switch (option = (unsigned char) buf[++i]) {
2141                       case TN_ECHO:
2142                         if (appData.debugMode)
2143                           fprintf(debugFP, "ECHO ");
2144                         /* Reply only if this is a change, according
2145                            to the protocol rules. */
2146                         if (remoteEchoOption) break;
2147                         if (appData.localLineEditing &&
2148                             atoi(appData.icsPort) == TN_PORT) {
2149                             TelnetRequest(TN_DONT, TN_ECHO);
2150                         } else {
2151                             EchoOff();
2152                             TelnetRequest(TN_DO, TN_ECHO);
2153                             remoteEchoOption = TRUE;
2154                         }
2155                         break;
2156                       default:
2157                         if (appData.debugMode)
2158                           fprintf(debugFP, "%d ", option);
2159                         /* Whatever this is, we don't want it. */
2160                         TelnetRequest(TN_DONT, option);
2161                         break;
2162                     }
2163                     break;
2164                   case TN_WONT:
2165                     if (appData.debugMode)
2166                       fprintf(debugFP, "\n<WONT ");
2167                     switch (option = (unsigned char) buf[++i]) {
2168                       case TN_ECHO:
2169                         if (appData.debugMode)
2170                           fprintf(debugFP, "ECHO ");
2171                         /* Reply only if this is a change, according
2172                            to the protocol rules. */
2173                         if (!remoteEchoOption) break;
2174                         EchoOn();
2175                         TelnetRequest(TN_DONT, TN_ECHO);
2176                         remoteEchoOption = FALSE;
2177                         break;
2178                       default:
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", (unsigned char) option);
2181                         /* Whatever this is, it must already be turned
2182                            off, because we never agree to turn on
2183                            anything non-default, so according to the
2184                            protocol rules, we don't reply. */
2185                         break;
2186                     }
2187                     break;
2188                   case TN_DO:
2189                     if (appData.debugMode)
2190                       fprintf(debugFP, "\n<DO ");
2191                     switch (option = (unsigned char) buf[++i]) {
2192                       default:
2193                         /* Whatever this is, we refuse to do it. */
2194                         if (appData.debugMode)
2195                           fprintf(debugFP, "%d ", option);
2196                         TelnetRequest(TN_WONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_DONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<DONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       default:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "%d ", option);
2207                         /* Whatever this is, we are already not doing
2208                            it, because we never agree to do anything
2209                            non-default, so according to the protocol
2210                            rules, we don't reply. */
2211                         break;
2212                     }
2213                     break;
2214                   case TN_IAC:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<IAC ");
2217                     /* Doubled IAC; pass it through */
2218                     i--;
2219                     break;
2220                   default:
2221                     if (appData.debugMode)
2222                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2223                     /* Drop all other telnet commands on the floor */
2224                     break;
2225                 }
2226                 if (oldi > next_out)
2227                   SendToPlayer(&buf[next_out], oldi - next_out);
2228                 if (++i > next_out)
2229                   next_out = i;
2230                 continue;
2231             }
2232                 
2233             /* OK, this at least will *usually* work */
2234             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2235                 loggedOn = TRUE;
2236             }
2237             
2238             if (loggedOn && !intfSet) {
2239                 if (ics_type == ICS_ICC) {
2240                   sprintf(str,
2241                           "/set-quietly interface %s\n/set-quietly style 12\n",
2242                           programVersion);
2243
2244                 } else if (ics_type == ICS_CHESSNET) {
2245                   sprintf(str, "/style 12\n");
2246                 } else {
2247                   strcpy(str, "alias $ @\n$set interface ");
2248                   strcat(str, programVersion);
2249                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2250 #ifdef WIN32
2251                   strcat(str, "$iset nohighlight 1\n");
2252 #endif
2253                   strcat(str, "$iset lock 1\n$style 12\n");
2254                 }
2255                 SendToICS(str);
2256                 intfSet = TRUE;
2257             }
2258
2259             if (started == STARTED_COMMENT) {
2260                 /* Accumulate characters in comment */
2261                 parse[parse_pos++] = buf[i];
2262                 if (buf[i] == '\n') {
2263                     parse[parse_pos] = NULLCHAR;
2264                     if(!suppressKibitz) // [HGM] kibitz
2265                         AppendComment(forwardMostMove, StripHighlight(parse));
2266                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2267                         int nrDigit = 0, nrAlph = 0, i;
2268                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2269                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2270                         parse[parse_pos] = NULLCHAR;
2271                         // try to be smart: if it does not look like search info, it should go to
2272                         // ICS interaction window after all, not to engine-output window.
2273                         for(i=0; i<parse_pos; i++) { // count letters and digits
2274                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2275                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2276                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2277                         }
2278                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2279                             int depth=0; float score;
2280                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2281                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2282                                 pvInfoList[forwardMostMove-1].depth = depth;
2283                                 pvInfoList[forwardMostMove-1].score = 100*score;
2284                             }
2285                             OutputKibitz(suppressKibitz, parse);
2286                         } else {
2287                             char tmp[MSG_SIZ];
2288                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2289                             SendToPlayer(tmp, strlen(tmp));
2290                         }
2291                     }
2292                     started = STARTED_NONE;
2293                 } else {
2294                     /* Don't match patterns against characters in chatter */
2295                     i++;
2296                     continue;
2297                 }
2298             }
2299             if (started == STARTED_CHATTER) {
2300                 if (buf[i] != '\n') {
2301                     /* Don't match patterns against characters in chatter */
2302                     i++;
2303                     continue;
2304                 }
2305                 started = STARTED_NONE;
2306             }
2307
2308             /* Kludge to deal with rcmd protocol */
2309             if (firstTime && looking_at(buf, &i, "\001*")) {
2310                 DisplayFatalError(&buf[1], 0, 1);
2311                 continue;
2312             } else {
2313                 firstTime = FALSE;
2314             }
2315
2316             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2317                 ics_type = ICS_ICC;
2318                 ics_prefix = "/";
2319                 if (appData.debugMode)
2320                   fprintf(debugFP, "ics_type %d\n", ics_type);
2321                 continue;
2322             }
2323             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2324                 ics_type = ICS_FICS;
2325                 ics_prefix = "$";
2326                 if (appData.debugMode)
2327                   fprintf(debugFP, "ics_type %d\n", ics_type);
2328                 continue;
2329             }
2330             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2331                 ics_type = ICS_CHESSNET;
2332                 ics_prefix = "/";
2333                 if (appData.debugMode)
2334                   fprintf(debugFP, "ics_type %d\n", ics_type);
2335                 continue;
2336             }
2337
2338             if (!loggedOn &&
2339                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2340                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2341                  looking_at(buf, &i, "will be \"*\""))) {
2342               strcpy(ics_handle, star_match[0]);
2343               continue;
2344             }
2345
2346             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2347               char buf[MSG_SIZ];
2348               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2349               DisplayIcsInteractionTitle(buf);
2350               have_set_title = TRUE;
2351             }
2352
2353             /* skip finger notes */
2354             if (started == STARTED_NONE &&
2355                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2356                  (buf[i] == '1' && buf[i+1] == '0')) &&
2357                 buf[i+2] == ':' && buf[i+3] == ' ') {
2358               started = STARTED_CHATTER;
2359               i += 3;
2360               continue;
2361             }
2362
2363             /* skip formula vars */
2364             if (started == STARTED_NONE &&
2365                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2366               started = STARTED_CHATTER;
2367               i += 3;
2368               continue;
2369             }
2370
2371             oldi = i;
2372             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2373             if (appData.autoKibitz && started == STARTED_NONE && 
2374                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2375                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2376                 if(looking_at(buf, &i, "* kibitzes: ") &&
2377                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2378                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2379                         suppressKibitz = TRUE;
2380                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2381                                 && (gameMode == IcsPlayingWhite)) ||
2382                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2383                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2384                             started = STARTED_CHATTER; // own kibitz we simply discard
2385                         else {
2386                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2387                             parse_pos = 0; parse[0] = NULLCHAR;
2388                             savingComment = TRUE;
2389                             suppressKibitz = gameMode != IcsObserving ? 2 :
2390                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2391                         } 
2392                         continue;
2393                 } else
2394                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2395                     started = STARTED_CHATTER;
2396                     suppressKibitz = TRUE;
2397                 }
2398             } // [HGM] kibitz: end of patch
2399
2400             if (appData.zippyTalk || appData.zippyPlay) {
2401                 /* [DM] Backup address for color zippy lines */
2402                 backup = i;
2403 #if ZIPPY
2404        #ifdef WIN32
2405                if (loggedOn == TRUE)
2406                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2407                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2408        #else
2409                 if (ZippyControl(buf, &i) ||
2410                     ZippyConverse(buf, &i) ||
2411                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2412                       loggedOn = TRUE;
2413                       if (!appData.colorize) continue;
2414                 }
2415        #endif
2416 #endif
2417             } // [DM] 'else { ' deleted
2418                 if (/* Don't color "message" or "messages" output */
2419                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2420                     looking_at(buf, &i, "*. * at *:*: ") ||
2421                     looking_at(buf, &i, "--* (*:*): ") ||
2422                     /* Regular tells and says */
2423                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2424                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2425                     looking_at(buf, &i, "* says: ") ||
2426                     /* Message notifications (same color as tells) */
2427                     looking_at(buf, &i, "* has left a message ") ||
2428                     looking_at(buf, &i, "* just sent you a message:\n") ||
2429                     /* Whispers and kibitzes */
2430                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2431                     looking_at(buf, &i, "* kibitzes: ") ||
2432                     /* Channel tells */
2433                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2434
2435                   if (tkind == 1 && strchr(star_match[0], ':')) {
2436                       /* Avoid "tells you:" spoofs in channels */
2437                      tkind = 3;
2438                   }
2439                   if (star_match[0][0] == NULLCHAR ||
2440                       strchr(star_match[0], ' ') ||
2441                       (tkind == 3 && strchr(star_match[1], ' '))) {
2442                     /* Reject bogus matches */
2443                     i = oldi;
2444                   } else {
2445                     if (appData.colorize) {
2446                       if (oldi > next_out) {
2447                         SendToPlayer(&buf[next_out], oldi - next_out);
2448                         next_out = oldi;
2449                       }
2450                       switch (tkind) {
2451                       case 1:
2452                         Colorize(ColorTell, FALSE);
2453                         curColor = ColorTell;
2454                         break;
2455                       case 2:
2456                         Colorize(ColorKibitz, FALSE);
2457                         curColor = ColorKibitz;
2458                         break;
2459                       case 3:
2460                         p = strrchr(star_match[1], '(');
2461                         if (p == NULL) {
2462                           p = star_match[1];
2463                         } else {
2464                           p++;
2465                         }
2466                         if (atoi(p) == 1) {
2467                           Colorize(ColorChannel1, FALSE);
2468                           curColor = ColorChannel1;
2469                         } else {
2470                           Colorize(ColorChannel, FALSE);
2471                           curColor = ColorChannel;
2472                         }
2473                         break;
2474                       case 5:
2475                         curColor = ColorNormal;
2476                         break;
2477                       }
2478                     }
2479                     if (started == STARTED_NONE && appData.autoComment &&
2480                         (gameMode == IcsObserving ||
2481                          gameMode == IcsPlayingWhite ||
2482                          gameMode == IcsPlayingBlack)) {
2483                       parse_pos = i - oldi;
2484                       memcpy(parse, &buf[oldi], parse_pos);
2485                       parse[parse_pos] = NULLCHAR;
2486                       started = STARTED_COMMENT;
2487                       savingComment = TRUE;
2488                     } else {
2489                       started = STARTED_CHATTER;
2490                       savingComment = FALSE;
2491                     }
2492                     loggedOn = TRUE;
2493                     continue;
2494                   }
2495                 }
2496
2497                 if (looking_at(buf, &i, "* s-shouts: ") ||
2498                     looking_at(buf, &i, "* c-shouts: ")) {
2499                     if (appData.colorize) {
2500                         if (oldi > next_out) {
2501                             SendToPlayer(&buf[next_out], oldi - next_out);
2502                             next_out = oldi;
2503                         }
2504                         Colorize(ColorSShout, FALSE);
2505                         curColor = ColorSShout;
2506                     }
2507                     loggedOn = TRUE;
2508                     started = STARTED_CHATTER;
2509                     continue;
2510                 }
2511
2512                 if (looking_at(buf, &i, "--->")) {
2513                     loggedOn = TRUE;
2514                     continue;
2515                 }
2516
2517                 if (looking_at(buf, &i, "* shouts: ") ||
2518                     looking_at(buf, &i, "--> ")) {
2519                     if (appData.colorize) {
2520                         if (oldi > next_out) {
2521                             SendToPlayer(&buf[next_out], oldi - next_out);
2522                             next_out = oldi;
2523                         }
2524                         Colorize(ColorShout, FALSE);
2525                         curColor = ColorShout;
2526                     }
2527                     loggedOn = TRUE;
2528                     started = STARTED_CHATTER;
2529                     continue;
2530                 }
2531
2532                 if (looking_at( buf, &i, "Challenge:")) {
2533                     if (appData.colorize) {
2534                         if (oldi > next_out) {
2535                             SendToPlayer(&buf[next_out], oldi - next_out);
2536                             next_out = oldi;
2537                         }
2538                         Colorize(ColorChallenge, FALSE);
2539                         curColor = ColorChallenge;
2540                     }
2541                     loggedOn = TRUE;
2542                     continue;
2543                 }
2544
2545                 if (looking_at(buf, &i, "* offers you") ||
2546                     looking_at(buf, &i, "* offers to be") ||
2547                     looking_at(buf, &i, "* would like to") ||
2548                     looking_at(buf, &i, "* requests to") ||
2549                     looking_at(buf, &i, "Your opponent offers") ||
2550                     looking_at(buf, &i, "Your opponent requests")) {
2551
2552                     if (appData.colorize) {
2553                         if (oldi > next_out) {
2554                             SendToPlayer(&buf[next_out], oldi - next_out);
2555                             next_out = oldi;
2556                         }
2557                         Colorize(ColorRequest, FALSE);
2558                         curColor = ColorRequest;
2559                     }
2560                     continue;
2561                 }
2562
2563                 if (looking_at(buf, &i, "* (*) seeking")) {
2564                     if (appData.colorize) {
2565                         if (oldi > next_out) {
2566                             SendToPlayer(&buf[next_out], oldi - next_out);
2567                             next_out = oldi;
2568                         }
2569                         Colorize(ColorSeek, FALSE);
2570                         curColor = ColorSeek;
2571                     }
2572                     continue;
2573             }
2574
2575             if (looking_at(buf, &i, "\\   ")) {
2576                 if (prevColor != ColorNormal) {
2577                     if (oldi > next_out) {
2578                         SendToPlayer(&buf[next_out], oldi - next_out);
2579                         next_out = oldi;
2580                     }
2581                     Colorize(prevColor, TRUE);
2582                     curColor = prevColor;
2583                 }
2584                 if (savingComment) {
2585                     parse_pos = i - oldi;
2586                     memcpy(parse, &buf[oldi], parse_pos);
2587                     parse[parse_pos] = NULLCHAR;
2588                     started = STARTED_COMMENT;
2589                 } else {
2590                     started = STARTED_CHATTER;
2591                 }
2592                 continue;
2593             }
2594
2595             if (looking_at(buf, &i, "Black Strength :") ||
2596                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2597                 looking_at(buf, &i, "<10>") ||
2598                 looking_at(buf, &i, "#@#")) {
2599                 /* Wrong board style */
2600                 loggedOn = TRUE;
2601                 SendToICS(ics_prefix);
2602                 SendToICS("set style 12\n");
2603                 SendToICS(ics_prefix);
2604                 SendToICS("refresh\n");
2605                 continue;
2606             }
2607             
2608             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2609                 ICSInitScript();
2610                 have_sent_ICS_logon = 1;
2611                 continue;
2612             }
2613               
2614             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2615                 (looking_at(buf, &i, "\n<12> ") ||
2616                  looking_at(buf, &i, "<12> "))) {
2617                 loggedOn = TRUE;
2618                 if (oldi > next_out) {
2619                     SendToPlayer(&buf[next_out], oldi - next_out);
2620                 }
2621                 next_out = i;
2622                 started = STARTED_BOARD;
2623                 parse_pos = 0;
2624                 continue;
2625             }
2626
2627             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2628                 looking_at(buf, &i, "<b1> ")) {
2629                 if (oldi > next_out) {
2630                     SendToPlayer(&buf[next_out], oldi - next_out);
2631                 }
2632                 next_out = i;
2633                 started = STARTED_HOLDINGS;
2634                 parse_pos = 0;
2635                 continue;
2636             }
2637
2638             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2639                 loggedOn = TRUE;
2640                 /* Header for a move list -- first line */
2641
2642                 switch (ics_getting_history) {
2643                   case H_FALSE:
2644                     switch (gameMode) {
2645                       case IcsIdle:
2646                       case BeginningOfGame:
2647                         /* User typed "moves" or "oldmoves" while we
2648                            were idle.  Pretend we asked for these
2649                            moves and soak them up so user can step
2650                            through them and/or save them.
2651                            */
2652                         Reset(FALSE, TRUE);
2653                         gameMode = IcsObserving;
2654                         ModeHighlight();
2655                         ics_gamenum = -1;
2656                         ics_getting_history = H_GOT_UNREQ_HEADER;
2657                         break;
2658                       case EditGame: /*?*/
2659                       case EditPosition: /*?*/
2660                         /* Should above feature work in these modes too? */
2661                         /* For now it doesn't */
2662                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2663                         break;
2664                       default:
2665                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2666                         break;
2667                     }
2668                     break;
2669                   case H_REQUESTED:
2670                     /* Is this the right one? */
2671                     if (gameInfo.white && gameInfo.black &&
2672                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2673                         strcmp(gameInfo.black, star_match[2]) == 0) {
2674                         /* All is well */
2675                         ics_getting_history = H_GOT_REQ_HEADER;
2676                     }
2677                     break;
2678                   case H_GOT_REQ_HEADER:
2679                   case H_GOT_UNREQ_HEADER:
2680                   case H_GOT_UNWANTED_HEADER:
2681                   case H_GETTING_MOVES:
2682                     /* Should not happen */
2683                     DisplayError(_("Error gathering move list: two headers"), 0);
2684                     ics_getting_history = H_FALSE;
2685                     break;
2686                 }
2687
2688                 /* Save player ratings into gameInfo if needed */
2689                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2690                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2691                     (gameInfo.whiteRating == -1 ||
2692                      gameInfo.blackRating == -1)) {
2693
2694                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2695                     gameInfo.blackRating = string_to_rating(star_match[3]);
2696                     if (appData.debugMode)
2697                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2698                               gameInfo.whiteRating, gameInfo.blackRating);
2699                 }
2700                 continue;
2701             }
2702
2703             if (looking_at(buf, &i,
2704               "* * match, initial time: * minute*, increment: * second")) {
2705                 /* Header for a move list -- second line */
2706                 /* Initial board will follow if this is a wild game */
2707                 if (gameInfo.event != NULL) free(gameInfo.event);
2708                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2709                 gameInfo.event = StrSave(str);
2710                 /* [HGM] we switched variant. Translate boards if needed. */
2711                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2712                 continue;
2713             }
2714
2715             if (looking_at(buf, &i, "Move  ")) {
2716                 /* Beginning of a move list */
2717                 switch (ics_getting_history) {
2718                   case H_FALSE:
2719                     /* Normally should not happen */
2720                     /* Maybe user hit reset while we were parsing */
2721                     break;
2722                   case H_REQUESTED:
2723                     /* Happens if we are ignoring a move list that is not
2724                      * the one we just requested.  Common if the user
2725                      * tries to observe two games without turning off
2726                      * getMoveList */
2727                     break;
2728                   case H_GETTING_MOVES:
2729                     /* Should not happen */
2730                     DisplayError(_("Error gathering move list: nested"), 0);
2731                     ics_getting_history = H_FALSE;
2732                     break;
2733                   case H_GOT_REQ_HEADER:
2734                     ics_getting_history = H_GETTING_MOVES;
2735                     started = STARTED_MOVES;
2736                     parse_pos = 0;
2737                     if (oldi > next_out) {
2738                         SendToPlayer(&buf[next_out], oldi - next_out);
2739                     }
2740                     break;
2741                   case H_GOT_UNREQ_HEADER:
2742                     ics_getting_history = H_GETTING_MOVES;
2743                     started = STARTED_MOVES_NOHIDE;
2744                     parse_pos = 0;
2745                     break;
2746                   case H_GOT_UNWANTED_HEADER:
2747                     ics_getting_history = H_FALSE;
2748                     break;
2749                 }
2750                 continue;
2751             }                           
2752             
2753             if (looking_at(buf, &i, "% ") ||
2754                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2755                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2756                 savingComment = FALSE;
2757                 switch (started) {
2758                   case STARTED_MOVES:
2759                   case STARTED_MOVES_NOHIDE:
2760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2761                     parse[parse_pos + i - oldi] = NULLCHAR;
2762                     ParseGameHistory(parse);
2763 #if ZIPPY
2764                     if (appData.zippyPlay && first.initDone) {
2765                         FeedMovesToProgram(&first, forwardMostMove);
2766                         if (gameMode == IcsPlayingWhite) {
2767                             if (WhiteOnMove(forwardMostMove)) {
2768                                 if (first.sendTime) {
2769                                   if (first.useColors) {
2770                                     SendToProgram("black\n", &first); 
2771                                   }
2772                                   SendTimeRemaining(&first, TRUE);
2773                                 }
2774 #if 0
2775                                 if (first.useColors) {
2776                                   SendToProgram("white\ngo\n", &first);
2777                                 } else {
2778                                   SendToProgram("go\n", &first);
2779                                 }
2780 #else
2781                                 if (first.useColors) {
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783                                 }
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785 #endif
2786                                 first.maybeThinking = TRUE;
2787                             } else {
2788                                 if (first.usePlayother) {
2789                                   if (first.sendTime) {
2790                                     SendTimeRemaining(&first, TRUE);
2791                                   }
2792                                   SendToProgram("playother\n", &first);
2793                                   firstMove = FALSE;
2794                                 } else {
2795                                   firstMove = TRUE;
2796                                 }
2797                             }
2798                         } else if (gameMode == IcsPlayingBlack) {
2799                             if (!WhiteOnMove(forwardMostMove)) {
2800                                 if (first.sendTime) {
2801                                   if (first.useColors) {
2802                                     SendToProgram("white\n", &first);
2803                                   }
2804                                   SendTimeRemaining(&first, FALSE);
2805                                 }
2806 #if 0
2807                                 if (first.useColors) {
2808                                   SendToProgram("black\ngo\n", &first);
2809                                 } else {
2810                                   SendToProgram("go\n", &first);
2811                                 }
2812 #else
2813                                 if (first.useColors) {
2814                                   SendToProgram("black\n", &first);
2815                                 }
2816                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2817 #endif
2818                                 first.maybeThinking = TRUE;
2819                             } else {
2820                                 if (first.usePlayother) {
2821                                   if (first.sendTime) {
2822                                     SendTimeRemaining(&first, FALSE);
2823                                   }
2824                                   SendToProgram("playother\n", &first);
2825                                   firstMove = FALSE;
2826                                 } else {
2827                                   firstMove = TRUE;
2828                                 }
2829                             }
2830                         }                       
2831                     }
2832 #endif
2833                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2834                         /* Moves came from oldmoves or moves command
2835                            while we weren't doing anything else.
2836                            */
2837                         currentMove = forwardMostMove;
2838                         ClearHighlights();/*!!could figure this out*/
2839                         flipView = appData.flipView;
2840                         DrawPosition(FALSE, boards[currentMove]);
2841                         DisplayBothClocks();
2842                         sprintf(str, "%s vs. %s",
2843                                 gameInfo.white, gameInfo.black);
2844                         DisplayTitle(str);
2845                         gameMode = IcsIdle;
2846                     } else {
2847                         /* Moves were history of an active game */
2848                         if (gameInfo.resultDetails != NULL) {
2849                             free(gameInfo.resultDetails);
2850                             gameInfo.resultDetails = NULL;
2851                         }
2852                     }
2853                     HistorySet(parseList, backwardMostMove,
2854                                forwardMostMove, currentMove-1);
2855                     DisplayMove(currentMove - 1);
2856                     if (started == STARTED_MOVES) next_out = i;
2857                     started = STARTED_NONE;
2858                     ics_getting_history = H_FALSE;
2859                     break;
2860
2861                   case STARTED_OBSERVE:
2862                     started = STARTED_NONE;
2863                     SendToICS(ics_prefix);
2864                     SendToICS("refresh\n");
2865                     break;
2866
2867                   default:
2868                     break;
2869                 }
2870                 if(bookHit) { // [HGM] book: simulate book reply
2871                     static char bookMove[MSG_SIZ]; // a bit generous?
2872
2873                     programStats.nodes = programStats.depth = programStats.time = 
2874                     programStats.score = programStats.got_only_move = 0;
2875                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2876
2877                     strcpy(bookMove, "move ");
2878                     strcat(bookMove, bookHit);
2879                     HandleMachineMove(bookMove, &first);
2880                 }
2881                 continue;
2882             }
2883             
2884             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2885                  started == STARTED_HOLDINGS ||
2886                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2887                 /* Accumulate characters in move list or board */
2888                 parse[parse_pos++] = buf[i];
2889             }
2890             
2891             /* Start of game messages.  Mostly we detect start of game
2892                when the first board image arrives.  On some versions
2893                of the ICS, though, we need to do a "refresh" after starting
2894                to observe in order to get the current board right away. */
2895             if (looking_at(buf, &i, "Adding game * to observation list")) {
2896                 started = STARTED_OBSERVE;
2897                 continue;
2898             }
2899
2900             /* Handle auto-observe */
2901             if (appData.autoObserve &&
2902                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2903                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2904                 char *player;
2905                 /* Choose the player that was highlighted, if any. */
2906                 if (star_match[0][0] == '\033' ||
2907                     star_match[1][0] != '\033') {
2908                     player = star_match[0];
2909                 } else {
2910                     player = star_match[2];
2911                 }
2912                 sprintf(str, "%sobserve %s\n",
2913                         ics_prefix, StripHighlightAndTitle(player));
2914                 SendToICS(str);
2915
2916                 /* Save ratings from notify string */
2917                 strcpy(player1Name, star_match[0]);
2918                 player1Rating = string_to_rating(star_match[1]);
2919                 strcpy(player2Name, star_match[2]);
2920                 player2Rating = string_to_rating(star_match[3]);
2921
2922                 if (appData.debugMode)
2923                   fprintf(debugFP, 
2924                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2925                           player1Name, player1Rating,
2926                           player2Name, player2Rating);
2927
2928                 continue;
2929             }
2930
2931             /* Deal with automatic examine mode after a game,
2932                and with IcsObserving -> IcsExamining transition */
2933             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2934                 looking_at(buf, &i, "has made you an examiner of game *")) {
2935
2936                 int gamenum = atoi(star_match[0]);
2937                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2938                     gamenum == ics_gamenum) {
2939                     /* We were already playing or observing this game;
2940                        no need to refetch history */
2941                     gameMode = IcsExamining;
2942                     if (pausing) {
2943                         pauseExamForwardMostMove = forwardMostMove;
2944                     } else if (currentMove < forwardMostMove) {
2945                         ForwardInner(forwardMostMove);
2946                     }
2947                 } else {
2948                     /* I don't think this case really can happen */
2949                     SendToICS(ics_prefix);
2950                     SendToICS("refresh\n");
2951                 }
2952                 continue;
2953             }    
2954             
2955             /* Error messages */
2956 //          if (ics_user_moved) {
2957             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2958                 if (looking_at(buf, &i, "Illegal move") ||
2959                     looking_at(buf, &i, "Not a legal move") ||
2960                     looking_at(buf, &i, "Your king is in check") ||
2961                     looking_at(buf, &i, "It isn't your turn") ||
2962                     looking_at(buf, &i, "It is not your move")) {
2963                     /* Illegal move */
2964                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2965                         currentMove = --forwardMostMove;
2966                         DisplayMove(currentMove - 1); /* before DMError */
2967                         DrawPosition(FALSE, boards[currentMove]);
2968                         SwitchClocks();
2969                         DisplayBothClocks();
2970                     }
2971                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2972                     ics_user_moved = 0;
2973                     continue;
2974                 }
2975             }
2976
2977             if (looking_at(buf, &i, "still have time") ||
2978                 looking_at(buf, &i, "not out of time") ||
2979                 looking_at(buf, &i, "either player is out of time") ||
2980                 looking_at(buf, &i, "has timeseal; checking")) {
2981                 /* We must have called his flag a little too soon */
2982                 whiteFlag = blackFlag = FALSE;
2983                 continue;
2984             }
2985
2986             if (looking_at(buf, &i, "added * seconds to") ||
2987                 looking_at(buf, &i, "seconds were added to")) {
2988                 /* Update the clocks */
2989                 SendToICS(ics_prefix);
2990                 SendToICS("refresh\n");
2991                 continue;
2992             }
2993
2994             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2995                 ics_clock_paused = TRUE;
2996                 StopClocks();
2997                 continue;
2998             }
2999
3000             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3001                 ics_clock_paused = FALSE;
3002                 StartClocks();
3003                 continue;
3004             }
3005
3006             /* Grab player ratings from the Creating: message.
3007                Note we have to check for the special case when
3008                the ICS inserts things like [white] or [black]. */
3009             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3010                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3011                 /* star_matches:
3012                    0    player 1 name (not necessarily white)
3013                    1    player 1 rating
3014                    2    empty, white, or black (IGNORED)
3015                    3    player 2 name (not necessarily black)
3016                    4    player 2 rating
3017                    
3018                    The names/ratings are sorted out when the game
3019                    actually starts (below).
3020                 */
3021                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3022                 player1Rating = string_to_rating(star_match[1]);
3023                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3024                 player2Rating = string_to_rating(star_match[4]);
3025
3026                 if (appData.debugMode)
3027                   fprintf(debugFP, 
3028                           "Ratings from 'Creating:' %s %d, %s %d\n",
3029                           player1Name, player1Rating,
3030                           player2Name, player2Rating);
3031
3032                 continue;
3033             }
3034             
3035             /* Improved generic start/end-of-game messages */
3036             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3037                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3038                 /* If tkind == 0: */
3039                 /* star_match[0] is the game number */
3040                 /*           [1] is the white player's name */
3041                 /*           [2] is the black player's name */
3042                 /* For end-of-game: */
3043                 /*           [3] is the reason for the game end */
3044                 /*           [4] is a PGN end game-token, preceded by " " */
3045                 /* For start-of-game: */
3046                 /*           [3] begins with "Creating" or "Continuing" */
3047                 /*           [4] is " *" or empty (don't care). */
3048                 int gamenum = atoi(star_match[0]);
3049                 char *whitename, *blackname, *why, *endtoken;
3050                 ChessMove endtype = (ChessMove) 0;
3051
3052                 if (tkind == 0) {
3053                   whitename = star_match[1];
3054                   blackname = star_match[2];
3055                   why = star_match[3];
3056                   endtoken = star_match[4];
3057                 } else {
3058                   whitename = star_match[1];
3059                   blackname = star_match[3];
3060                   why = star_match[5];
3061                   endtoken = star_match[6];
3062                 }
3063
3064                 /* Game start messages */
3065                 if (strncmp(why, "Creating ", 9) == 0 ||
3066                     strncmp(why, "Continuing ", 11) == 0) {
3067                     gs_gamenum = gamenum;
3068                     strcpy(gs_kind, strchr(why, ' ') + 1);
3069 #if ZIPPY
3070                     if (appData.zippyPlay) {
3071                         ZippyGameStart(whitename, blackname);
3072                     }
3073 #endif /*ZIPPY*/
3074                     continue;
3075                 }
3076
3077                 /* Game end messages */
3078                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3079                     ics_gamenum != gamenum) {
3080                     continue;
3081                 }
3082                 while (endtoken[0] == ' ') endtoken++;
3083                 switch (endtoken[0]) {
3084                   case '*':
3085                   default:
3086                     endtype = GameUnfinished;
3087                     break;
3088                   case '0':
3089                     endtype = BlackWins;
3090                     break;
3091                   case '1':
3092                     if (endtoken[1] == '/')
3093                       endtype = GameIsDrawn;
3094                     else
3095                       endtype = WhiteWins;
3096                     break;
3097                 }
3098                 GameEnds(endtype, why, GE_ICS);
3099 #if ZIPPY
3100                 if (appData.zippyPlay && first.initDone) {
3101                     ZippyGameEnd(endtype, why);
3102                     if (first.pr == NULL) {
3103                       /* Start the next process early so that we'll
3104                          be ready for the next challenge */
3105                       StartChessProgram(&first);
3106                     }
3107                     /* Send "new" early, in case this command takes
3108                        a long time to finish, so that we'll be ready
3109                        for the next challenge. */
3110                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3111                     Reset(TRUE, TRUE);
3112                 }
3113 #endif /*ZIPPY*/
3114                 continue;
3115             }
3116
3117             if (looking_at(buf, &i, "Removing game * from observation") ||
3118                 looking_at(buf, &i, "no longer observing game *") ||
3119                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3120                 if (gameMode == IcsObserving &&
3121                     atoi(star_match[0]) == ics_gamenum)
3122                   {
3123                       /* icsEngineAnalyze */
3124                       if (appData.icsEngineAnalyze) {
3125                             ExitAnalyzeMode();
3126                             ModeHighlight();
3127                       }
3128                       StopClocks();
3129                       gameMode = IcsIdle;
3130                       ics_gamenum = -1;
3131                       ics_user_moved = FALSE;
3132                   }
3133                 continue;
3134             }
3135
3136             if (looking_at(buf, &i, "no longer examining game *")) {
3137                 if (gameMode == IcsExamining &&
3138                     atoi(star_match[0]) == ics_gamenum)
3139                   {
3140                       gameMode = IcsIdle;
3141                       ics_gamenum = -1;
3142                       ics_user_moved = FALSE;
3143                   }
3144                 continue;
3145             }
3146
3147             /* Advance leftover_start past any newlines we find,
3148                so only partial lines can get reparsed */
3149             if (looking_at(buf, &i, "\n")) {
3150                 prevColor = curColor;
3151                 if (curColor != ColorNormal) {
3152                     if (oldi > next_out) {
3153                         SendToPlayer(&buf[next_out], oldi - next_out);
3154                         next_out = oldi;
3155                     }
3156                     Colorize(ColorNormal, FALSE);
3157                     curColor = ColorNormal;
3158                 }
3159                 if (started == STARTED_BOARD) {
3160                     started = STARTED_NONE;
3161                     parse[parse_pos] = NULLCHAR;
3162                     ParseBoard12(parse);
3163                     ics_user_moved = 0;
3164
3165                     /* Send premove here */
3166                     if (appData.premove) {
3167                       char str[MSG_SIZ];
3168                       if (currentMove == 0 &&
3169                           gameMode == IcsPlayingWhite &&
3170                           appData.premoveWhite) {
3171                         sprintf(str, "%s%s\n", ics_prefix,
3172                                 appData.premoveWhiteText);
3173                         if (appData.debugMode)
3174                           fprintf(debugFP, "Sending premove:\n");
3175                         SendToICS(str);
3176                       } else if (currentMove == 1 &&
3177                                  gameMode == IcsPlayingBlack &&
3178                                  appData.premoveBlack) {
3179                         sprintf(str, "%s%s\n", ics_prefix,
3180                                 appData.premoveBlackText);
3181                         if (appData.debugMode)
3182                           fprintf(debugFP, "Sending premove:\n");
3183                         SendToICS(str);
3184                       } else if (gotPremove) {
3185                         gotPremove = 0;
3186                         ClearPremoveHighlights();
3187                         if (appData.debugMode)
3188                           fprintf(debugFP, "Sending premove:\n");
3189                           UserMoveEvent(premoveFromX, premoveFromY, 
3190                                         premoveToX, premoveToY, 
3191                                         premovePromoChar);
3192                       }
3193                     }
3194
3195                     /* Usually suppress following prompt */
3196                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3197                         if (looking_at(buf, &i, "*% ")) {
3198                             savingComment = FALSE;
3199                         }
3200                     }
3201                     next_out = i;
3202                 } else if (started == STARTED_HOLDINGS) {
3203                     int gamenum;
3204                     char new_piece[MSG_SIZ];
3205                     started = STARTED_NONE;
3206                     parse[parse_pos] = NULLCHAR;
3207                     if (appData.debugMode)
3208                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3209                                                         parse, currentMove);
3210                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3211                         gamenum == ics_gamenum) {
3212                         if (gameInfo.variant == VariantNormal) {
3213                           /* [HGM] We seem to switch variant during a game!
3214                            * Presumably no holdings were displayed, so we have
3215                            * to move the position two files to the right to
3216                            * create room for them!
3217                            */
3218                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3219                           /* Get a move list just to see the header, which
3220                              will tell us whether this is really bug or zh */
3221                           if (ics_getting_history == H_FALSE) {
3222                             ics_getting_history = H_REQUESTED;
3223                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3224                             SendToICS(str);
3225                           }
3226                         }
3227                         new_piece[0] = NULLCHAR;
3228                         sscanf(parse, "game %d white [%s black [%s <- %s",
3229                                &gamenum, white_holding, black_holding,
3230                                new_piece);
3231                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3232                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3233                         /* [HGM] copy holdings to board holdings area */
3234                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3235                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3236 #if ZIPPY
3237                         if (appData.zippyPlay && first.initDone) {
3238                             ZippyHoldings(white_holding, black_holding,
3239                                           new_piece);
3240                         }
3241 #endif /*ZIPPY*/
3242                         if (tinyLayout || smallLayout) {
3243                             char wh[16], bh[16];
3244                             PackHolding(wh, white_holding);
3245                             PackHolding(bh, black_holding);
3246                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3247                                     gameInfo.white, gameInfo.black);
3248                         } else {
3249                             sprintf(str, "%s [%s] vs. %s [%s]",
3250                                     gameInfo.white, white_holding,
3251                                     gameInfo.black, black_holding);
3252                         }
3253
3254                         DrawPosition(FALSE, boards[currentMove]);
3255                         DisplayTitle(str);
3256                     }
3257                     /* Suppress following prompt */
3258                     if (looking_at(buf, &i, "*% ")) {
3259                         savingComment = FALSE;
3260                     }
3261                     next_out = i;
3262                 }
3263                 continue;
3264             }
3265
3266             i++;                /* skip unparsed character and loop back */
3267         }
3268         
3269         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3270             started != STARTED_HOLDINGS && i > next_out) {
3271             SendToPlayer(&buf[next_out], i - next_out);
3272             next_out = i;
3273         }
3274         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3275         
3276         leftover_len = buf_len - leftover_start;
3277         /* if buffer ends with something we couldn't parse,
3278            reparse it after appending the next read */
3279         
3280     } else if (count == 0) {
3281         RemoveInputSource(isr);
3282         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3283     } else {
3284         DisplayFatalError(_("Error reading from ICS"), error, 1);
3285     }
3286 }
3287
3288
3289 /* Board style 12 looks like this:
3290    
3291    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3292    
3293  * The "<12> " is stripped before it gets to this routine.  The two
3294  * trailing 0's (flip state and clock ticking) are later addition, and
3295  * some chess servers may not have them, or may have only the first.
3296  * Additional trailing fields may be added in the future.  
3297  */
3298
3299 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3300
3301 #define RELATION_OBSERVING_PLAYED    0
3302 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3303 #define RELATION_PLAYING_MYMOVE      1
3304 #define RELATION_PLAYING_NOTMYMOVE  -1
3305 #define RELATION_EXAMINING           2
3306 #define RELATION_ISOLATED_BOARD     -3
3307 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3308
3309 void
3310 ParseBoard12(string)
3311      char *string;
3312
3313     GameMode newGameMode;
3314     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3315     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3316     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3317     char to_play, board_chars[200];
3318     char move_str[500], str[500], elapsed_time[500];
3319     char black[32], white[32];
3320     Board board;
3321     int prevMove = currentMove;
3322     int ticking = 2;
3323     ChessMove moveType;
3324     int fromX, fromY, toX, toY;
3325     char promoChar;
3326     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3327     char *bookHit = NULL; // [HGM] book
3328
3329     fromX = fromY = toX = toY = -1;
3330     
3331     newGame = FALSE;
3332
3333     if (appData.debugMode)
3334       fprintf(debugFP, _("Parsing board: %s\n"), string);
3335
3336     move_str[0] = NULLCHAR;
3337     elapsed_time[0] = NULLCHAR;
3338     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3339         int  i = 0, j;
3340         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3341             if(string[i] == ' ') { ranks++; files = 0; }
3342             else files++;
3343             i++;
3344         }
3345         for(j = 0; j <i; j++) board_chars[j] = string[j];
3346         board_chars[i] = '\0';
3347         string += i + 1;
3348     }
3349     n = sscanf(string, PATTERN, &to_play, &double_push,
3350                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3351                &gamenum, white, black, &relation, &basetime, &increment,
3352                &white_stren, &black_stren, &white_time, &black_time,
3353                &moveNum, str, elapsed_time, move_str, &ics_flip,
3354                &ticking);
3355
3356     if (n < 21) {
3357         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3358         DisplayError(str, 0);
3359         return;
3360     }
3361
3362     /* Convert the move number to internal form */
3363     moveNum = (moveNum - 1) * 2;
3364     if (to_play == 'B') moveNum++;
3365     if (moveNum >= MAX_MOVES) {
3366       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3367                         0, 1);
3368       return;
3369     }
3370     
3371     switch (relation) {
3372       case RELATION_OBSERVING_PLAYED:
3373       case RELATION_OBSERVING_STATIC:
3374         if (gamenum == -1) {
3375             /* Old ICC buglet */
3376             relation = RELATION_OBSERVING_STATIC;
3377         }
3378         newGameMode = IcsObserving;
3379         break;
3380       case RELATION_PLAYING_MYMOVE:
3381       case RELATION_PLAYING_NOTMYMOVE:
3382         newGameMode =
3383           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3384             IcsPlayingWhite : IcsPlayingBlack;
3385         break;
3386       case RELATION_EXAMINING:
3387         newGameMode = IcsExamining;
3388         break;
3389       case RELATION_ISOLATED_BOARD:
3390       default:
3391         /* Just display this board.  If user was doing something else,
3392            we will forget about it until the next board comes. */ 
3393         newGameMode = IcsIdle;
3394         break;
3395       case RELATION_STARTING_POSITION:
3396         newGameMode = gameMode;
3397         break;
3398     }
3399     
3400     /* Modify behavior for initial board display on move listing
3401        of wild games.
3402        */
3403     switch (ics_getting_history) {
3404       case H_FALSE:
3405       case H_REQUESTED:
3406         break;
3407       case H_GOT_REQ_HEADER:
3408       case H_GOT_UNREQ_HEADER:
3409         /* This is the initial position of the current game */
3410         gamenum = ics_gamenum;
3411         moveNum = 0;            /* old ICS bug workaround */
3412         if (to_play == 'B') {
3413           startedFromSetupPosition = TRUE;
3414           blackPlaysFirst = TRUE;
3415           moveNum = 1;
3416           if (forwardMostMove == 0) forwardMostMove = 1;
3417           if (backwardMostMove == 0) backwardMostMove = 1;
3418           if (currentMove == 0) currentMove = 1;
3419         }
3420         newGameMode = gameMode;
3421         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3422         break;
3423       case H_GOT_UNWANTED_HEADER:
3424         /* This is an initial board that we don't want */
3425         return;
3426       case H_GETTING_MOVES:
3427         /* Should not happen */
3428         DisplayError(_("Error gathering move list: extra board"), 0);
3429         ics_getting_history = H_FALSE;
3430         return;
3431     }
3432     
3433     /* Take action if this is the first board of a new game, or of a
3434        different game than is currently being displayed.  */
3435     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3436         relation == RELATION_ISOLATED_BOARD) {
3437         
3438         /* Forget the old game and get the history (if any) of the new one */
3439         if (gameMode != BeginningOfGame) {
3440           Reset(FALSE, TRUE);
3441         }
3442         newGame = TRUE;
3443         if (appData.autoRaiseBoard) BoardToTop();
3444         prevMove = -3;
3445         if (gamenum == -1) {
3446             newGameMode = IcsIdle;
3447         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3448                    appData.getMoveList) {
3449             /* Need to get game history */
3450             ics_getting_history = H_REQUESTED;
3451             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3452             SendToICS(str);
3453         }
3454         
3455         /* Initially flip the board to have black on the bottom if playing
3456            black or if the ICS flip flag is set, but let the user change
3457            it with the Flip View button. */
3458         flipView = appData.autoFlipView ? 
3459           (newGameMode == IcsPlayingBlack) || ics_flip :
3460           appData.flipView;
3461         
3462         /* Done with values from previous mode; copy in new ones */
3463         gameMode = newGameMode;
3464         ModeHighlight();
3465         ics_gamenum = gamenum;
3466         if (gamenum == gs_gamenum) {
3467             int klen = strlen(gs_kind);
3468             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3469             sprintf(str, "ICS %s", gs_kind);
3470             gameInfo.event = StrSave(str);
3471         } else {
3472             gameInfo.event = StrSave("ICS game");
3473         }
3474         gameInfo.site = StrSave(appData.icsHost);
3475         gameInfo.date = PGNDate();
3476         gameInfo.round = StrSave("-");
3477         gameInfo.white = StrSave(white);
3478         gameInfo.black = StrSave(black);
3479         timeControl = basetime * 60 * 1000;
3480         timeControl_2 = 0;
3481         timeIncrement = increment * 1000;
3482         movesPerSession = 0;
3483         gameInfo.timeControl = TimeControlTagValue();
3484         VariantSwitch(board, StringToVariant(gameInfo.event) );
3485   if (appData.debugMode) {
3486     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3487     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3488     setbuf(debugFP, NULL);
3489   }
3490
3491         gameInfo.outOfBook = NULL;
3492         
3493         /* Do we have the ratings? */
3494         if (strcmp(player1Name, white) == 0 &&
3495             strcmp(player2Name, black) == 0) {
3496             if (appData.debugMode)
3497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3498                       player1Rating, player2Rating);
3499             gameInfo.whiteRating = player1Rating;
3500             gameInfo.blackRating = player2Rating;
3501         } else if (strcmp(player2Name, white) == 0 &&
3502                    strcmp(player1Name, black) == 0) {
3503             if (appData.debugMode)
3504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505                       player2Rating, player1Rating);
3506             gameInfo.whiteRating = player2Rating;
3507             gameInfo.blackRating = player1Rating;
3508         }
3509         player1Name[0] = player2Name[0] = NULLCHAR;
3510
3511         /* Silence shouts if requested */
3512         if (appData.quietPlay &&
3513             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3514             SendToICS(ics_prefix);
3515             SendToICS("set shout 0\n");
3516         }
3517     }
3518     
3519     /* Deal with midgame name changes */
3520     if (!newGame) {
3521         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3522             if (gameInfo.white) free(gameInfo.white);
3523             gameInfo.white = StrSave(white);
3524         }
3525         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3526             if (gameInfo.black) free(gameInfo.black);
3527             gameInfo.black = StrSave(black);
3528         }
3529     }
3530     
3531     /* Throw away game result if anything actually changes in examine mode */
3532     if (gameMode == IcsExamining && !newGame) {
3533         gameInfo.result = GameUnfinished;
3534         if (gameInfo.resultDetails != NULL) {
3535             free(gameInfo.resultDetails);
3536             gameInfo.resultDetails = NULL;
3537         }
3538     }
3539     
3540     /* In pausing && IcsExamining mode, we ignore boards coming
3541        in if they are in a different variation than we are. */
3542     if (pauseExamInvalid) return;
3543     if (pausing && gameMode == IcsExamining) {
3544         if (moveNum <= pauseExamForwardMostMove) {
3545             pauseExamInvalid = TRUE;
3546             forwardMostMove = pauseExamForwardMostMove;
3547             return;
3548         }
3549     }
3550     
3551   if (appData.debugMode) {
3552     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3553   }
3554     /* Parse the board */
3555     for (k = 0; k < ranks; k++) {
3556       for (j = 0; j < files; j++)
3557         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3558       if(gameInfo.holdingsWidth > 1) {
3559            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3560            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3561       }
3562     }
3563     CopyBoard(boards[moveNum], board);
3564     if (moveNum == 0) {
3565         startedFromSetupPosition =
3566           !CompareBoards(board, initialPosition);
3567         if(startedFromSetupPosition)
3568             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3569     }
3570
3571     /* [HGM] Set castling rights. Take the outermost Rooks,
3572        to make it also work for FRC opening positions. Note that board12
3573        is really defective for later FRC positions, as it has no way to
3574        indicate which Rook can castle if they are on the same side of King.
3575        For the initial position we grant rights to the outermost Rooks,
3576        and remember thos rights, and we then copy them on positions
3577        later in an FRC game. This means WB might not recognize castlings with
3578        Rooks that have moved back to their original position as illegal,
3579        but in ICS mode that is not its job anyway.
3580     */
3581     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3582     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3583
3584         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3585             if(board[0][i] == WhiteRook) j = i;
3586         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3588             if(board[0][i] == WhiteRook) j = i;
3589         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3590         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3591             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3592         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3593         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3594             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3595         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3596
3597         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3598         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3599             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3600         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601             if(board[BOARD_HEIGHT-1][k] == bKing)
3602                 initialRights[5] = castlingRights[moveNum][5] = k;
3603     } else { int r;
3604         r = castlingRights[moveNum][0] = initialRights[0];
3605         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3606         r = castlingRights[moveNum][1] = initialRights[1];
3607         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3608         r = castlingRights[moveNum][3] = initialRights[3];
3609         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3610         r = castlingRights[moveNum][4] = initialRights[4];
3611         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3612         /* wildcastle kludge: always assume King has rights */
3613         r = castlingRights[moveNum][2] = initialRights[2];
3614         r = castlingRights[moveNum][5] = initialRights[5];
3615     }
3616     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3617     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3618
3619     
3620     if (ics_getting_history == H_GOT_REQ_HEADER ||
3621         ics_getting_history == H_GOT_UNREQ_HEADER) {
3622         /* This was an initial position from a move list, not
3623            the current position */
3624         return;
3625     }
3626     
3627     /* Update currentMove and known move number limits */
3628     newMove = newGame || moveNum > forwardMostMove;
3629
3630     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3631     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3632         takeback = forwardMostMove - moveNum;
3633         for (i = 0; i < takeback; i++) {
3634              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3635              SendToProgram("undo\n", &first);
3636         }
3637     }
3638
3639     if (newGame) {
3640         forwardMostMove = backwardMostMove = currentMove = moveNum;
3641         if (gameMode == IcsExamining && moveNum == 0) {
3642           /* Workaround for ICS limitation: we are not told the wild
3643              type when starting to examine a game.  But if we ask for
3644              the move list, the move list header will tell us */
3645             ics_getting_history = H_REQUESTED;
3646             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3647             SendToICS(str);
3648         }
3649     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3650                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3651         forwardMostMove = moveNum;
3652         if (!pausing || currentMove > forwardMostMove)
3653           currentMove = forwardMostMove;
3654     } else {
3655         /* New part of history that is not contiguous with old part */ 
3656         if (pausing && gameMode == IcsExamining) {
3657             pauseExamInvalid = TRUE;
3658             forwardMostMove = pauseExamForwardMostMove;
3659             return;
3660         }
3661         forwardMostMove = backwardMostMove = currentMove = moveNum;
3662         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3663             ics_getting_history = H_REQUESTED;
3664             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3665             SendToICS(str);
3666         }
3667     }
3668     
3669     /* Update the clocks */
3670     if (strchr(elapsed_time, '.')) {
3671       /* Time is in ms */
3672       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3673       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3674     } else {
3675       /* Time is in seconds */
3676       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3677       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3678     }
3679       
3680
3681 #if ZIPPY
3682     if (appData.zippyPlay && newGame &&
3683         gameMode != IcsObserving && gameMode != IcsIdle &&
3684         gameMode != IcsExamining)
3685       ZippyFirstBoard(moveNum, basetime, increment);
3686 #endif
3687     
3688     /* Put the move on the move list, first converting
3689        to canonical algebraic form. */
3690     if (moveNum > 0) {
3691   if (appData.debugMode) {
3692     if (appData.debugMode) { int f = forwardMostMove;
3693         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3694                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3695     }
3696     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3697     fprintf(debugFP, "moveNum = %d\n", moveNum);
3698     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3699     setbuf(debugFP, NULL);
3700   }
3701         if (moveNum <= backwardMostMove) {
3702             /* We don't know what the board looked like before
3703                this move.  Punt. */
3704             strcpy(parseList[moveNum - 1], move_str);
3705             strcat(parseList[moveNum - 1], " ");
3706             strcat(parseList[moveNum - 1], elapsed_time);
3707             moveList[moveNum - 1][0] = NULLCHAR;
3708         } else if (strcmp(move_str, "none") == 0) {
3709             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3710             /* Again, we don't know what the board looked like;
3711                this is really the start of the game. */
3712             parseList[moveNum - 1][0] = NULLCHAR;
3713             moveList[moveNum - 1][0] = NULLCHAR;
3714             backwardMostMove = moveNum;
3715             startedFromSetupPosition = TRUE;
3716             fromX = fromY = toX = toY = -1;
3717         } else {
3718           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3719           //                 So we parse the long-algebraic move string in stead of the SAN move
3720           int valid; char buf[MSG_SIZ], *prom;
3721
3722           // str looks something like "Q/a1-a2"; kill the slash
3723           if(str[1] == '/') 
3724                 sprintf(buf, "%c%s", str[0], str+2);
3725           else  strcpy(buf, str); // might be castling
3726           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3727                 strcat(buf, prom); // long move lacks promo specification!
3728           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3729                 if(appData.debugMode) 
3730                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3731                 strcpy(move_str, buf);
3732           }
3733           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3734                                 &fromX, &fromY, &toX, &toY, &promoChar)
3735                || ParseOneMove(buf, moveNum - 1, &moveType,
3736                                 &fromX, &fromY, &toX, &toY, &promoChar);
3737           // end of long SAN patch
3738           if (valid) {
3739             (void) CoordsToAlgebraic(boards[moveNum - 1],
3740                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3741                                      fromY, fromX, toY, toX, promoChar,
3742                                      parseList[moveNum-1]);
3743             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3744                              castlingRights[moveNum]) ) {
3745               case MT_NONE:
3746               case MT_STALEMATE:
3747               default:
3748                 break;
3749               case MT_CHECK:
3750                 if(gameInfo.variant != VariantShogi)
3751                     strcat(parseList[moveNum - 1], "+");
3752                 break;
3753               case MT_CHECKMATE:
3754               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3755                 strcat(parseList[moveNum - 1], "#");
3756                 break;
3757             }
3758             strcat(parseList[moveNum - 1], " ");
3759             strcat(parseList[moveNum - 1], elapsed_time);
3760             /* currentMoveString is set as a side-effect of ParseOneMove */
3761             strcpy(moveList[moveNum - 1], currentMoveString);
3762             strcat(moveList[moveNum - 1], "\n");
3763           } else {
3764             /* Move from ICS was illegal!?  Punt. */
3765   if (appData.debugMode) {
3766     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3767     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3768   }
3769 #if 0
3770             if (appData.testLegality && appData.debugMode) {
3771                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3772                 DisplayError(str, 0);
3773             }
3774 #endif
3775             strcpy(parseList[moveNum - 1], move_str);
3776             strcat(parseList[moveNum - 1], " ");
3777             strcat(parseList[moveNum - 1], elapsed_time);
3778             moveList[moveNum - 1][0] = NULLCHAR;
3779             fromX = fromY = toX = toY = -1;
3780           }
3781         }
3782   if (appData.debugMode) {
3783     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3784     setbuf(debugFP, NULL);
3785   }
3786
3787 #if ZIPPY
3788         /* Send move to chess program (BEFORE animating it). */
3789         if (appData.zippyPlay && !newGame && newMove && 
3790            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791
3792             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3793                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3794                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3795                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796                             move_str);
3797                     DisplayError(str, 0);
3798                 } else {
3799                     if (first.sendTime) {
3800                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801                     }
3802                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3803                     if (firstMove && !bookHit) {
3804                         firstMove = FALSE;
3805                         if (first.useColors) {
3806                           SendToProgram(gameMode == IcsPlayingWhite ?
3807                                         "white\ngo\n" :
3808                                         "black\ngo\n", &first);
3809                         } else {
3810                           SendToProgram("go\n", &first);
3811                         }
3812                         first.maybeThinking = TRUE;
3813                     }
3814                 }
3815             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3816               if (moveList[moveNum - 1][0] == NULLCHAR) {
3817                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3818                 DisplayError(str, 0);
3819               } else {
3820                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3821                 SendMoveToProgram(moveNum - 1, &first);
3822               }
3823             }
3824         }
3825 #endif
3826     }
3827
3828     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3829         /* If move comes from a remote source, animate it.  If it
3830            isn't remote, it will have already been animated. */
3831         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3832             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3833         }
3834         if (!pausing && appData.highlightLastMove) {
3835             SetHighlights(fromX, fromY, toX, toY);
3836         }
3837     }
3838     
3839     /* Start the clocks */
3840     whiteFlag = blackFlag = FALSE;
3841     appData.clockMode = !(basetime == 0 && increment == 0);
3842     if (ticking == 0) {
3843       ics_clock_paused = TRUE;
3844       StopClocks();
3845     } else if (ticking == 1) {
3846       ics_clock_paused = FALSE;
3847     }
3848     if (gameMode == IcsIdle ||
3849         relation == RELATION_OBSERVING_STATIC ||
3850         relation == RELATION_EXAMINING ||
3851         ics_clock_paused)
3852       DisplayBothClocks();
3853     else
3854       StartClocks();
3855     
3856     /* Display opponents and material strengths */
3857     if (gameInfo.variant != VariantBughouse &&
3858         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3859         if (tinyLayout || smallLayout) {
3860             if(gameInfo.variant == VariantNormal)
3861                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3862                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3863                     basetime, increment);
3864             else
3865                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3866                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3867                     basetime, increment, (int) gameInfo.variant);
3868         } else {
3869             if(gameInfo.variant == VariantNormal)
3870                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3872                     basetime, increment);
3873             else
3874                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3876                     basetime, increment, VariantName(gameInfo.variant));
3877         }
3878         DisplayTitle(str);
3879   if (appData.debugMode) {
3880     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3881   }
3882     }
3883
3884    
3885     /* Display the board */
3886     if (!pausing && !appData.noGUI) {
3887       
3888       if (appData.premove)
3889           if (!gotPremove || 
3890              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3891              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3892               ClearPremoveHighlights();
3893
3894       DrawPosition(FALSE, boards[currentMove]);
3895       DisplayMove(moveNum - 1);
3896       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3897             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3898               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3899         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3900       }
3901     }
3902
3903     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3904 #if ZIPPY
3905     if(bookHit) { // [HGM] book: simulate book reply
3906         static char bookMove[MSG_SIZ]; // a bit generous?
3907
3908         programStats.nodes = programStats.depth = programStats.time = 
3909         programStats.score = programStats.got_only_move = 0;
3910         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3911
3912         strcpy(bookMove, "move ");
3913         strcat(bookMove, bookHit);
3914         HandleMachineMove(bookMove, &first);
3915     }
3916 #endif
3917 }
3918
3919 void
3920 GetMoveListEvent()
3921 {
3922     char buf[MSG_SIZ];
3923     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3924         ics_getting_history = H_REQUESTED;
3925         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3926         SendToICS(buf);
3927     }
3928 }
3929
3930 void
3931 AnalysisPeriodicEvent(force)
3932      int force;
3933 {
3934     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3935          && !force) || !appData.periodicUpdates)
3936       return;
3937
3938     /* Send . command to Crafty to collect stats */
3939     SendToProgram(".\n", &first);
3940
3941     /* Don't send another until we get a response (this makes
3942        us stop sending to old Crafty's which don't understand
3943        the "." command (sending illegal cmds resets node count & time,
3944        which looks bad)) */
3945     programStats.ok_to_send = 0;
3946 }
3947
3948 void
3949 SendMoveToProgram(moveNum, cps)
3950      int moveNum;
3951      ChessProgramState *cps;
3952 {
3953     char buf[MSG_SIZ];
3954
3955     if (cps->useUsermove) {
3956       SendToProgram("usermove ", cps);
3957     }
3958     if (cps->useSAN) {
3959       char *space;
3960       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3961         int len = space - parseList[moveNum];
3962         memcpy(buf, parseList[moveNum], len);
3963         buf[len++] = '\n';
3964         buf[len] = NULLCHAR;
3965       } else {
3966         sprintf(buf, "%s\n", parseList[moveNum]);
3967       }
3968       SendToProgram(buf, cps);
3969     } else {
3970       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3971         AlphaRank(moveList[moveNum], 4);
3972         SendToProgram(moveList[moveNum], cps);
3973         AlphaRank(moveList[moveNum], 4); // and back
3974       } else
3975       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3976        * the engine. It would be nice to have a better way to identify castle 
3977        * moves here. */
3978       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3979                                                                          && cps->useOOCastle) {
3980         int fromX = moveList[moveNum][0] - AAA; 
3981         int fromY = moveList[moveNum][1] - ONE;
3982         int toX = moveList[moveNum][2] - AAA; 
3983         int toY = moveList[moveNum][3] - ONE;
3984         if((boards[moveNum][fromY][fromX] == WhiteKing 
3985             && boards[moveNum][toY][toX] == WhiteRook)
3986            || (boards[moveNum][fromY][fromX] == BlackKing 
3987                && boards[moveNum][toY][toX] == BlackRook)) {
3988           if(toX > fromX) SendToProgram("O-O\n", cps);
3989           else SendToProgram("O-O-O\n", cps);
3990         }
3991         else SendToProgram(moveList[moveNum], cps);
3992       }
3993       else SendToProgram(moveList[moveNum], cps);
3994       /* End of additions by Tord */
3995     }
3996
3997     /* [HGM] setting up the opening has brought engine in force mode! */
3998     /*       Send 'go' if we are in a mode where machine should play. */
3999     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4000         (gameMode == TwoMachinesPlay   ||
4001 #ifdef ZIPPY
4002          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4003 #endif
4004          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4005         SendToProgram("go\n", cps);
4006   if (appData.debugMode) {
4007     fprintf(debugFP, "(extra)\n");
4008   }
4009     }
4010     setboardSpoiledMachineBlack = 0;
4011 }
4012
4013 void
4014 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4015      ChessMove moveType;
4016      int fromX, fromY, toX, toY;
4017 {
4018     char user_move[MSG_SIZ];
4019
4020     switch (moveType) {
4021       default:
4022         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4023                 (int)moveType, fromX, fromY, toX, toY);
4024         DisplayError(user_move + strlen("say "), 0);
4025         break;
4026       case WhiteKingSideCastle:
4027       case BlackKingSideCastle:
4028       case WhiteQueenSideCastleWild:
4029       case BlackQueenSideCastleWild:
4030       /* PUSH Fabien */
4031       case WhiteHSideCastleFR:
4032       case BlackHSideCastleFR:
4033       /* POP Fabien */
4034         sprintf(user_move, "o-o\n");
4035         break;
4036       case WhiteQueenSideCastle:
4037       case BlackQueenSideCastle:
4038       case WhiteKingSideCastleWild:
4039       case BlackKingSideCastleWild:
4040       /* PUSH Fabien */
4041       case WhiteASideCastleFR:
4042       case BlackASideCastleFR:
4043       /* POP Fabien */
4044         sprintf(user_move, "o-o-o\n");
4045         break;
4046       case WhitePromotionQueen:
4047       case BlackPromotionQueen:
4048       case WhitePromotionRook:
4049       case BlackPromotionRook:
4050       case WhitePromotionBishop:
4051       case BlackPromotionBishop:
4052       case WhitePromotionKnight:
4053       case BlackPromotionKnight:
4054       case WhitePromotionKing:
4055       case BlackPromotionKing:
4056       case WhitePromotionChancellor:
4057       case BlackPromotionChancellor:
4058       case WhitePromotionArchbishop:
4059       case BlackPromotionArchbishop:
4060         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4061             sprintf(user_move, "%c%c%c%c=%c\n",
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063                 PieceToChar(WhiteFerz));
4064         else if(gameInfo.variant == VariantGreat)
4065             sprintf(user_move, "%c%c%c%c=%c\n",
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067                 PieceToChar(WhiteMan));
4068         else
4069             sprintf(user_move, "%c%c%c%c=%c\n",
4070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071                 PieceToChar(PromoPiece(moveType)));
4072         break;
4073       case WhiteDrop:
4074       case BlackDrop:
4075         sprintf(user_move, "%c@%c%c\n",
4076                 ToUpper(PieceToChar((ChessSquare) fromX)),
4077                 AAA + toX, ONE + toY);
4078         break;
4079       case NormalMove:
4080       case WhiteCapturesEnPassant:
4081       case BlackCapturesEnPassant:
4082       case IllegalMove:  /* could be a variant we don't quite understand */
4083         sprintf(user_move, "%c%c%c%c\n",
4084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4085         break;
4086     }
4087     SendToICS(user_move);
4088 }
4089
4090 void
4091 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4092      int rf, ff, rt, ft;
4093      char promoChar;
4094      char move[7];
4095 {
4096     if (rf == DROP_RANK) {
4097         sprintf(move, "%c@%c%c\n",
4098                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4099     } else {
4100         if (promoChar == 'x' || promoChar == NULLCHAR) {
4101             sprintf(move, "%c%c%c%c\n",
4102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4103         } else {
4104             sprintf(move, "%c%c%c%c%c\n",
4105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4106         }
4107     }
4108 }
4109
4110 void
4111 ProcessICSInitScript(f)
4112      FILE *f;
4113 {
4114     char buf[MSG_SIZ];
4115
4116     while (fgets(buf, MSG_SIZ, f)) {
4117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4118     }
4119
4120     fclose(f);
4121 }
4122
4123
4124 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4125 void
4126 AlphaRank(char *move, int n)
4127 {
4128 //    char *p = move, c; int x, y;
4129
4130     if (appData.debugMode) {
4131         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4132     }
4133
4134     if(move[1]=='*' && 
4135        move[2]>='0' && move[2]<='9' &&
4136        move[3]>='a' && move[3]<='x'    ) {
4137         move[1] = '@';
4138         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4139         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4140     } else
4141     if(move[0]>='0' && move[0]<='9' &&
4142        move[1]>='a' && move[1]<='x' &&
4143        move[2]>='0' && move[2]<='9' &&
4144        move[3]>='a' && move[3]<='x'    ) {
4145         /* input move, Shogi -> normal */
4146         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4147         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4148         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4149         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4150     } else
4151     if(move[1]=='@' &&
4152        move[3]>='0' && move[3]<='9' &&
4153        move[2]>='a' && move[2]<='x'    ) {
4154         move[1] = '*';
4155         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4156         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4157     } else
4158     if(
4159        move[0]>='a' && move[0]<='x' &&
4160        move[3]>='0' && move[3]<='9' &&
4161        move[2]>='a' && move[2]<='x'    ) {
4162          /* output move, normal -> Shogi */
4163         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4164         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4165         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4166         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4167         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4168     }
4169     if (appData.debugMode) {
4170         fprintf(debugFP, "   out = '%s'\n", move);
4171     }
4172 }
4173
4174 /* Parser for moves from gnuchess, ICS, or user typein box */
4175 Boolean
4176 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4177      char *move;
4178      int moveNum;
4179      ChessMove *moveType;
4180      int *fromX, *fromY, *toX, *toY;
4181      char *promoChar;
4182 {       
4183     if (appData.debugMode) {
4184         fprintf(debugFP, "move to parse: %s\n", move);
4185     }
4186     *moveType = yylexstr(moveNum, move);
4187
4188     switch (*moveType) {
4189       case WhitePromotionChancellor:
4190       case BlackPromotionChancellor:
4191       case WhitePromotionArchbishop:
4192       case BlackPromotionArchbishop:
4193       case WhitePromotionQueen:
4194       case BlackPromotionQueen:
4195       case WhitePromotionRook:
4196       case BlackPromotionRook:
4197       case WhitePromotionBishop:
4198       case BlackPromotionBishop:
4199       case WhitePromotionKnight:
4200       case BlackPromotionKnight:
4201       case WhitePromotionKing:
4202       case BlackPromotionKing:
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case WhiteKingSideCastle:
4207       case WhiteQueenSideCastle:
4208       case BlackKingSideCastle:
4209       case BlackQueenSideCastle:
4210       case WhiteKingSideCastleWild:
4211       case WhiteQueenSideCastleWild:
4212       case BlackKingSideCastleWild:
4213       case BlackQueenSideCastleWild:
4214       /* Code added by Tord: */
4215       case WhiteHSideCastleFR:
4216       case WhiteASideCastleFR:
4217       case BlackHSideCastleFR:
4218       case BlackASideCastleFR:
4219       /* End of code added by Tord */
4220       case IllegalMove:         /* bug or odd chess variant */
4221         *fromX = currentMoveString[0] - AAA;
4222         *fromY = currentMoveString[1] - ONE;
4223         *toX = currentMoveString[2] - AAA;
4224         *toY = currentMoveString[3] - ONE;
4225         *promoChar = currentMoveString[4];
4226         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4227             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4228     if (appData.debugMode) {
4229         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4230     }
4231             *fromX = *fromY = *toX = *toY = 0;
4232             return FALSE;
4233         }
4234         if (appData.testLegality) {
4235           return (*moveType != IllegalMove);
4236         } else {
4237           return !(fromX == fromY && toX == toY);
4238         }
4239
4240       case WhiteDrop:
4241       case BlackDrop:
4242         *fromX = *moveType == WhiteDrop ?
4243           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4244           (int) CharToPiece(ToLower(currentMoveString[0]));
4245         *fromY = DROP_RANK;
4246         *toX = currentMoveString[2] - AAA;
4247         *toY = currentMoveString[3] - ONE;
4248         *promoChar = NULLCHAR;
4249         return TRUE;
4250
4251       case AmbiguousMove:
4252       case ImpossibleMove:
4253       case (ChessMove) 0:       /* end of file */
4254       case ElapsedTime:
4255       case Comment:
4256       case PGNTag:
4257       case NAG:
4258       case WhiteWins:
4259       case BlackWins:
4260       case GameIsDrawn:
4261       default:
4262     if (appData.debugMode) {
4263         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4264     }
4265         /* bug? */
4266         *fromX = *fromY = *toX = *toY = 0;
4267         *promoChar = NULLCHAR;
4268         return FALSE;
4269     }
4270 }
4271
4272 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4273 // All positions will have equal probability, but the current method will not provide a unique
4274 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4275 #define DARK 1
4276 #define LITE 2
4277 #define ANY 3
4278
4279 int squaresLeft[4];
4280 int piecesLeft[(int)BlackPawn];
4281 int seed, nrOfShuffles;
4282
4283 void GetPositionNumber()
4284 {       // sets global variable seed
4285         int i;
4286
4287         seed = appData.defaultFrcPosition;
4288         if(seed < 0) { // randomize based on time for negative FRC position numbers
4289                 for(i=0; i<50; i++) seed += random();
4290                 seed = random() ^ random() >> 8 ^ random() << 8;
4291                 if(seed<0) seed = -seed;
4292         }
4293 }
4294
4295 int put(Board board, int pieceType, int rank, int n, int shade)
4296 // put the piece on the (n-1)-th empty squares of the given shade
4297 {
4298         int i;
4299
4300         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4301                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4302                         board[rank][i] = (ChessSquare) pieceType;
4303                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4304                         squaresLeft[ANY]--;
4305                         piecesLeft[pieceType]--; 
4306                         return i;
4307                 }
4308         }
4309         return -1;
4310 }
4311
4312
4313 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4314 // calculate where the next piece goes, (any empty square), and put it there
4315 {
4316         int i;
4317
4318         i = seed % squaresLeft[shade];
4319         nrOfShuffles *= squaresLeft[shade];
4320         seed /= squaresLeft[shade];
4321         put(board, pieceType, rank, i, shade);
4322 }
4323
4324 void AddTwoPieces(Board board, int pieceType, int rank)
4325 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4326 {
4327         int i, n=squaresLeft[ANY], j=n-1, k;
4328
4329         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4330         i = seed % k;  // pick one
4331         nrOfShuffles *= k;
4332         seed /= k;
4333         while(i >= j) i -= j--;
4334         j = n - 1 - j; i += j;
4335         put(board, pieceType, rank, j, ANY);
4336         put(board, pieceType, rank, i, ANY);
4337 }
4338
4339 void SetUpShuffle(Board board, int number)
4340 {
4341         int i, p, first=1;
4342
4343         GetPositionNumber(); nrOfShuffles = 1;
4344
4345         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4346         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4347         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4348
4349         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4350
4351         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4352             p = (int) board[0][i];
4353             if(p < (int) BlackPawn) piecesLeft[p] ++;
4354             board[0][i] = EmptySquare;
4355         }
4356
4357         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4358             // shuffles restricted to allow normal castling put KRR first
4359             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4360                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4361             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4362                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4363             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4364                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4365             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4366                 put(board, WhiteRook, 0, 0, ANY);
4367             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4368         }
4369
4370         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4371             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4372             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4373                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4374                 while(piecesLeft[p] >= 2) {
4375                     AddOnePiece(board, p, 0, LITE);
4376                     AddOnePiece(board, p, 0, DARK);
4377                 }
4378                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4379             }
4380
4381         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4382             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4383             // but we leave King and Rooks for last, to possibly obey FRC restriction
4384             if(p == (int)WhiteRook) continue;
4385             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4386             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4387         }
4388
4389         // now everything is placed, except perhaps King (Unicorn) and Rooks
4390
4391         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4392             // Last King gets castling rights
4393             while(piecesLeft[(int)WhiteUnicorn]) {
4394                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4395                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4396             }
4397
4398             while(piecesLeft[(int)WhiteKing]) {
4399                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4400                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4401             }
4402
4403
4404         } else {
4405             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4406             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4407         }
4408
4409         // Only Rooks can be left; simply place them all
4410         while(piecesLeft[(int)WhiteRook]) {
4411                 i = put(board, WhiteRook, 0, 0, ANY);
4412                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4413                         if(first) {
4414                                 first=0;
4415                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4416                         }
4417                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4418                 }
4419         }
4420         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4421             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4422         }
4423
4424         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4425 }
4426
4427 int SetCharTable( char *table, const char * map )
4428 /* [HGM] moved here from winboard.c because of its general usefulness */
4429 /*       Basically a safe strcpy that uses the last character as King */
4430 {
4431     int result = FALSE; int NrPieces;
4432
4433     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4434                     && NrPieces >= 12 && !(NrPieces&1)) {
4435         int i; /* [HGM] Accept even length from 12 to 34 */
4436
4437         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4438         for( i=0; i<NrPieces/2-1; i++ ) {
4439             table[i] = map[i];
4440             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4441         }
4442         table[(int) WhiteKing]  = map[NrPieces/2-1];
4443         table[(int) BlackKing]  = map[NrPieces-1];
4444
4445         result = TRUE;
4446     }
4447
4448     return result;
4449 }
4450
4451 void Prelude(Board board)
4452 {       // [HGM] superchess: random selection of exo-pieces
4453         int i, j, k; ChessSquare p; 
4454         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4455
4456         GetPositionNumber(); // use FRC position number
4457
4458         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4459             SetCharTable(pieceToChar, appData.pieceToCharTable);
4460             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4461                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4462         }
4463
4464         j = seed%4;                 seed /= 4; 
4465         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4466         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4467         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4468         j = seed%3 + (seed%3 >= j); seed /= 3; 
4469         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4470         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4471         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4472         j = seed%3;                 seed /= 3; 
4473         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4474         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4475         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4476         j = seed%2 + (seed%2 >= j); seed /= 2; 
4477         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4478         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4479         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4480         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4481         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4482         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4483         put(board, exoPieces[0],    0, 0, ANY);
4484         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4485 }
4486
4487 void
4488 InitPosition(redraw)
4489      int redraw;
4490 {
4491     ChessSquare (* pieces)[BOARD_SIZE];
4492     int i, j, pawnRow, overrule,
4493     oldx = gameInfo.boardWidth,
4494     oldy = gameInfo.boardHeight,
4495     oldh = gameInfo.holdingsWidth,
4496     oldv = gameInfo.variant;
4497
4498     currentMove = forwardMostMove = backwardMostMove = 0;
4499     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4500
4501     /* [AS] Initialize pv info list [HGM] and game status */
4502     {
4503         for( i=0; i<MAX_MOVES; i++ ) {
4504             pvInfoList[i].depth = 0;
4505             epStatus[i]=EP_NONE;
4506             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4507         }
4508
4509         initialRulePlies = 0; /* 50-move counter start */
4510
4511         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4512         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4513     }
4514
4515     
4516     /* [HGM] logic here is completely changed. In stead of full positions */
4517     /* the initialized data only consist of the two backranks. The switch */
4518     /* selects which one we will use, which is than copied to the Board   */
4519     /* initialPosition, which for the rest is initialized by Pawns and    */
4520     /* empty squares. This initial position is then copied to boards[0],  */
4521     /* possibly after shuffling, so that it remains available.            */
4522
4523     gameInfo.holdingsWidth = 0; /* default board sizes */
4524     gameInfo.boardWidth    = 8;
4525     gameInfo.boardHeight   = 8;
4526     gameInfo.holdingsSize  = 0;
4527     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4528     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4529     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4530
4531     switch (gameInfo.variant) {
4532     case VariantFischeRandom:
4533       shuffleOpenings = TRUE;
4534     default:
4535       pieces = FIDEArray;
4536       break;
4537     case VariantShatranj:
4538       pieces = ShatranjArray;
4539       nrCastlingRights = 0;
4540       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4541       break;
4542     case VariantTwoKings:
4543       pieces = twoKingsArray;
4544       break;
4545     case VariantCapaRandom:
4546       shuffleOpenings = TRUE;
4547     case VariantCapablanca:
4548       pieces = CapablancaArray;
4549       gameInfo.boardWidth = 10;
4550       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4551       break;
4552     case VariantGothic:
4553       pieces = GothicArray;
4554       gameInfo.boardWidth = 10;
4555       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4556       break;
4557     case VariantJanus:
4558       pieces = JanusArray;
4559       gameInfo.boardWidth = 10;
4560       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4561       nrCastlingRights = 6;
4562         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4563         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4564         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4565         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4566         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4567         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4568       break;
4569     case VariantFalcon:
4570       pieces = FalconArray;
4571       gameInfo.boardWidth = 10;
4572       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4573       break;
4574     case VariantXiangqi:
4575       pieces = XiangqiArray;
4576       gameInfo.boardWidth  = 9;
4577       gameInfo.boardHeight = 10;
4578       nrCastlingRights = 0;
4579       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4580       break;
4581     case VariantShogi:
4582       pieces = ShogiArray;
4583       gameInfo.boardWidth  = 9;
4584       gameInfo.boardHeight = 9;
4585       gameInfo.holdingsSize = 7;
4586       nrCastlingRights = 0;
4587       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4588       break;
4589     case VariantCourier:
4590       pieces = CourierArray;
4591       gameInfo.boardWidth  = 12;
4592       nrCastlingRights = 0;
4593       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4594       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4595       break;
4596     case VariantKnightmate:
4597       pieces = KnightmateArray;
4598       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4599       break;
4600     case VariantFairy:
4601       pieces = fairyArray;
4602       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4603       break;
4604     case VariantGreat:
4605       pieces = GreatArray;
4606       gameInfo.boardWidth = 10;
4607       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4608       gameInfo.holdingsSize = 8;
4609       break;
4610     case VariantSuper:
4611       pieces = FIDEArray;
4612       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4613       gameInfo.holdingsSize = 8;
4614       startedFromSetupPosition = TRUE;
4615       break;
4616     case VariantCrazyhouse:
4617     case VariantBughouse:
4618       pieces = FIDEArray;
4619       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4620       gameInfo.holdingsSize = 5;
4621       break;
4622     case VariantWildCastle:
4623       pieces = FIDEArray;
4624       /* !!?shuffle with kings guaranteed to be on d or e file */
4625       shuffleOpenings = 1;
4626       break;
4627     case VariantNoCastle:
4628       pieces = FIDEArray;
4629       nrCastlingRights = 0;
4630       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4631       /* !!?unconstrained back-rank shuffle */
4632       shuffleOpenings = 1;
4633       break;
4634     }
4635
4636     overrule = 0;
4637     if(appData.NrFiles >= 0) {
4638         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4639         gameInfo.boardWidth = appData.NrFiles;
4640     }
4641     if(appData.NrRanks >= 0) {
4642         gameInfo.boardHeight = appData.NrRanks;
4643     }
4644     if(appData.holdingsSize >= 0) {
4645         i = appData.holdingsSize;
4646         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4647         gameInfo.holdingsSize = i;
4648     }
4649     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4650     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4651         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4652
4653     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4654     if(pawnRow < 1) pawnRow = 1;
4655
4656     /* User pieceToChar list overrules defaults */
4657     if(appData.pieceToCharTable != NULL)
4658         SetCharTable(pieceToChar, appData.pieceToCharTable);
4659
4660     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4661
4662         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4663             s = (ChessSquare) 0; /* account holding counts in guard band */
4664         for( i=0; i<BOARD_HEIGHT; i++ )
4665             initialPosition[i][j] = s;
4666
4667         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4668         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4669         initialPosition[pawnRow][j] = WhitePawn;
4670         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4671         if(gameInfo.variant == VariantXiangqi) {
4672             if(j&1) {
4673                 initialPosition[pawnRow][j] = 
4674                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4675                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4676                    initialPosition[2][j] = WhiteCannon;
4677                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4678                 }
4679             }
4680         }
4681         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4682     }
4683     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4684
4685             j=BOARD_LEFT+1;
4686             initialPosition[1][j] = WhiteBishop;
4687             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4688             j=BOARD_RGHT-2;
4689             initialPosition[1][j] = WhiteRook;
4690             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4691     }
4692
4693     if( nrCastlingRights == -1) {
4694         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4695         /*       This sets default castling rights from none to normal corners   */
4696         /* Variants with other castling rights must set them themselves above    */
4697         nrCastlingRights = 6;
4698        
4699         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4700         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4701         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4702         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4703         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4704         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4705      }
4706
4707      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4708      if(gameInfo.variant == VariantGreat) { // promotion commoners
4709         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4710         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4711         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4712         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4713      }
4714 #if 0
4715     if(gameInfo.variant == VariantFischeRandom) {
4716       if( appData.defaultFrcPosition < 0 ) {
4717         ShuffleFRC( initialPosition );
4718       }
4719       else {
4720         SetupFRC( initialPosition, appData.defaultFrcPosition );
4721       }
4722       startedFromSetupPosition = TRUE;
4723     } else 
4724 #else
4725   if (appData.debugMode) {
4726     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4727   }
4728     if(shuffleOpenings) {
4729         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4730         startedFromSetupPosition = TRUE;
4731     }
4732 #endif
4733     if(startedFromPositionFile) {
4734       /* [HGM] loadPos: use PositionFile for every new game */
4735       CopyBoard(initialPosition, filePosition);
4736       for(i=0; i<nrCastlingRights; i++)
4737           castlingRights[0][i] = initialRights[i] = fileRights[i];
4738       startedFromSetupPosition = TRUE;
4739     }
4740
4741     CopyBoard(boards[0], initialPosition);
4742
4743     if(oldx != gameInfo.boardWidth ||
4744        oldy != gameInfo.boardHeight ||
4745        oldh != gameInfo.holdingsWidth
4746 #ifdef GOTHIC
4747        || oldv == VariantGothic ||        // For licensing popups
4748        gameInfo.variant == VariantGothic
4749 #endif
4750 #ifdef FALCON
4751        || oldv == VariantFalcon ||
4752        gameInfo.variant == VariantFalcon
4753 #endif
4754                                          )
4755             InitDrawingSizes(-2 ,0);
4756
4757     if (redraw)
4758       DrawPosition(TRUE, boards[currentMove]);
4759 }
4760
4761 void
4762 SendBoard(cps, moveNum)
4763      ChessProgramState *cps;
4764      int moveNum;
4765 {
4766     char message[MSG_SIZ];
4767     
4768     if (cps->useSetboard) {
4769       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4770       sprintf(message, "setboard %s\n", fen);
4771       SendToProgram(message, cps);
4772       free(fen);
4773
4774     } else {
4775       ChessSquare *bp;
4776       int i, j;
4777       /* Kludge to set black to move, avoiding the troublesome and now
4778        * deprecated "black" command.
4779        */
4780       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4781
4782       SendToProgram("edit\n", cps);
4783       SendToProgram("#\n", cps);
4784       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4785         bp = &boards[moveNum][i][BOARD_LEFT];
4786         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4787           if ((int) *bp < (int) BlackPawn) {
4788             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4789                     AAA + j, ONE + i);
4790             if(message[0] == '+' || message[0] == '~') {
4791                 sprintf(message, "%c%c%c+\n",
4792                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4793                         AAA + j, ONE + i);
4794             }
4795             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4796                 message[1] = BOARD_RGHT   - 1 - j + '1';
4797                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4798             }
4799             SendToProgram(message, cps);
4800           }
4801         }
4802       }
4803     
4804       SendToProgram("c\n", cps);
4805       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4806         bp = &boards[moveNum][i][BOARD_LEFT];
4807         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4808           if (((int) *bp != (int) EmptySquare)
4809               && ((int) *bp >= (int) BlackPawn)) {
4810             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4811                     AAA + j, ONE + i);
4812             if(message[0] == '+' || message[0] == '~') {
4813                 sprintf(message, "%c%c%c+\n",
4814                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4815                         AAA + j, ONE + i);
4816             }
4817             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4818                 message[1] = BOARD_RGHT   - 1 - j + '1';
4819                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4820             }
4821             SendToProgram(message, cps);
4822           }
4823         }
4824       }
4825     
4826       SendToProgram(".\n", cps);
4827     }
4828     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4829 }
4830
4831 int
4832 IsPromotion(fromX, fromY, toX, toY)
4833      int fromX, fromY, toX, toY;
4834 {
4835     /* [HGM] add Shogi promotions */
4836     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4837     ChessSquare piece;
4838
4839     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4840       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4841    /* [HGM] Note to self: line above also weeds out drops */
4842     piece = boards[currentMove][fromY][fromX];
4843     if(gameInfo.variant == VariantShogi) {
4844         promotionZoneSize = 3;
4845         highestPromotingPiece = (int)WhiteKing;
4846         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4847            and if in normal chess we then allow promotion to King, why not
4848            allow promotion of other piece in Shogi?                         */
4849     }
4850     if((int)piece >= BlackPawn) {
4851         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4852              return FALSE;
4853         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4854     } else {
4855         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4856            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4857     }
4858     return ( (int)piece <= highestPromotingPiece );
4859 }
4860
4861 int
4862 InPalace(row, column)
4863      int row, column;
4864 {   /* [HGM] for Xiangqi */
4865     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4866          column < (BOARD_WIDTH + 4)/2 &&
4867          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4868     return FALSE;
4869 }
4870
4871 int
4872 PieceForSquare (x, y)
4873      int x;
4874      int y;
4875 {
4876   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4877      return -1;
4878   else
4879      return boards[currentMove][y][x];
4880 }
4881
4882 int
4883 OKToStartUserMove(x, y)
4884      int x, y;
4885 {
4886     ChessSquare from_piece;
4887     int white_piece;
4888
4889     if (matchMode) return FALSE;
4890     if (gameMode == EditPosition) return TRUE;
4891
4892     if (x >= 0 && y >= 0)
4893       from_piece = boards[currentMove][y][x];
4894     else
4895       from_piece = EmptySquare;
4896
4897     if (from_piece == EmptySquare) return FALSE;
4898
4899     white_piece = (int)from_piece >= (int)WhitePawn &&
4900       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4901
4902     switch (gameMode) {
4903       case PlayFromGameFile:
4904       case AnalyzeFile:
4905       case TwoMachinesPlay:
4906       case EndOfGame:
4907         return FALSE;
4908
4909       case IcsObserving:
4910       case IcsIdle:
4911         return FALSE;
4912
4913       case MachinePlaysWhite:
4914       case IcsPlayingBlack:
4915         if (appData.zippyPlay) return FALSE;
4916         if (white_piece) {
4917             DisplayMoveError(_("You are playing Black"));
4918             return FALSE;
4919         }
4920         break;
4921
4922       case MachinePlaysBlack:
4923       case IcsPlayingWhite:
4924         if (appData.zippyPlay) return FALSE;
4925         if (!white_piece) {
4926             DisplayMoveError(_("You are playing White"));
4927             return FALSE;
4928         }
4929         break;
4930
4931       case EditGame:
4932         if (!white_piece && WhiteOnMove(currentMove)) {
4933             DisplayMoveError(_("It is White's turn"));
4934             return FALSE;
4935         }           
4936         if (white_piece && !WhiteOnMove(currentMove)) {
4937             DisplayMoveError(_("It is Black's turn"));
4938             return FALSE;
4939         }           
4940         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4941             /* Editing correspondence game history */
4942             /* Could disallow this or prompt for confirmation */
4943             cmailOldMove = -1;
4944         }
4945         if (currentMove < forwardMostMove) {
4946             /* Discarding moves */
4947             /* Could prompt for confirmation here,
4948                but I don't think that's such a good idea */
4949             forwardMostMove = currentMove;
4950         }
4951         break;
4952
4953       case BeginningOfGame:
4954         if (appData.icsActive) return FALSE;
4955         if (!appData.noChessProgram) {
4956             if (!white_piece) {
4957                 DisplayMoveError(_("You are playing White"));
4958                 return FALSE;
4959             }
4960         }
4961         break;
4962         
4963       case Training:
4964         if (!white_piece && WhiteOnMove(currentMove)) {
4965             DisplayMoveError(_("It is White's turn"));
4966             return FALSE;
4967         }           
4968         if (white_piece && !WhiteOnMove(currentMove)) {
4969             DisplayMoveError(_("It is Black's turn"));
4970             return FALSE;
4971         }           
4972         break;
4973
4974       default:
4975       case IcsExamining:
4976         break;
4977     }
4978     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4979         && gameMode != AnalyzeFile && gameMode != Training) {
4980         DisplayMoveError(_("Displayed position is not current"));
4981         return FALSE;
4982     }
4983     return TRUE;
4984 }
4985
4986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4988 int lastLoadGameUseList = FALSE;
4989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4990 ChessMove lastLoadGameStart = (ChessMove) 0;
4991
4992
4993 ChessMove
4994 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4995      int fromX, fromY, toX, toY;
4996      int promoChar;
4997 {
4998     ChessMove moveType;
4999     ChessSquare pdown, pup;
5000
5001     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5002     if ((fromX == toX) && (fromY == toY)) {
5003         return ImpossibleMove;
5004     }
5005
5006     /* [HGM] suppress all moves into holdings area and guard band */
5007     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5008             return ImpossibleMove;
5009
5010     /* [HGM] <sameColor> moved to here from winboard.c */
5011     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5012     pdown = boards[currentMove][fromY][fromX];
5013     pup = boards[currentMove][toY][toX];
5014     if (    gameMode != EditPosition &&
5015             (WhitePawn <= pdown && pdown < BlackPawn &&
5016              WhitePawn <= pup && pup < BlackPawn  ||
5017              BlackPawn <= pdown && pdown < EmptySquare &&
5018              BlackPawn <= pup && pup < EmptySquare 
5019             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5020                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5021                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5022         )           )
5023          return ImpossibleMove;
5024
5025     /* Check if the user is playing in turn.  This is complicated because we
5026        let the user "pick up" a piece before it is his turn.  So the piece he
5027        tried to pick up may have been captured by the time he puts it down!
5028        Therefore we use the color the user is supposed to be playing in this
5029        test, not the color of the piece that is currently on the starting
5030        square---except in EditGame mode, where the user is playing both
5031        sides; fortunately there the capture race can't happen.  (It can
5032        now happen in IcsExamining mode, but that's just too bad.  The user
5033        will get a somewhat confusing message in that case.)
5034        */
5035
5036     switch (gameMode) {
5037       case PlayFromGameFile:
5038       case AnalyzeFile:
5039       case TwoMachinesPlay:
5040       case EndOfGame:
5041       case IcsObserving:
5042       case IcsIdle:
5043         /* We switched into a game mode where moves are not accepted,
5044            perhaps while the mouse button was down. */
5045         return ImpossibleMove;
5046
5047       case MachinePlaysWhite:
5048         /* User is moving for Black */
5049         if (WhiteOnMove(currentMove)) {
5050             DisplayMoveError(_("It is White's turn"));
5051             return ImpossibleMove;
5052         }
5053         break;
5054
5055       case MachinePlaysBlack:
5056         /* User is moving for White */
5057         if (!WhiteOnMove(currentMove)) {
5058             DisplayMoveError(_("It is Black's turn"));
5059             return ImpossibleMove;
5060         }
5061         break;
5062
5063       case EditGame:
5064       case IcsExamining:
5065       case BeginningOfGame:
5066       case AnalyzeMode:
5067       case Training:
5068         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5069             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5070             /* User is moving for Black */
5071             if (WhiteOnMove(currentMove)) {
5072                 DisplayMoveError(_("It is White's turn"));
5073                 return ImpossibleMove;
5074             }
5075         } else {
5076             /* User is moving for White */
5077             if (!WhiteOnMove(currentMove)) {
5078                 DisplayMoveError(_("It is Black's turn"));
5079                 return ImpossibleMove;
5080             }
5081         }
5082         break;
5083
5084       case IcsPlayingBlack:
5085         /* User is moving for Black */
5086         if (WhiteOnMove(currentMove)) {
5087             if (!appData.premove) {
5088                 DisplayMoveError(_("It is White's turn"));
5089             } else if (toX >= 0 && toY >= 0) {
5090                 premoveToX = toX;
5091                 premoveToY = toY;
5092                 premoveFromX = fromX;
5093                 premoveFromY = fromY;
5094                 premovePromoChar = promoChar;
5095                 gotPremove = 1;
5096                 if (appData.debugMode) 
5097                     fprintf(debugFP, "Got premove: fromX %d,"
5098                             "fromY %d, toX %d, toY %d\n",
5099                             fromX, fromY, toX, toY);
5100             }
5101             return ImpossibleMove;
5102         }
5103         break;
5104
5105       case IcsPlayingWhite:
5106         /* User is moving for White */
5107         if (!WhiteOnMove(currentMove)) {
5108             if (!appData.premove) {
5109                 DisplayMoveError(_("It is Black's turn"));
5110             } else if (toX >= 0 && toY >= 0) {
5111                 premoveToX = toX;
5112                 premoveToY = toY;
5113                 premoveFromX = fromX;
5114                 premoveFromY = fromY;
5115                 premovePromoChar = promoChar;
5116                 gotPremove = 1;
5117                 if (appData.debugMode) 
5118                     fprintf(debugFP, "Got premove: fromX %d,"
5119                             "fromY %d, toX %d, toY %d\n",
5120                             fromX, fromY, toX, toY);
5121             }
5122             return ImpossibleMove;
5123         }
5124         break;
5125
5126       default:
5127         break;
5128
5129       case EditPosition:
5130         /* EditPosition, empty square, or different color piece;
5131            click-click move is possible */
5132         if (toX == -2 || toY == -2) {
5133             boards[0][fromY][fromX] = EmptySquare;
5134             return AmbiguousMove;
5135         } else if (toX >= 0 && toY >= 0) {
5136             boards[0][toY][toX] = boards[0][fromY][fromX];
5137             boards[0][fromY][fromX] = EmptySquare;
5138             return AmbiguousMove;
5139         }
5140         return ImpossibleMove;
5141     }
5142
5143     /* [HGM] If move started in holdings, it means a drop */
5144     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5145          if( pup != EmptySquare ) return ImpossibleMove;
5146          if(appData.testLegality) {
5147              /* it would be more logical if LegalityTest() also figured out
5148               * which drops are legal. For now we forbid pawns on back rank.
5149               * Shogi is on its own here...
5150               */
5151              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5152                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5153                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5154          }
5155          return WhiteDrop; /* Not needed to specify white or black yet */
5156     }
5157
5158     userOfferedDraw = FALSE;
5159         
5160     /* [HGM] always test for legality, to get promotion info */
5161     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162                           epStatus[currentMove], castlingRights[currentMove],
5163                                          fromY, fromX, toY, toX, promoChar);
5164
5165     /* [HGM] but possibly ignore an IllegalMove result */
5166     if (appData.testLegality) {
5167         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5168             DisplayMoveError(_("Illegal move"));
5169             return ImpossibleMove;
5170         }
5171     }
5172 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5173     return moveType;
5174     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5175        function is made into one that returns an OK move type if FinishMove
5176        should be called. This to give the calling driver routine the
5177        opportunity to finish the userMove input with a promotion popup,
5178        without bothering the user with this for invalid or illegal moves */
5179
5180 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5181 }
5182
5183 /* Common tail of UserMoveEvent and DropMenuEvent */
5184 int
5185 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5186      ChessMove moveType;
5187      int fromX, fromY, toX, toY;
5188      /*char*/int promoChar;
5189 {
5190     char *bookHit = 0;
5191 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5192     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5193         // [HGM] superchess: suppress promotions to non-available piece
5194         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5195         if(WhiteOnMove(currentMove)) {
5196             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5197         } else {
5198             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5199         }
5200     }
5201
5202     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5203        move type in caller when we know the move is a legal promotion */
5204     if(moveType == NormalMove && promoChar)
5205         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5206 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5207     /* [HGM] convert drag-and-drop piece drops to standard form */
5208     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5209          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5210            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5211                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5212 //         fromX = boards[currentMove][fromY][fromX];
5213            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5214            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5215            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5216            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5217          fromY = DROP_RANK;
5218     }
5219
5220     /* [HGM] <popupFix> The following if has been moved here from
5221        UserMoveEvent(). Because it seemed to belon here (why not allow
5222        piece drops in training games?), and because it can only be
5223        performed after it is known to what we promote. */
5224     if (gameMode == Training) {
5225       /* compare the move played on the board to the next move in the
5226        * game. If they match, display the move and the opponent's response. 
5227        * If they don't match, display an error message.
5228        */
5229       int saveAnimate;
5230       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5231       CopyBoard(testBoard, boards[currentMove]);
5232       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5233
5234       if (CompareBoards(testBoard, boards[currentMove+1])) {
5235         ForwardInner(currentMove+1);
5236
5237         /* Autoplay the opponent's response.
5238          * if appData.animate was TRUE when Training mode was entered,
5239          * the response will be animated.
5240          */
5241         saveAnimate = appData.animate;
5242         appData.animate = animateTraining;
5243         ForwardInner(currentMove+1);
5244         appData.animate = saveAnimate;
5245
5246         /* check for the end of the game */
5247         if (currentMove >= forwardMostMove) {
5248           gameMode = PlayFromGameFile;
5249           ModeHighlight();
5250           SetTrainingModeOff();
5251           DisplayInformation(_("End of game"));
5252         }
5253       } else {
5254         DisplayError(_("Incorrect move"), 0);
5255       }
5256       return 1;
5257     }
5258
5259   /* Ok, now we know that the move is good, so we can kill
5260      the previous line in Analysis Mode */
5261   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5262     forwardMostMove = currentMove;
5263   }
5264
5265   /* If we need the chess program but it's dead, restart it */
5266   ResurrectChessProgram();
5267
5268   /* A user move restarts a paused game*/
5269   if (pausing)
5270     PauseEvent();
5271
5272   thinkOutput[0] = NULLCHAR;
5273
5274   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5275
5276   if (gameMode == BeginningOfGame) {
5277     if (appData.noChessProgram) {
5278       gameMode = EditGame;
5279       SetGameInfo();
5280     } else {
5281       char buf[MSG_SIZ];
5282       gameMode = MachinePlaysBlack;
5283       StartClocks();
5284       SetGameInfo();
5285       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5286       DisplayTitle(buf);
5287       if (first.sendName) {
5288         sprintf(buf, "name %s\n", gameInfo.white);
5289         SendToProgram(buf, &first);
5290       }
5291       StartClocks();
5292     }
5293     ModeHighlight();
5294   }
5295 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5296   /* Relay move to ICS or chess engine */
5297   if (appData.icsActive) {
5298     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5299         gameMode == IcsExamining) {
5300       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5301       ics_user_moved = 1;
5302     }
5303   } else {
5304     if (first.sendTime && (gameMode == BeginningOfGame ||
5305                            gameMode == MachinePlaysWhite ||
5306                            gameMode == MachinePlaysBlack)) {
5307       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5308     }
5309     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5310          // [HGM] book: if program might be playing, let it use book
5311         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5312         first.maybeThinking = TRUE;
5313     } else SendMoveToProgram(forwardMostMove-1, &first);
5314     if (currentMove == cmailOldMove + 1) {
5315       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5316     }
5317   }
5318
5319   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5320
5321   switch (gameMode) {
5322   case EditGame:
5323     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5324                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5325     case MT_NONE:
5326     case MT_CHECK:
5327       break;
5328     case MT_CHECKMATE:
5329     case MT_STAINMATE:
5330       if (WhiteOnMove(currentMove)) {
5331         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5332       } else {
5333         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5334       }
5335       break;
5336     case MT_STALEMATE:
5337       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5338       break;
5339     }
5340     break;
5341     
5342   case MachinePlaysBlack:
5343   case MachinePlaysWhite:
5344     /* disable certain menu options while machine is thinking */
5345     SetMachineThinkingEnables();
5346     break;
5347
5348   default:
5349     break;
5350   }
5351
5352   if(bookHit) { // [HGM] book: simulate book reply
5353         static char bookMove[MSG_SIZ]; // a bit generous?
5354
5355         programStats.nodes = programStats.depth = programStats.time = 
5356         programStats.score = programStats.got_only_move = 0;
5357         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5358
5359         strcpy(bookMove, "move ");
5360         strcat(bookMove, bookHit);
5361         HandleMachineMove(bookMove, &first);
5362   }
5363   return 1;
5364 }
5365
5366 void
5367 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5368      int fromX, fromY, toX, toY;
5369      int promoChar;
5370 {
5371     /* [HGM] This routine was added to allow calling of its two logical
5372        parts from other modules in the old way. Before, UserMoveEvent()
5373        automatically called FinishMove() if the move was OK, and returned
5374        otherwise. I separated the two, in order to make it possible to
5375        slip a promotion popup in between. But that it always needs two
5376        calls, to the first part, (now called UserMoveTest() ), and to
5377        FinishMove if the first part succeeded. Calls that do not need
5378        to do anything in between, can call this routine the old way. 
5379     */
5380     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5381 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5382     if(moveType != ImpossibleMove)
5383         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5384 }
5385
5386 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5387 {
5388 //    char * hint = lastHint;
5389     FrontEndProgramStats stats;
5390
5391     stats.which = cps == &first ? 0 : 1;
5392     stats.depth = cpstats->depth;
5393     stats.nodes = cpstats->nodes;
5394     stats.score = cpstats->score;
5395     stats.time = cpstats->time;
5396     stats.pv = cpstats->movelist;
5397     stats.hint = lastHint;
5398     stats.an_move_index = 0;
5399     stats.an_move_count = 0;
5400
5401     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5402         stats.hint = cpstats->move_name;
5403         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5404         stats.an_move_count = cpstats->nr_moves;
5405     }
5406
5407     SetProgramStats( &stats );
5408 }
5409
5410 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5411 {   // [HGM] book: this routine intercepts moves to simulate book replies
5412     char *bookHit = NULL;
5413
5414     //first determine if the incoming move brings opponent into his book
5415     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5416         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5417     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5418     if(bookHit != NULL && !cps->bookSuspend) {
5419         // make sure opponent is not going to reply after receiving move to book position
5420         SendToProgram("force\n", cps);
5421         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5422     }
5423     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5424     // now arrange restart after book miss
5425     if(bookHit) {
5426         // after a book hit we never send 'go', and the code after the call to this routine
5427         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5428         char buf[MSG_SIZ];
5429         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5430         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5431         SendToProgram(buf, cps);
5432         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5433     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5434         SendToProgram("go\n", cps);
5435         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5436     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5437         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5438             SendToProgram("go\n", cps); 
5439         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5440     }
5441     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5442 }
5443
5444 char *savedMessage;
5445 ChessProgramState *savedState;
5446 void DeferredBookMove(void)
5447 {
5448         if(savedState->lastPing != savedState->lastPong)
5449                     ScheduleDelayedEvent(DeferredBookMove, 10);
5450         else
5451         HandleMachineMove(savedMessage, savedState);
5452 }
5453
5454 void
5455 HandleMachineMove(message, cps)
5456      char *message;
5457      ChessProgramState *cps;
5458 {
5459     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5460     char realname[MSG_SIZ];
5461     int fromX, fromY, toX, toY;
5462     ChessMove moveType;
5463     char promoChar;
5464     char *p;
5465     int machineWhite;
5466     char *bookHit;
5467
5468 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5469     /*
5470      * Kludge to ignore BEL characters
5471      */
5472     while (*message == '\007') message++;
5473
5474     /*
5475      * [HGM] engine debug message: ignore lines starting with '#' character
5476      */
5477     if(cps->debug && *message == '#') return;
5478
5479     /*
5480      * Look for book output
5481      */
5482     if (cps == &first && bookRequested) {
5483         if (message[0] == '\t' || message[0] == ' ') {
5484             /* Part of the book output is here; append it */
5485             strcat(bookOutput, message);
5486             strcat(bookOutput, "  \n");
5487             return;
5488         } else if (bookOutput[0] != NULLCHAR) {
5489             /* All of book output has arrived; display it */
5490             char *p = bookOutput;
5491             while (*p != NULLCHAR) {
5492                 if (*p == '\t') *p = ' ';
5493                 p++;
5494             }
5495             DisplayInformation(bookOutput);
5496             bookRequested = FALSE;
5497             /* Fall through to parse the current output */
5498         }
5499     }
5500
5501     /*
5502      * Look for machine move.
5503      */
5504     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5505         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5506     {
5507         /* This method is only useful on engines that support ping */
5508         if (cps->lastPing != cps->lastPong) {
5509           if (gameMode == BeginningOfGame) {
5510             /* Extra move from before last new; ignore */
5511             if (appData.debugMode) {
5512                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5513             }
5514           } else {
5515             if (appData.debugMode) {
5516                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5517                         cps->which, gameMode);
5518             }
5519
5520             SendToProgram("undo\n", cps);
5521           }
5522           return;
5523         }
5524
5525         switch (gameMode) {
5526           case BeginningOfGame:
5527             /* Extra move from before last reset; ignore */
5528             if (appData.debugMode) {
5529                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5530             }
5531             return;
5532
5533           case EndOfGame:
5534           case IcsIdle:
5535           default:
5536             /* Extra move after we tried to stop.  The mode test is
5537                not a reliable way of detecting this problem, but it's
5538                the best we can do on engines that don't support ping.
5539             */
5540             if (appData.debugMode) {
5541                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5542                         cps->which, gameMode);
5543             }
5544             SendToProgram("undo\n", cps);
5545             return;
5546
5547           case MachinePlaysWhite:
5548           case IcsPlayingWhite:
5549             machineWhite = TRUE;
5550             break;
5551
5552           case MachinePlaysBlack:
5553           case IcsPlayingBlack:
5554             machineWhite = FALSE;
5555             break;
5556
5557           case TwoMachinesPlay:
5558             machineWhite = (cps->twoMachinesColor[0] == 'w');
5559             break;
5560         }
5561         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5562             if (appData.debugMode) {
5563                 fprintf(debugFP,
5564                         "Ignoring move out of turn by %s, gameMode %d"
5565                         ", forwardMost %d\n",
5566                         cps->which, gameMode, forwardMostMove);
5567             }
5568             return;
5569         }
5570
5571     if (appData.debugMode) { int f = forwardMostMove;
5572         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5573                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5574     }
5575         if(cps->alphaRank) AlphaRank(machineMove, 4);
5576         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5577                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5578             /* Machine move could not be parsed; ignore it. */
5579             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5580                     machineMove, cps->which);
5581             DisplayError(buf1, 0);
5582             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5583                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5584             if (gameMode == TwoMachinesPlay) {
5585               GameEnds(machineWhite ? BlackWins : WhiteWins,
5586                        buf1, GE_XBOARD);
5587             }
5588             return;
5589         }
5590
5591         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5592         /* So we have to redo legality test with true e.p. status here,  */
5593         /* to make sure an illegal e.p. capture does not slip through,   */
5594         /* to cause a forfeit on a justified illegal-move complaint      */
5595         /* of the opponent.                                              */
5596         if( gameMode==TwoMachinesPlay && appData.testLegality
5597             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5598                                                               ) {
5599            ChessMove moveType;
5600            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5601                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5602                              fromY, fromX, toY, toX, promoChar);
5603             if (appData.debugMode) {
5604                 int i;
5605                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5606                     castlingRights[forwardMostMove][i], castlingRank[i]);
5607                 fprintf(debugFP, "castling rights\n");
5608             }
5609             if(moveType == IllegalMove) {
5610                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5611                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5612                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5613                            buf1, GE_XBOARD);
5614                 return;
5615            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5616            /* [HGM] Kludge to handle engines that send FRC-style castling
5617               when they shouldn't (like TSCP-Gothic) */
5618            switch(moveType) {
5619              case WhiteASideCastleFR:
5620              case BlackASideCastleFR:
5621                toX+=2;
5622                currentMoveString[2]++;
5623                break;
5624              case WhiteHSideCastleFR:
5625              case BlackHSideCastleFR:
5626                toX--;
5627                currentMoveString[2]--;
5628                break;
5629              default: ; // nothing to do, but suppresses warning of pedantic compilers
5630            }
5631         }
5632         hintRequested = FALSE;
5633         lastHint[0] = NULLCHAR;
5634         bookRequested = FALSE;
5635         /* Program may be pondering now */
5636         cps->maybeThinking = TRUE;
5637         if (cps->sendTime == 2) cps->sendTime = 1;
5638         if (cps->offeredDraw) cps->offeredDraw--;
5639
5640 #if ZIPPY
5641         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5642             first.initDone) {
5643           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5644           ics_user_moved = 1;
5645           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5646                 char buf[3*MSG_SIZ];
5647
5648                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5649                         programStats.score / 100.,
5650                         programStats.depth,
5651                         programStats.time / 100.,
5652                         (unsigned int)programStats.nodes,
5653                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5654                         programStats.movelist);
5655                 SendToICS(buf);
5656           }
5657         }
5658 #endif
5659         /* currentMoveString is set as a side-effect of ParseOneMove */
5660         strcpy(machineMove, currentMoveString);
5661         strcat(machineMove, "\n");
5662         strcpy(moveList[forwardMostMove], machineMove);
5663
5664         /* [AS] Save move info and clear stats for next move */
5665         pvInfoList[ forwardMostMove ].score = programStats.score;
5666         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5667         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5668         ClearProgramStats();
5669         thinkOutput[0] = NULLCHAR;
5670         hiddenThinkOutputState = 0;
5671
5672         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5673
5674         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5675         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5676             int count = 0;
5677
5678             while( count < adjudicateLossPlies ) {
5679                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5680
5681                 if( count & 1 ) {
5682                     score = -score; /* Flip score for winning side */
5683                 }
5684
5685                 if( score > adjudicateLossThreshold ) {
5686                     break;
5687                 }
5688
5689                 count++;
5690             }
5691
5692             if( count >= adjudicateLossPlies ) {
5693                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5694
5695                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5696                     "Xboard adjudication", 
5697                     GE_XBOARD );
5698
5699                 return;
5700             }
5701         }
5702
5703         if( gameMode == TwoMachinesPlay ) {
5704           // [HGM] some adjudications useful with buggy engines
5705             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5706           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5707
5708
5709             if( appData.testLegality )
5710             {   /* [HGM] Some more adjudications for obstinate engines */
5711                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5712                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5713                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5714                 static int moveCount = 6;
5715                 ChessMove result;
5716                 char *reason = NULL;
5717
5718                 /* Count what is on board. */
5719                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5720                 {   ChessSquare p = boards[forwardMostMove][i][j];
5721                     int m=i;
5722
5723                     switch((int) p)
5724                     {   /* count B,N,R and other of each side */
5725                         case WhiteKing:
5726                         case BlackKing:
5727                              NrK++; break; // [HGM] atomic: count Kings
5728                         case WhiteKnight:
5729                              NrWN++; break;
5730                         case WhiteBishop:
5731                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5732                              bishopsColor |= 1 << ((i^j)&1);
5733                              NrWB++; break;
5734                         case BlackKnight:
5735                              NrBN++; break;
5736                         case BlackBishop:
5737                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5738                              bishopsColor |= 1 << ((i^j)&1);
5739                              NrBB++; break;
5740                         case WhiteRook:
5741                              NrWR++; break;
5742                         case BlackRook:
5743                              NrBR++; break;
5744                         case WhiteQueen:
5745                              NrWQ++; break;
5746                         case BlackQueen:
5747                              NrBQ++; break;
5748                         case EmptySquare: 
5749                              break;
5750                         case BlackPawn:
5751                              m = 7-i;
5752                         case WhitePawn:
5753                              PawnAdvance += m; NrPawns++;
5754                     }
5755                     NrPieces += (p != EmptySquare);
5756                     NrW += ((int)p < (int)BlackPawn);
5757                     if(gameInfo.variant == VariantXiangqi && 
5758                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5759                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5760                         NrW -= ((int)p < (int)BlackPawn);
5761                     }
5762                 }
5763
5764                 /* Some material-based adjudications that have to be made before stalemate test */
5765                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5766                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5767                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5768                      if(appData.checkMates) {
5769                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5770                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5771                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5772                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5773                          return;
5774                      }
5775                 }
5776
5777                 /* Bare King in Shatranj (loses) or Losers (wins) */
5778                 if( NrW == 1 || NrPieces - NrW == 1) {
5779                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5780                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5781                      if(appData.checkMates) {
5782                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5783                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5784                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5785                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5786                          return;
5787                      }
5788                   } else
5789                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5790                   {    /* bare King */
5791                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5792                         if(appData.checkMates) {
5793                             /* but only adjudicate if adjudication enabled */
5794                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5795                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5796                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5797                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5798                             return;
5799                         }
5800                   }
5801                 } else bare = 1;
5802
5803
5804             // don't wait for engine to announce game end if we can judge ourselves
5805             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5806                                        castlingRights[forwardMostMove]) ) {
5807               case MT_CHECK:
5808                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5809                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5810                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5811                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5812                             checkCnt++;
5813                         if(checkCnt >= 2) {
5814                             reason = "Xboard adjudication: 3rd check";
5815                             epStatus[forwardMostMove] = EP_CHECKMATE;
5816                             break;
5817                         }
5818                     }
5819                 }
5820               case MT_NONE:
5821               default:
5822                 break;
5823               case MT_STALEMATE:
5824               case MT_STAINMATE:
5825                 reason = "Xboard adjudication: Stalemate";
5826                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5827                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5828                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5829                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5830                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5831                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5832                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5833                                                                         EP_CHECKMATE : EP_WINS);
5834                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5835                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5836                 }
5837                 break;
5838               case MT_CHECKMATE:
5839                 reason = "Xboard adjudication: Checkmate";
5840                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5841                 break;
5842             }
5843
5844                 switch(i = epStatus[forwardMostMove]) {
5845                     case EP_STALEMATE:
5846                         result = GameIsDrawn; break;
5847                     case EP_CHECKMATE:
5848                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5849                     case EP_WINS:
5850                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5851                     default:
5852                         result = (ChessMove) 0;
5853                 }
5854                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5855                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5856                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5857                     GameEnds( result, reason, GE_XBOARD );
5858                     return;
5859                 }
5860
5861                 /* Next absolutely insufficient mating material. */
5862                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5863                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5864                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5865                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5866                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5867
5868                      /* always flag draws, for judging claims */
5869                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5870
5871                      if(appData.materialDraws) {
5872                          /* but only adjudicate them if adjudication enabled */
5873                          SendToProgram("force\n", cps->other); // suppress reply
5874                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5875                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5876                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5877                          return;
5878                      }
5879                 }
5880
5881                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5882                 if(NrPieces == 4 && 
5883                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5884                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5885                    || NrWN==2 || NrBN==2     /* KNNK */
5886                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5887                   ) ) {
5888                      if(--moveCount < 0 && appData.trivialDraws)
5889                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5890                           SendToProgram("force\n", cps->other); // suppress reply
5891                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5892                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5894                           return;
5895                      }
5896                 } else moveCount = 6;
5897             }
5898           }
5899 #if 1
5900     if (appData.debugMode) { int i;
5901       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5902               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5903               appData.drawRepeats);
5904       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5905            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5906
5907     }
5908 #endif
5909                 /* Check for rep-draws */
5910                 count = 0;
5911                 for(k = forwardMostMove-2;
5912                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5913                         epStatus[k] < EP_UNKNOWN &&
5914                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5915                     k-=2)
5916                 {   int rights=0;
5917 #if 0
5918     if (appData.debugMode) {
5919       fprintf(debugFP, " loop\n");
5920     }
5921 #endif
5922                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5923 #if 0
5924     if (appData.debugMode) {
5925       fprintf(debugFP, "match\n");
5926     }
5927 #endif
5928                         /* compare castling rights */
5929                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5930                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5931                                 rights++; /* King lost rights, while rook still had them */
5932                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5933                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5934                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5935                                    rights++; /* but at least one rook lost them */
5936                         }
5937                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5938                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5939                                 rights++; 
5940                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5941                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5942                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5943                                    rights++;
5944                         }
5945 #if 0
5946     if (appData.debugMode) {
5947       for(i=0; i<nrCastlingRights; i++)
5948       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5949     }
5950
5951     if (appData.debugMode) {
5952       fprintf(debugFP, " %d %d\n", rights, k);
5953     }
5954 #endif
5955                         if( rights == 0 && ++count > appData.drawRepeats-2
5956                             && appData.drawRepeats > 1) {
5957                              /* adjudicate after user-specified nr of repeats */
5958                              SendToProgram("force\n", cps->other); // suppress reply
5959                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5960                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5961                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5962                                 // [HGM] xiangqi: check for forbidden perpetuals
5963                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5964                                 for(m=forwardMostMove; m>k; m-=2) {
5965                                     if(MateTest(boards[m], PosFlags(m), 
5966                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5967                                         ourPerpetual = 0; // the current mover did not always check
5968                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5969                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5970                                         hisPerpetual = 0; // the opponent did not always check
5971                                 }
5972                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5973                                                                         ourPerpetual, hisPerpetual);
5974                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5975                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5976                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5977                                     return;
5978                                 }
5979                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5980                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5981                                 // Now check for perpetual chases
5982                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5983                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5984                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5985                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5986                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5987                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5988                                         return;
5989                                     }
5990                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5991                                         break; // Abort repetition-checking loop.
5992                                 }
5993                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5994                              }
5995                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5996                              return;
5997                         }
5998                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5999                              epStatus[forwardMostMove] = EP_REP_DRAW;
6000                     }
6001                 }
6002
6003                 /* Now we test for 50-move draws. Determine ply count */
6004                 count = forwardMostMove;
6005                 /* look for last irreversble move */
6006                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6007                     count--;
6008                 /* if we hit starting position, add initial plies */
6009                 if( count == backwardMostMove )
6010                     count -= initialRulePlies;
6011                 count = forwardMostMove - count; 
6012                 if( count >= 100)
6013                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6014                          /* this is used to judge if draw claims are legal */
6015                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6016                          SendToProgram("force\n", cps->other); // suppress reply
6017                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6018                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6019                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6020                          return;
6021                 }
6022
6023                 /* if draw offer is pending, treat it as a draw claim
6024                  * when draw condition present, to allow engines a way to
6025                  * claim draws before making their move to avoid a race
6026                  * condition occurring after their move
6027                  */
6028                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6029                          char *p = NULL;
6030                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6031                              p = "Draw claim: 50-move rule";
6032                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6033                              p = "Draw claim: 3-fold repetition";
6034                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6035                              p = "Draw claim: insufficient mating material";
6036                          if( p != NULL ) {
6037                              SendToProgram("force\n", cps->other); // suppress reply
6038                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6039                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6040                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6041                              return;
6042                          }
6043                 }
6044
6045
6046                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6047                     SendToProgram("force\n", cps->other); // suppress reply
6048                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6049                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6050
6051                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6052
6053                     return;
6054                 }
6055         }
6056
6057         bookHit = NULL;
6058         if (gameMode == TwoMachinesPlay) {
6059             /* [HGM] relaying draw offers moved to after reception of move */
6060             /* and interpreting offer as claim if it brings draw condition */
6061             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6062                 SendToProgram("draw\n", cps->other);
6063             }
6064             if (cps->other->sendTime) {
6065                 SendTimeRemaining(cps->other,
6066                                   cps->other->twoMachinesColor[0] == 'w');
6067             }
6068             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6069             if (firstMove && !bookHit) {
6070                 firstMove = FALSE;
6071                 if (cps->other->useColors) {
6072                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6073                 }
6074                 SendToProgram("go\n", cps->other);
6075             }
6076             cps->other->maybeThinking = TRUE;
6077         }
6078
6079         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6080         
6081         if (!pausing && appData.ringBellAfterMoves) {
6082             RingBell();
6083         }
6084
6085         /* 
6086          * Reenable menu items that were disabled while
6087          * machine was thinking
6088          */
6089         if (gameMode != TwoMachinesPlay)
6090             SetUserThinkingEnables();
6091
6092         // [HGM] book: after book hit opponent has received move and is now in force mode
6093         // force the book reply into it, and then fake that it outputted this move by jumping
6094         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6095         if(bookHit) {
6096                 static char bookMove[MSG_SIZ]; // a bit generous?
6097
6098                 strcpy(bookMove, "move ");
6099                 strcat(bookMove, bookHit);
6100                 message = bookMove;
6101                 cps = cps->other;
6102                 programStats.nodes = programStats.depth = programStats.time = 
6103                 programStats.score = programStats.got_only_move = 0;
6104                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6105
6106                 if(cps->lastPing != cps->lastPong) {
6107                     savedMessage = message; // args for deferred call
6108                     savedState = cps;
6109                     ScheduleDelayedEvent(DeferredBookMove, 10);
6110                     return;
6111                 }
6112                 goto FakeBookMove;
6113         }
6114
6115         return;
6116     }
6117
6118     /* Set special modes for chess engines.  Later something general
6119      *  could be added here; for now there is just one kludge feature,
6120      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6121      *  when "xboard" is given as an interactive command.
6122      */
6123     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6124         cps->useSigint = FALSE;
6125         cps->useSigterm = FALSE;
6126     }
6127     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6128       ParseFeatures(message+8, cps);
6129       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6130     }
6131
6132     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6133      * want this, I was asked to put it in, and obliged.
6134      */
6135     if (!strncmp(message, "setboard ", 9)) {
6136         Board initial_position; int i;
6137
6138         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6139
6140         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6141             DisplayError(_("Bad FEN received from engine"), 0);
6142             return ;
6143         } else {
6144            Reset(FALSE, FALSE);
6145            CopyBoard(boards[0], initial_position);
6146            initialRulePlies = FENrulePlies;
6147            epStatus[0] = FENepStatus;
6148            for( i=0; i<nrCastlingRights; i++ )
6149                 castlingRights[0][i] = FENcastlingRights[i];
6150            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6151            else gameMode = MachinePlaysBlack;                 
6152            DrawPosition(FALSE, boards[currentMove]);
6153         }
6154         return;
6155     }
6156
6157     /*
6158      * Look for communication commands
6159      */
6160     if (!strncmp(message, "telluser ", 9)) {
6161         DisplayNote(message + 9);
6162         return;
6163     }
6164     if (!strncmp(message, "tellusererror ", 14)) {
6165         DisplayError(message + 14, 0);
6166         return;
6167     }
6168     if (!strncmp(message, "tellopponent ", 13)) {
6169       if (appData.icsActive) {
6170         if (loggedOn) {
6171           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6172           SendToICS(buf1);
6173         }
6174       } else {
6175         DisplayNote(message + 13);
6176       }
6177       return;
6178     }
6179     if (!strncmp(message, "tellothers ", 11)) {
6180       if (appData.icsActive) {
6181         if (loggedOn) {
6182           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6183           SendToICS(buf1);
6184         }
6185       }
6186       return;
6187     }
6188     if (!strncmp(message, "tellall ", 8)) {
6189       if (appData.icsActive) {
6190         if (loggedOn) {
6191           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6192           SendToICS(buf1);
6193         }
6194       } else {
6195         DisplayNote(message + 8);
6196       }
6197       return;
6198     }
6199     if (strncmp(message, "warning", 7) == 0) {
6200         /* Undocumented feature, use tellusererror in new code */
6201         DisplayError(message, 0);
6202         return;
6203     }
6204     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6205         strcpy(realname, cps->tidy);
6206         strcat(realname, " query");
6207         AskQuestion(realname, buf2, buf1, cps->pr);
6208         return;
6209     }
6210     /* Commands from the engine directly to ICS.  We don't allow these to be 
6211      *  sent until we are logged on. Crafty kibitzes have been known to 
6212      *  interfere with the login process.
6213      */
6214     if (loggedOn) {
6215         if (!strncmp(message, "tellics ", 8)) {
6216             SendToICS(message + 8);
6217             SendToICS("\n");
6218             return;
6219         }
6220         if (!strncmp(message, "tellicsnoalias ", 15)) {
6221             SendToICS(ics_prefix);
6222             SendToICS(message + 15);
6223             SendToICS("\n");
6224             return;
6225         }
6226         /* The following are for backward compatibility only */
6227         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6228             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6229             SendToICS(ics_prefix);
6230             SendToICS(message);
6231             SendToICS("\n");
6232             return;
6233         }
6234     }
6235     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6236         return;
6237     }
6238     /*
6239      * If the move is illegal, cancel it and redraw the board.
6240      * Also deal with other error cases.  Matching is rather loose
6241      * here to accommodate engines written before the spec.
6242      */
6243     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6244         strncmp(message, "Error", 5) == 0) {
6245         if (StrStr(message, "name") || 
6246             StrStr(message, "rating") || StrStr(message, "?") ||
6247             StrStr(message, "result") || StrStr(message, "board") ||
6248             StrStr(message, "bk") || StrStr(message, "computer") ||
6249             StrStr(message, "variant") || StrStr(message, "hint") ||
6250             StrStr(message, "random") || StrStr(message, "depth") ||
6251             StrStr(message, "accepted")) {
6252             return;
6253         }
6254         if (StrStr(message, "protover")) {
6255           /* Program is responding to input, so it's apparently done
6256              initializing, and this error message indicates it is
6257              protocol version 1.  So we don't need to wait any longer
6258              for it to initialize and send feature commands. */
6259           FeatureDone(cps, 1);
6260           cps->protocolVersion = 1;
6261           return;
6262         }
6263         cps->maybeThinking = FALSE;
6264
6265         if (StrStr(message, "draw")) {
6266             /* Program doesn't have "draw" command */
6267             cps->sendDrawOffers = 0;
6268             return;
6269         }
6270         if (cps->sendTime != 1 &&
6271             (StrStr(message, "time") || StrStr(message, "otim"))) {
6272           /* Program apparently doesn't have "time" or "otim" command */
6273           cps->sendTime = 0;
6274           return;
6275         }
6276         if (StrStr(message, "analyze")) {
6277             cps->analysisSupport = FALSE;
6278             cps->analyzing = FALSE;
6279             Reset(FALSE, TRUE);
6280             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6281             DisplayError(buf2, 0);
6282             return;
6283         }
6284         if (StrStr(message, "(no matching move)st")) {
6285           /* Special kludge for GNU Chess 4 only */
6286           cps->stKludge = TRUE;
6287           SendTimeControl(cps, movesPerSession, timeControl,
6288                           timeIncrement, appData.searchDepth,
6289                           searchTime);
6290           return;
6291         }
6292         if (StrStr(message, "(no matching move)sd")) {
6293           /* Special kludge for GNU Chess 4 only */
6294           cps->sdKludge = TRUE;
6295           SendTimeControl(cps, movesPerSession, timeControl,
6296                           timeIncrement, appData.searchDepth,
6297                           searchTime);
6298           return;
6299         }
6300         if (!StrStr(message, "llegal")) {
6301             return;
6302         }
6303         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6304             gameMode == IcsIdle) return;
6305         if (forwardMostMove <= backwardMostMove) return;
6306 #if 0
6307         /* Following removed: it caused a bug where a real illegal move
6308            message in analyze mored would be ignored. */
6309         if (cps == &first && programStats.ok_to_send == 0) {
6310             /* Bogus message from Crafty responding to "."  This filtering
6311                can miss some of the bad messages, but fortunately the bug 
6312                is fixed in current Crafty versions, so it doesn't matter. */
6313             return;
6314         }
6315 #endif
6316         if (pausing) PauseEvent();
6317         if (gameMode == PlayFromGameFile) {
6318             /* Stop reading this game file */
6319             gameMode = EditGame;
6320             ModeHighlight();
6321         }
6322         currentMove = --forwardMostMove;
6323         DisplayMove(currentMove-1); /* before DisplayMoveError */
6324         SwitchClocks();
6325         DisplayBothClocks();
6326         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6327                 parseList[currentMove], cps->which);
6328         DisplayMoveError(buf1);
6329         DrawPosition(FALSE, boards[currentMove]);
6330
6331         /* [HGM] illegal-move claim should forfeit game when Xboard */
6332         /* only passes fully legal moves                            */
6333         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6334             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6335                                 "False illegal-move claim", GE_XBOARD );
6336         }
6337         return;
6338     }
6339     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6340         /* Program has a broken "time" command that
6341            outputs a string not ending in newline.
6342            Don't use it. */
6343         cps->sendTime = 0;
6344     }
6345     
6346     /*
6347      * If chess program startup fails, exit with an error message.
6348      * Attempts to recover here are futile.
6349      */
6350     if ((StrStr(message, "unknown host") != NULL)
6351         || (StrStr(message, "No remote directory") != NULL)
6352         || (StrStr(message, "not found") != NULL)
6353         || (StrStr(message, "No such file") != NULL)
6354         || (StrStr(message, "can't alloc") != NULL)
6355         || (StrStr(message, "Permission denied") != NULL)) {
6356
6357         cps->maybeThinking = FALSE;
6358         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6359                 cps->which, cps->program, cps->host, message);
6360         RemoveInputSource(cps->isr);
6361         DisplayFatalError(buf1, 0, 1);
6362         return;
6363     }
6364     
6365     /* 
6366      * Look for hint output
6367      */
6368     if (sscanf(message, "Hint: %s", buf1) == 1) {
6369         if (cps == &first && hintRequested) {
6370             hintRequested = FALSE;
6371             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6372                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6373                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6374                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6375                                     fromY, fromX, toY, toX, promoChar, buf1);
6376                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6377                 DisplayInformation(buf2);
6378             } else {
6379                 /* Hint move could not be parsed!? */
6380               snprintf(buf2, sizeof(buf2),
6381                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6382                         buf1, cps->which);
6383                 DisplayError(buf2, 0);
6384             }
6385         } else {
6386             strcpy(lastHint, buf1);
6387         }
6388         return;
6389     }
6390
6391     /*
6392      * Ignore other messages if game is not in progress
6393      */
6394     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6395         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6396
6397     /*
6398      * look for win, lose, draw, or draw offer
6399      */
6400     if (strncmp(message, "1-0", 3) == 0) {
6401         char *p, *q, *r = "";
6402         p = strchr(message, '{');
6403         if (p) {
6404             q = strchr(p, '}');
6405             if (q) {
6406                 *q = NULLCHAR;
6407                 r = p + 1;
6408             }
6409         }
6410         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6411         return;
6412     } else if (strncmp(message, "0-1", 3) == 0) {
6413         char *p, *q, *r = "";
6414         p = strchr(message, '{');
6415         if (p) {
6416             q = strchr(p, '}');
6417             if (q) {
6418                 *q = NULLCHAR;
6419                 r = p + 1;
6420             }
6421         }
6422         /* Kludge for Arasan 4.1 bug */
6423         if (strcmp(r, "Black resigns") == 0) {
6424             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6425             return;
6426         }
6427         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6428         return;
6429     } else if (strncmp(message, "1/2", 3) == 0) {
6430         char *p, *q, *r = "";
6431         p = strchr(message, '{');
6432         if (p) {
6433             q = strchr(p, '}');
6434             if (q) {
6435                 *q = NULLCHAR;
6436                 r = p + 1;
6437             }
6438         }
6439             
6440         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6441         return;
6442
6443     } else if (strncmp(message, "White resign", 12) == 0) {
6444         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6445         return;
6446     } else if (strncmp(message, "Black resign", 12) == 0) {
6447         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6448         return;
6449     } else if (strncmp(message, "White matches", 13) == 0 ||
6450                strncmp(message, "Black matches", 13) == 0   ) {
6451         /* [HGM] ignore GNUShogi noises */
6452         return;
6453     } else if (strncmp(message, "White", 5) == 0 &&
6454                message[5] != '(' &&
6455                StrStr(message, "Black") == NULL) {
6456         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6457         return;
6458     } else if (strncmp(message, "Black", 5) == 0 &&
6459                message[5] != '(') {
6460         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6461         return;
6462     } else if (strcmp(message, "resign") == 0 ||
6463                strcmp(message, "computer resigns") == 0) {
6464         switch (gameMode) {
6465           case MachinePlaysBlack:
6466           case IcsPlayingBlack:
6467             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6468             break;
6469           case MachinePlaysWhite:
6470           case IcsPlayingWhite:
6471             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6472             break;
6473           case TwoMachinesPlay:
6474             if (cps->twoMachinesColor[0] == 'w')
6475               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6476             else
6477               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6478             break;
6479           default:
6480             /* can't happen */
6481             break;
6482         }
6483         return;
6484     } else if (strncmp(message, "opponent mates", 14) == 0) {
6485         switch (gameMode) {
6486           case MachinePlaysBlack:
6487           case IcsPlayingBlack:
6488             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6489             break;
6490           case MachinePlaysWhite:
6491           case IcsPlayingWhite:
6492             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6493             break;
6494           case TwoMachinesPlay:
6495             if (cps->twoMachinesColor[0] == 'w')
6496               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6497             else
6498               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6499             break;
6500           default:
6501             /* can't happen */
6502             break;
6503         }
6504         return;
6505     } else if (strncmp(message, "computer mates", 14) == 0) {
6506         switch (gameMode) {
6507           case MachinePlaysBlack:
6508           case IcsPlayingBlack:
6509             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6510             break;
6511           case MachinePlaysWhite:
6512           case IcsPlayingWhite:
6513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6514             break;
6515           case TwoMachinesPlay:
6516             if (cps->twoMachinesColor[0] == 'w')
6517               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6518             else
6519               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6520             break;
6521           default:
6522             /* can't happen */
6523             break;
6524         }
6525         return;
6526     } else if (strncmp(message, "checkmate", 9) == 0) {
6527         if (WhiteOnMove(forwardMostMove)) {
6528             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6529         } else {
6530             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6531         }
6532         return;
6533     } else if (strstr(message, "Draw") != NULL ||
6534                strstr(message, "game is a draw") != NULL) {
6535         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6536         return;
6537     } else if (strstr(message, "offer") != NULL &&
6538                strstr(message, "draw") != NULL) {
6539 #if ZIPPY
6540         if (appData.zippyPlay && first.initDone) {
6541             /* Relay offer to ICS */
6542             SendToICS(ics_prefix);
6543             SendToICS("draw\n");
6544         }
6545 #endif
6546         cps->offeredDraw = 2; /* valid until this engine moves twice */
6547         if (gameMode == TwoMachinesPlay) {
6548             if (cps->other->offeredDraw) {
6549                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6550             /* [HGM] in two-machine mode we delay relaying draw offer      */
6551             /* until after we also have move, to see if it is really claim */
6552             }
6553 #if 0
6554               else {
6555                 if (cps->other->sendDrawOffers) {
6556                     SendToProgram("draw\n", cps->other);
6557                 }
6558             }
6559 #endif
6560         } else if (gameMode == MachinePlaysWhite ||
6561                    gameMode == MachinePlaysBlack) {
6562           if (userOfferedDraw) {
6563             DisplayInformation(_("Machine accepts your draw offer"));
6564             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6565           } else {
6566             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6567           }
6568         }
6569     }
6570
6571     
6572     /*
6573      * Look for thinking output
6574      */
6575     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6576           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6577                                 ) {
6578         int plylev, mvleft, mvtot, curscore, time;
6579         char mvname[MOVE_LEN];
6580         u64 nodes; // [DM]
6581         char plyext;
6582         int ignore = FALSE;
6583         int prefixHint = FALSE;
6584         mvname[0] = NULLCHAR;
6585
6586         switch (gameMode) {
6587           case MachinePlaysBlack:
6588           case IcsPlayingBlack:
6589             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6590             break;
6591           case MachinePlaysWhite:
6592           case IcsPlayingWhite:
6593             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6594             break;
6595           case AnalyzeMode:
6596           case AnalyzeFile:
6597             break;
6598           case IcsObserving: /* [DM] icsEngineAnalyze */
6599             if (!appData.icsEngineAnalyze) ignore = TRUE;
6600             break;
6601           case TwoMachinesPlay:
6602             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6603                 ignore = TRUE;
6604             }
6605             break;
6606           default:
6607             ignore = TRUE;
6608             break;
6609         }
6610
6611         if (!ignore) {
6612             buf1[0] = NULLCHAR;
6613             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6614                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6615
6616                 if (plyext != ' ' && plyext != '\t') {
6617                     time *= 100;
6618                 }
6619
6620                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6621                 if( cps->scoreIsAbsolute && 
6622                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6623                 {
6624                     curscore = -curscore;
6625                 }
6626
6627
6628                 programStats.depth = plylev;
6629                 programStats.nodes = nodes;
6630                 programStats.time = time;
6631                 programStats.score = curscore;
6632                 programStats.got_only_move = 0;
6633
6634                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6635                         int ticklen;
6636
6637                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6638                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6639                         if(WhiteOnMove(forwardMostMove)) 
6640                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6641                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6642                 }
6643
6644                 /* Buffer overflow protection */
6645                 if (buf1[0] != NULLCHAR) {
6646                     if (strlen(buf1) >= sizeof(programStats.movelist)
6647                         && appData.debugMode) {
6648                         fprintf(debugFP,
6649                                 "PV is too long; using the first %d bytes.\n",
6650                                 sizeof(programStats.movelist) - 1);
6651                     }
6652
6653                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6654                 } else {
6655                     sprintf(programStats.movelist, " no PV\n");
6656                 }
6657
6658                 if (programStats.seen_stat) {
6659                     programStats.ok_to_send = 1;
6660                 }
6661
6662                 if (strchr(programStats.movelist, '(') != NULL) {
6663                     programStats.line_is_book = 1;
6664                     programStats.nr_moves = 0;
6665                     programStats.moves_left = 0;
6666                 } else {
6667                     programStats.line_is_book = 0;
6668                 }
6669
6670                 SendProgramStatsToFrontend( cps, &programStats );
6671
6672                 /* 
6673                     [AS] Protect the thinkOutput buffer from overflow... this
6674                     is only useful if buf1 hasn't overflowed first!
6675                 */
6676                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6677                         plylev, 
6678                         (gameMode == TwoMachinesPlay ?
6679                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6680                         ((double) curscore) / 100.0,
6681                         prefixHint ? lastHint : "",
6682                         prefixHint ? " " : "" );
6683
6684                 if( buf1[0] != NULLCHAR ) {
6685                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6686
6687                     if( strlen(buf1) > max_len ) {
6688                         if( appData.debugMode) {
6689                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6690                         }
6691                         buf1[max_len+1] = '\0';
6692                     }
6693
6694                     strcat( thinkOutput, buf1 );
6695                 }
6696
6697                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6698                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6699                     DisplayMove(currentMove - 1);
6700                     DisplayAnalysis();
6701                 }
6702                 return;
6703
6704             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6705                 /* crafty (9.25+) says "(only move) <move>"
6706                  * if there is only 1 legal move
6707                  */
6708                 sscanf(p, "(only move) %s", buf1);
6709                 sprintf(thinkOutput, "%s (only move)", buf1);
6710                 sprintf(programStats.movelist, "%s (only move)", buf1);
6711                 programStats.depth = 1;
6712                 programStats.nr_moves = 1;
6713                 programStats.moves_left = 1;
6714                 programStats.nodes = 1;
6715                 programStats.time = 1;
6716                 programStats.got_only_move = 1;
6717
6718                 /* Not really, but we also use this member to
6719                    mean "line isn't going to change" (Crafty
6720                    isn't searching, so stats won't change) */
6721                 programStats.line_is_book = 1;
6722
6723                 SendProgramStatsToFrontend( cps, &programStats );
6724                 
6725                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6726                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6727                     DisplayMove(currentMove - 1);
6728                     DisplayAnalysis();
6729                 }
6730                 return;
6731             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6732                               &time, &nodes, &plylev, &mvleft,
6733                               &mvtot, mvname) >= 5) {
6734                 /* The stat01: line is from Crafty (9.29+) in response
6735                    to the "." command */
6736                 programStats.seen_stat = 1;
6737                 cps->maybeThinking = TRUE;
6738
6739                 if (programStats.got_only_move || !appData.periodicUpdates)
6740                   return;
6741
6742                 programStats.depth = plylev;
6743                 programStats.time = time;
6744                 programStats.nodes = nodes;
6745                 programStats.moves_left = mvleft;
6746                 programStats.nr_moves = mvtot;
6747                 strcpy(programStats.move_name, mvname);
6748                 programStats.ok_to_send = 1;
6749                 programStats.movelist[0] = '\0';
6750
6751                 SendProgramStatsToFrontend( cps, &programStats );
6752
6753                 DisplayAnalysis();
6754                 return;
6755
6756             } else if (strncmp(message,"++",2) == 0) {
6757                 /* Crafty 9.29+ outputs this */
6758                 programStats.got_fail = 2;
6759                 return;
6760
6761             } else if (strncmp(message,"--",2) == 0) {
6762                 /* Crafty 9.29+ outputs this */
6763                 programStats.got_fail = 1;
6764                 return;
6765
6766             } else if (thinkOutput[0] != NULLCHAR &&
6767                        strncmp(message, "    ", 4) == 0) {
6768                 unsigned message_len;
6769
6770                 p = message;
6771                 while (*p && *p == ' ') p++;
6772
6773                 message_len = strlen( p );
6774
6775                 /* [AS] Avoid buffer overflow */
6776                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6777                     strcat(thinkOutput, " ");
6778                     strcat(thinkOutput, p);
6779                 }
6780
6781                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6782                     strcat(programStats.movelist, " ");
6783                     strcat(programStats.movelist, p);
6784                 }
6785
6786                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6787                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6788                     DisplayMove(currentMove - 1);
6789                     DisplayAnalysis();
6790                 }
6791                 return;
6792             }
6793         }
6794         else {
6795             buf1[0] = NULLCHAR;
6796
6797             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6798                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6799             {
6800                 ChessProgramStats cpstats;
6801
6802                 if (plyext != ' ' && plyext != '\t') {
6803                     time *= 100;
6804                 }
6805
6806                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6807                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6808                     curscore = -curscore;
6809                 }
6810
6811                 cpstats.depth = plylev;
6812                 cpstats.nodes = nodes;
6813                 cpstats.time = time;
6814                 cpstats.score = curscore;
6815                 cpstats.got_only_move = 0;
6816                 cpstats.movelist[0] = '\0';
6817
6818                 if (buf1[0] != NULLCHAR) {
6819                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6820                 }
6821
6822                 cpstats.ok_to_send = 0;
6823                 cpstats.line_is_book = 0;
6824                 cpstats.nr_moves = 0;
6825                 cpstats.moves_left = 0;
6826
6827                 SendProgramStatsToFrontend( cps, &cpstats );
6828             }
6829         }
6830     }
6831 }
6832
6833
6834 /* Parse a game score from the character string "game", and
6835    record it as the history of the current game.  The game
6836    score is NOT assumed to start from the standard position. 
6837    The display is not updated in any way.
6838    */
6839 void
6840 ParseGameHistory(game)
6841      char *game;
6842 {
6843     ChessMove moveType;
6844     int fromX, fromY, toX, toY, boardIndex;
6845     char promoChar;
6846     char *p, *q;
6847     char buf[MSG_SIZ];
6848
6849     if (appData.debugMode)
6850       fprintf(debugFP, "Parsing game history: %s\n", game);
6851
6852     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6853     gameInfo.site = StrSave(appData.icsHost);
6854     gameInfo.date = PGNDate();
6855     gameInfo.round = StrSave("-");
6856
6857     /* Parse out names of players */
6858     while (*game == ' ') game++;
6859     p = buf;
6860     while (*game != ' ') *p++ = *game++;
6861     *p = NULLCHAR;
6862     gameInfo.white = StrSave(buf);
6863     while (*game == ' ') game++;
6864     p = buf;
6865     while (*game != ' ' && *game != '\n') *p++ = *game++;
6866     *p = NULLCHAR;
6867     gameInfo.black = StrSave(buf);
6868
6869     /* Parse moves */
6870     boardIndex = blackPlaysFirst ? 1 : 0;
6871     yynewstr(game);
6872     for (;;) {
6873         yyboardindex = boardIndex;
6874         moveType = (ChessMove) yylex();
6875         switch (moveType) {
6876           case IllegalMove:             /* maybe suicide chess, etc. */
6877   if (appData.debugMode) {
6878     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6879     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6880     setbuf(debugFP, NULL);
6881   }
6882           case WhitePromotionChancellor:
6883           case BlackPromotionChancellor:
6884           case WhitePromotionArchbishop:
6885           case BlackPromotionArchbishop:
6886           case WhitePromotionQueen:
6887           case BlackPromotionQueen:
6888           case WhitePromotionRook:
6889           case BlackPromotionRook:
6890           case WhitePromotionBishop:
6891           case BlackPromotionBishop:
6892           case WhitePromotionKnight:
6893           case BlackPromotionKnight:
6894           case WhitePromotionKing:
6895           case BlackPromotionKing:
6896           case NormalMove:
6897           case WhiteCapturesEnPassant:
6898           case BlackCapturesEnPassant:
6899           case WhiteKingSideCastle:
6900           case WhiteQueenSideCastle:
6901           case BlackKingSideCastle:
6902           case BlackQueenSideCastle:
6903           case WhiteKingSideCastleWild:
6904           case WhiteQueenSideCastleWild:
6905           case BlackKingSideCastleWild:
6906           case BlackQueenSideCastleWild:
6907           /* PUSH Fabien */
6908           case WhiteHSideCastleFR:
6909           case WhiteASideCastleFR:
6910           case BlackHSideCastleFR:
6911           case BlackASideCastleFR:
6912           /* POP Fabien */
6913             fromX = currentMoveString[0] - AAA;
6914             fromY = currentMoveString[1] - ONE;
6915             toX = currentMoveString[2] - AAA;
6916             toY = currentMoveString[3] - ONE;
6917             promoChar = currentMoveString[4];
6918             break;
6919           case WhiteDrop:
6920           case BlackDrop:
6921             fromX = moveType == WhiteDrop ?
6922               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6923             (int) CharToPiece(ToLower(currentMoveString[0]));
6924             fromY = DROP_RANK;
6925             toX = currentMoveString[2] - AAA;
6926             toY = currentMoveString[3] - ONE;
6927             promoChar = NULLCHAR;
6928             break;
6929           case AmbiguousMove:
6930             /* bug? */
6931             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6932   if (appData.debugMode) {
6933     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6935     setbuf(debugFP, NULL);
6936   }
6937             DisplayError(buf, 0);
6938             return;
6939           case ImpossibleMove:
6940             /* bug? */
6941             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6942   if (appData.debugMode) {
6943     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6945     setbuf(debugFP, NULL);
6946   }
6947             DisplayError(buf, 0);
6948             return;
6949           case (ChessMove) 0:   /* end of file */
6950             if (boardIndex < backwardMostMove) {
6951                 /* Oops, gap.  How did that happen? */
6952                 DisplayError(_("Gap in move list"), 0);
6953                 return;
6954             }
6955             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6956             if (boardIndex > forwardMostMove) {
6957                 forwardMostMove = boardIndex;
6958             }
6959             return;
6960           case ElapsedTime:
6961             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6962                 strcat(parseList[boardIndex-1], " ");
6963                 strcat(parseList[boardIndex-1], yy_text);
6964             }
6965             continue;
6966           case Comment:
6967           case PGNTag:
6968           case NAG:
6969           default:
6970             /* ignore */
6971             continue;
6972           case WhiteWins:
6973           case BlackWins:
6974           case GameIsDrawn:
6975           case GameUnfinished:
6976             if (gameMode == IcsExamining) {
6977                 if (boardIndex < backwardMostMove) {
6978                     /* Oops, gap.  How did that happen? */
6979                     return;
6980                 }
6981                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6982                 return;
6983             }
6984             gameInfo.result = moveType;
6985             p = strchr(yy_text, '{');
6986             if (p == NULL) p = strchr(yy_text, '(');
6987             if (p == NULL) {
6988                 p = yy_text;
6989                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6990             } else {
6991                 q = strchr(p, *p == '{' ? '}' : ')');
6992                 if (q != NULL) *q = NULLCHAR;
6993                 p++;
6994             }
6995             gameInfo.resultDetails = StrSave(p);
6996             continue;
6997         }
6998         if (boardIndex >= forwardMostMove &&
6999             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7000             backwardMostMove = blackPlaysFirst ? 1 : 0;
7001             return;
7002         }
7003         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7004                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7005                                  parseList[boardIndex]);
7006         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7007         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7008         /* currentMoveString is set as a side-effect of yylex */
7009         strcpy(moveList[boardIndex], currentMoveString);
7010         strcat(moveList[boardIndex], "\n");
7011         boardIndex++;
7012         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7013                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7014         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7015                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7016           case MT_NONE:
7017           case MT_STALEMATE:
7018           default:
7019             break;
7020           case MT_CHECK:
7021             if(gameInfo.variant != VariantShogi)
7022                 strcat(parseList[boardIndex - 1], "+");
7023             break;
7024           case MT_CHECKMATE:
7025           case MT_STAINMATE:
7026             strcat(parseList[boardIndex - 1], "#");
7027             break;
7028         }
7029     }
7030 }
7031
7032
7033 /* Apply a move to the given board  */
7034 void
7035 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7036      int fromX, fromY, toX, toY;
7037      int promoChar;
7038      Board board;
7039      char *castling;
7040      char *ep;
7041 {
7042   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7043
7044     /* [HGM] compute & store e.p. status and castling rights for new position */
7045     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7046     { int i;
7047
7048       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7049       oldEP = *ep;
7050       *ep = EP_NONE;
7051
7052       if( board[toY][toX] != EmptySquare ) 
7053            *ep = EP_CAPTURE;  
7054
7055       if( board[fromY][fromX] == WhitePawn ) {
7056            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7057                *ep = EP_PAWN_MOVE;
7058            if( toY-fromY==2) {
7059                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7060                         gameInfo.variant != VariantBerolina || toX < fromX)
7061                       *ep = toX | berolina;
7062                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7063                         gameInfo.variant != VariantBerolina || toX > fromX) 
7064                       *ep = toX;
7065            }
7066       } else 
7067       if( board[fromY][fromX] == BlackPawn ) {
7068            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7069                *ep = EP_PAWN_MOVE; 
7070            if( toY-fromY== -2) {
7071                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7072                         gameInfo.variant != VariantBerolina || toX < fromX)
7073                       *ep = toX | berolina;
7074                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7075                         gameInfo.variant != VariantBerolina || toX > fromX) 
7076                       *ep = toX;
7077            }
7078        }
7079
7080        for(i=0; i<nrCastlingRights; i++) {
7081            if(castling[i] == fromX && castlingRank[i] == fromY ||
7082               castling[i] == toX   && castlingRank[i] == toY   
7083              ) castling[i] = -1; // revoke for moved or captured piece
7084        }
7085
7086     }
7087
7088   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7089   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7090        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7091          
7092   if (fromX == toX && fromY == toY) return;
7093
7094   if (fromY == DROP_RANK) {
7095         /* must be first */
7096         piece = board[toY][toX] = (ChessSquare) fromX;
7097   } else {
7098      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7099      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7100      if(gameInfo.variant == VariantKnightmate)
7101          king += (int) WhiteUnicorn - (int) WhiteKing;
7102
7103     /* Code added by Tord: */
7104     /* FRC castling assumed when king captures friendly rook. */
7105     if (board[fromY][fromX] == WhiteKing &&
7106              board[toY][toX] == WhiteRook) {
7107       board[fromY][fromX] = EmptySquare;
7108       board[toY][toX] = EmptySquare;
7109       if(toX > fromX) {
7110         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7111       } else {
7112         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7113       }
7114     } else if (board[fromY][fromX] == BlackKing &&
7115                board[toY][toX] == BlackRook) {
7116       board[fromY][fromX] = EmptySquare;
7117       board[toY][toX] = EmptySquare;
7118       if(toX > fromX) {
7119         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7120       } else {
7121         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7122       }
7123     /* End of code added by Tord */
7124
7125     } else if (board[fromY][fromX] == king
7126         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7127         && toY == fromY && toX > fromX+1) {
7128         board[fromY][fromX] = EmptySquare;
7129         board[toY][toX] = king;
7130         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7131         board[fromY][BOARD_RGHT-1] = EmptySquare;
7132     } else if (board[fromY][fromX] == king
7133         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7134                && toY == fromY && toX < fromX-1) {
7135         board[fromY][fromX] = EmptySquare;
7136         board[toY][toX] = king;
7137         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7138         board[fromY][BOARD_LEFT] = EmptySquare;
7139     } else if (board[fromY][fromX] == WhitePawn
7140                && toY == BOARD_HEIGHT-1
7141                && gameInfo.variant != VariantXiangqi
7142                ) {
7143         /* white pawn promotion */
7144         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7145         if (board[toY][toX] == EmptySquare) {
7146             board[toY][toX] = WhiteQueen;
7147         }
7148         if(gameInfo.variant==VariantBughouse ||
7149            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7150             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7151         board[fromY][fromX] = EmptySquare;
7152     } else if ((fromY == BOARD_HEIGHT-4)
7153                && (toX != fromX)
7154                && gameInfo.variant != VariantXiangqi
7155                && gameInfo.variant != VariantBerolina
7156                && (board[fromY][fromX] == WhitePawn)
7157                && (board[toY][toX] == EmptySquare)) {
7158         board[fromY][fromX] = EmptySquare;
7159         board[toY][toX] = WhitePawn;
7160         captured = board[toY - 1][toX];
7161         board[toY - 1][toX] = EmptySquare;
7162     } else if ((fromY == BOARD_HEIGHT-4)
7163                && (toX == fromX)
7164                && gameInfo.variant == VariantBerolina
7165                && (board[fromY][fromX] == WhitePawn)
7166                && (board[toY][toX] == EmptySquare)) {
7167         board[fromY][fromX] = EmptySquare;
7168         board[toY][toX] = WhitePawn;
7169         if(oldEP & EP_BEROLIN_A) {
7170                 captured = board[fromY][fromX-1];
7171                 board[fromY][fromX-1] = EmptySquare;
7172         }else{  captured = board[fromY][fromX+1];
7173                 board[fromY][fromX+1] = EmptySquare;
7174         }
7175     } else if (board[fromY][fromX] == king
7176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177                && toY == fromY && toX > fromX+1) {
7178         board[fromY][fromX] = EmptySquare;
7179         board[toY][toX] = king;
7180         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7181         board[fromY][BOARD_RGHT-1] = EmptySquare;
7182     } else if (board[fromY][fromX] == king
7183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7184                && toY == fromY && toX < fromX-1) {
7185         board[fromY][fromX] = EmptySquare;
7186         board[toY][toX] = king;
7187         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7188         board[fromY][BOARD_LEFT] = EmptySquare;
7189     } else if (fromY == 7 && fromX == 3
7190                && board[fromY][fromX] == BlackKing
7191                && toY == 7 && toX == 5) {
7192         board[fromY][fromX] = EmptySquare;
7193         board[toY][toX] = BlackKing;
7194         board[fromY][7] = EmptySquare;
7195         board[toY][4] = BlackRook;
7196     } else if (fromY == 7 && fromX == 3
7197                && board[fromY][fromX] == BlackKing
7198                && toY == 7 && toX == 1) {
7199         board[fromY][fromX] = EmptySquare;
7200         board[toY][toX] = BlackKing;
7201         board[fromY][0] = EmptySquare;
7202         board[toY][2] = BlackRook;
7203     } else if (board[fromY][fromX] == BlackPawn
7204                && toY == 0
7205                && gameInfo.variant != VariantXiangqi
7206                ) {
7207         /* black pawn promotion */
7208         board[0][toX] = CharToPiece(ToLower(promoChar));
7209         if (board[0][toX] == EmptySquare) {
7210             board[0][toX] = BlackQueen;
7211         }
7212         if(gameInfo.variant==VariantBughouse ||
7213            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7214             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7215         board[fromY][fromX] = EmptySquare;
7216     } else if ((fromY == 3)
7217                && (toX != fromX)
7218                && gameInfo.variant != VariantXiangqi
7219                && gameInfo.variant != VariantBerolina
7220                && (board[fromY][fromX] == BlackPawn)
7221                && (board[toY][toX] == EmptySquare)) {
7222         board[fromY][fromX] = EmptySquare;
7223         board[toY][toX] = BlackPawn;
7224         captured = board[toY + 1][toX];
7225         board[toY + 1][toX] = EmptySquare;
7226     } else if ((fromY == 3)
7227                && (toX == fromX)
7228                && gameInfo.variant == VariantBerolina
7229                && (board[fromY][fromX] == BlackPawn)
7230                && (board[toY][toX] == EmptySquare)) {
7231         board[fromY][fromX] = EmptySquare;
7232         board[toY][toX] = BlackPawn;
7233         if(oldEP & EP_BEROLIN_A) {
7234                 captured = board[fromY][fromX-1];
7235                 board[fromY][fromX-1] = EmptySquare;
7236         }else{  captured = board[fromY][fromX+1];
7237                 board[fromY][fromX+1] = EmptySquare;
7238         }
7239     } else {
7240         board[toY][toX] = board[fromY][fromX];
7241         board[fromY][fromX] = EmptySquare;
7242     }
7243
7244     /* [HGM] now we promote for Shogi, if needed */
7245     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7246         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7247   }
7248
7249     if (gameInfo.holdingsWidth != 0) {
7250
7251       /* !!A lot more code needs to be written to support holdings  */
7252       /* [HGM] OK, so I have written it. Holdings are stored in the */
7253       /* penultimate board files, so they are automaticlly stored   */
7254       /* in the game history.                                       */
7255       if (fromY == DROP_RANK) {
7256         /* Delete from holdings, by decreasing count */
7257         /* and erasing image if necessary            */
7258         p = (int) fromX;
7259         if(p < (int) BlackPawn) { /* white drop */
7260              p -= (int)WhitePawn;
7261              if(p >= gameInfo.holdingsSize) p = 0;
7262              if(--board[p][BOARD_WIDTH-2] == 0)
7263                   board[p][BOARD_WIDTH-1] = EmptySquare;
7264         } else {                  /* black drop */
7265              p -= (int)BlackPawn;
7266              if(p >= gameInfo.holdingsSize) p = 0;
7267              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7268                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7269         }
7270       }
7271       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7272           && gameInfo.variant != VariantBughouse        ) {
7273         /* [HGM] holdings: Add to holdings, if holdings exist */
7274         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7275                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7276                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7277         }
7278         p = (int) captured;
7279         if (p >= (int) BlackPawn) {
7280           p -= (int)BlackPawn;
7281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7282                   /* in Shogi restore piece to its original  first */
7283                   captured = (ChessSquare) (DEMOTED captured);
7284                   p = DEMOTED p;
7285           }
7286           p = PieceToNumber((ChessSquare)p);
7287           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7288           board[p][BOARD_WIDTH-2]++;
7289           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7290         } else {
7291           p -= (int)WhitePawn;
7292           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7293                   captured = (ChessSquare) (DEMOTED captured);
7294                   p = DEMOTED p;
7295           }
7296           p = PieceToNumber((ChessSquare)p);
7297           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7298           board[BOARD_HEIGHT-1-p][1]++;
7299           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7300         }
7301       }
7302
7303     } else if (gameInfo.variant == VariantAtomic) {
7304       if (captured != EmptySquare) {
7305         int y, x;
7306         for (y = toY-1; y <= toY+1; y++) {
7307           for (x = toX-1; x <= toX+1; x++) {
7308             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7309                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7310               board[y][x] = EmptySquare;
7311             }
7312           }
7313         }
7314         board[toY][toX] = EmptySquare;
7315       }
7316     }
7317     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7318         /* [HGM] Shogi promotions */
7319         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7320     }
7321
7322     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7323                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7324         // [HGM] superchess: take promotion piece out of holdings
7325         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7326         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7327             if(!--board[k][BOARD_WIDTH-2])
7328                 board[k][BOARD_WIDTH-1] = EmptySquare;
7329         } else {
7330             if(!--board[BOARD_HEIGHT-1-k][1])
7331                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7332         }
7333     }
7334
7335 }
7336
7337 /* Updates forwardMostMove */
7338 void
7339 MakeMove(fromX, fromY, toX, toY, promoChar)
7340      int fromX, fromY, toX, toY;
7341      int promoChar;
7342 {
7343 //    forwardMostMove++; // [HGM] bare: moved downstream
7344
7345     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7346         int timeLeft; static int lastLoadFlag=0; int king, piece;
7347         piece = boards[forwardMostMove][fromY][fromX];
7348         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7349         if(gameInfo.variant == VariantKnightmate)
7350             king += (int) WhiteUnicorn - (int) WhiteKing;
7351         if(forwardMostMove == 0) {
7352             if(blackPlaysFirst) 
7353                 fprintf(serverMoves, "%s;", second.tidy);
7354             fprintf(serverMoves, "%s;", first.tidy);
7355             if(!blackPlaysFirst) 
7356                 fprintf(serverMoves, "%s;", second.tidy);
7357         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7358         lastLoadFlag = loadFlag;
7359         // print base move
7360         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7361         // print castling suffix
7362         if( toY == fromY && piece == king ) {
7363             if(toX-fromX > 1)
7364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7365             if(fromX-toX >1)
7366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7367         }
7368         // e.p. suffix
7369         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7370              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7371              boards[forwardMostMove][toY][toX] == EmptySquare
7372              && fromX != toX )
7373                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7374         // promotion suffix
7375         if(promoChar != NULLCHAR)
7376                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7377         if(!loadFlag) {
7378             fprintf(serverMoves, "/%d/%d",
7379                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7380             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7381             else                      timeLeft = blackTimeRemaining/1000;
7382             fprintf(serverMoves, "/%d", timeLeft);
7383         }
7384         fflush(serverMoves);
7385     }
7386
7387     if (forwardMostMove+1 >= MAX_MOVES) {
7388       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7389                         0, 1);
7390       return;
7391     }
7392     SwitchClocks();
7393     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7394     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7395     if (commentList[forwardMostMove+1] != NULL) {
7396         free(commentList[forwardMostMove+1]);
7397         commentList[forwardMostMove+1] = NULL;
7398     }
7399     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7400     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7401     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7402                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7403     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7404     gameInfo.result = GameUnfinished;
7405     if (gameInfo.resultDetails != NULL) {
7406         free(gameInfo.resultDetails);
7407         gameInfo.resultDetails = NULL;
7408     }
7409     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7410                               moveList[forwardMostMove - 1]);
7411     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7412                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7413                              fromY, fromX, toY, toX, promoChar,
7414                              parseList[forwardMostMove - 1]);
7415     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7416                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7417                             castlingRights[forwardMostMove]) ) {
7418       case MT_NONE:
7419       case MT_STALEMATE:
7420       default:
7421         break;
7422       case MT_CHECK:
7423         if(gameInfo.variant != VariantShogi)
7424             strcat(parseList[forwardMostMove - 1], "+");
7425         break;
7426       case MT_CHECKMATE:
7427       case MT_STAINMATE:
7428         strcat(parseList[forwardMostMove - 1], "#");
7429         break;
7430     }
7431     if (appData.debugMode) {
7432         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7433     }
7434
7435 }
7436
7437 /* Updates currentMove if not pausing */
7438 void
7439 ShowMove(fromX, fromY, toX, toY)
7440 {
7441     int instant = (gameMode == PlayFromGameFile) ?
7442         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7443     if(appData.noGUI) return;
7444     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7445         if (!instant) {
7446             if (forwardMostMove == currentMove + 1) {
7447                 AnimateMove(boards[forwardMostMove - 1],
7448                             fromX, fromY, toX, toY);
7449             }
7450             if (appData.highlightLastMove) {
7451                 SetHighlights(fromX, fromY, toX, toY);
7452             }
7453         }
7454         currentMove = forwardMostMove;
7455     }
7456
7457     if (instant) return;
7458
7459     DisplayMove(currentMove - 1);
7460     DrawPosition(FALSE, boards[currentMove]);
7461     DisplayBothClocks();
7462     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7463 }
7464
7465 void SendEgtPath(ChessProgramState *cps)
7466 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7467         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7468
7469         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7470
7471         while(*p) {
7472             char c, *q = name+1, *r, *s;
7473
7474             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7475             while(*p && *p != ',') *q++ = *p++;
7476             *q++ = ':'; *q = 0;
7477             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7478                 strcmp(name, ",nalimov:") == 0 ) {
7479                 // take nalimov path from the menu-changeable option first, if it is defined
7480                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7481                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7482             } else
7483             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7484                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7485                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7486                 s = r = StrStr(s, ":") + 1; // beginning of path info
7487                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7488                 c = *r; *r = 0;             // temporarily null-terminate path info
7489                     *--q = 0;               // strip of trailig ':' from name
7490                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7491                 *r = c;
7492                 SendToProgram(buf,cps);     // send egtbpath command for this format
7493             }
7494             if(*p == ',') p++; // read away comma to position for next format name
7495         }
7496 }
7497
7498 void
7499 InitChessProgram(cps, setup)
7500      ChessProgramState *cps;
7501      int setup; /* [HGM] needed to setup FRC opening position */
7502 {
7503     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7504     if (appData.noChessProgram) return;
7505     hintRequested = FALSE;
7506     bookRequested = FALSE;
7507
7508     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7509     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7510     if(cps->memSize) { /* [HGM] memory */
7511         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7512         SendToProgram(buf, cps);
7513     }
7514     SendEgtPath(cps); /* [HGM] EGT */
7515     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7516         sprintf(buf, "cores %d\n", appData.smpCores);
7517         SendToProgram(buf, cps);
7518     }
7519
7520     SendToProgram(cps->initString, cps);
7521     if (gameInfo.variant != VariantNormal &&
7522         gameInfo.variant != VariantLoadable
7523         /* [HGM] also send variant if board size non-standard */
7524         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7525                                             ) {
7526       char *v = VariantName(gameInfo.variant);
7527       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7528         /* [HGM] in protocol 1 we have to assume all variants valid */
7529         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7530         DisplayFatalError(buf, 0, 1);
7531         return;
7532       }
7533
7534       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7535       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536       if( gameInfo.variant == VariantXiangqi )
7537            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7538       if( gameInfo.variant == VariantShogi )
7539            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7540       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7541            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7542       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7543                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7544            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7545       if( gameInfo.variant == VariantCourier )
7546            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7547       if( gameInfo.variant == VariantSuper )
7548            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7549       if( gameInfo.variant == VariantGreat )
7550            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7551
7552       if(overruled) {
7553            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7554                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7555            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7556            if(StrStr(cps->variants, b) == NULL) { 
7557                // specific sized variant not known, check if general sizing allowed
7558                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7559                    if(StrStr(cps->variants, "boardsize") == NULL) {
7560                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7561                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7562                        DisplayFatalError(buf, 0, 1);
7563                        return;
7564                    }
7565                    /* [HGM] here we really should compare with the maximum supported board size */
7566                }
7567            }
7568       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7569       sprintf(buf, "variant %s\n", b);
7570       SendToProgram(buf, cps);
7571     }
7572     currentlyInitializedVariant = gameInfo.variant;
7573
7574     /* [HGM] send opening position in FRC to first engine */
7575     if(setup) {
7576           SendToProgram("force\n", cps);
7577           SendBoard(cps, 0);
7578           /* engine is now in force mode! Set flag to wake it up after first move. */
7579           setboardSpoiledMachineBlack = 1;
7580     }
7581
7582     if (cps->sendICS) {
7583       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7584       SendToProgram(buf, cps);
7585     }
7586     cps->maybeThinking = FALSE;
7587     cps->offeredDraw = 0;
7588     if (!appData.icsActive) {
7589         SendTimeControl(cps, movesPerSession, timeControl,
7590                         timeIncrement, appData.searchDepth,
7591                         searchTime);
7592     }
7593     if (appData.showThinking 
7594         // [HGM] thinking: four options require thinking output to be sent
7595         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7596                                 ) {
7597         SendToProgram("post\n", cps);
7598     }
7599     SendToProgram("hard\n", cps);
7600     if (!appData.ponderNextMove) {
7601         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7602            it without being sure what state we are in first.  "hard"
7603            is not a toggle, so that one is OK.
7604          */
7605         SendToProgram("easy\n", cps);
7606     }
7607     if (cps->usePing) {
7608       sprintf(buf, "ping %d\n", ++cps->lastPing);
7609       SendToProgram(buf, cps);
7610     }
7611     cps->initDone = TRUE;
7612 }   
7613
7614
7615 void
7616 StartChessProgram(cps)
7617      ChessProgramState *cps;
7618 {
7619     char buf[MSG_SIZ];
7620     int err;
7621
7622     if (appData.noChessProgram) return;
7623     cps->initDone = FALSE;
7624
7625     if (strcmp(cps->host, "localhost") == 0) {
7626         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7627     } else if (*appData.remoteShell == NULLCHAR) {
7628         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7629     } else {
7630         if (*appData.remoteUser == NULLCHAR) {
7631           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7632                     cps->program);
7633         } else {
7634           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7635                     cps->host, appData.remoteUser, cps->program);
7636         }
7637         err = StartChildProcess(buf, "", &cps->pr);
7638     }
7639     
7640     if (err != 0) {
7641         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7642         DisplayFatalError(buf, err, 1);
7643         cps->pr = NoProc;
7644         cps->isr = NULL;
7645         return;
7646     }
7647     
7648     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7649     if (cps->protocolVersion > 1) {
7650       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7651       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7652       cps->comboCnt = 0;  //                and values of combo boxes
7653       SendToProgram(buf, cps);
7654     } else {
7655       SendToProgram("xboard\n", cps);
7656     }
7657 }
7658
7659
7660 void
7661 TwoMachinesEventIfReady P((void))
7662 {
7663   if (first.lastPing != first.lastPong) {
7664     DisplayMessage("", _("Waiting for first chess program"));
7665     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7666     return;
7667   }
7668   if (second.lastPing != second.lastPong) {
7669     DisplayMessage("", _("Waiting for second chess program"));
7670     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7671     return;
7672   }
7673   ThawUI();
7674   TwoMachinesEvent();
7675 }
7676
7677 void
7678 NextMatchGame P((void))
7679 {
7680     int index; /* [HGM] autoinc: step lod index during match */
7681     Reset(FALSE, TRUE);
7682     if (*appData.loadGameFile != NULLCHAR) {
7683         index = appData.loadGameIndex;
7684         if(index < 0) { // [HGM] autoinc
7685             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7686             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7687         } 
7688         LoadGameFromFile(appData.loadGameFile,
7689                          index,
7690                          appData.loadGameFile, FALSE);
7691     } else if (*appData.loadPositionFile != NULLCHAR) {
7692         index = appData.loadPositionIndex;
7693         if(index < 0) { // [HGM] autoinc
7694             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7695             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7696         } 
7697         LoadPositionFromFile(appData.loadPositionFile,
7698                              index,
7699                              appData.loadPositionFile);
7700     }
7701     TwoMachinesEventIfReady();
7702 }
7703
7704 void UserAdjudicationEvent( int result )
7705 {
7706     ChessMove gameResult = GameIsDrawn;
7707
7708     if( result > 0 ) {
7709         gameResult = WhiteWins;
7710     }
7711     else if( result < 0 ) {
7712         gameResult = BlackWins;
7713     }
7714
7715     if( gameMode == TwoMachinesPlay ) {
7716         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7717     }
7718 }
7719
7720
7721 void
7722 GameEnds(result, resultDetails, whosays)
7723      ChessMove result;
7724      char *resultDetails;
7725      int whosays;
7726 {
7727     GameMode nextGameMode;
7728     int isIcsGame;
7729     char buf[MSG_SIZ];
7730
7731     if(endingGame) return; /* [HGM] crash: forbid recursion */
7732     endingGame = 1;
7733
7734     if (appData.debugMode) {
7735       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7736               result, resultDetails ? resultDetails : "(null)", whosays);
7737     }
7738
7739     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7740         /* If we are playing on ICS, the server decides when the
7741            game is over, but the engine can offer to draw, claim 
7742            a draw, or resign. 
7743          */
7744 #if ZIPPY
7745         if (appData.zippyPlay && first.initDone) {
7746             if (result == GameIsDrawn) {
7747                 /* In case draw still needs to be claimed */
7748                 SendToICS(ics_prefix);
7749                 SendToICS("draw\n");
7750             } else if (StrCaseStr(resultDetails, "resign")) {
7751                 SendToICS(ics_prefix);
7752                 SendToICS("resign\n");
7753             }
7754         }
7755 #endif
7756         endingGame = 0; /* [HGM] crash */
7757         return;
7758     }
7759
7760     /* If we're loading the game from a file, stop */
7761     if (whosays == GE_FILE) {
7762       (void) StopLoadGameTimer();
7763       gameFileFP = NULL;
7764     }
7765
7766     /* Cancel draw offers */
7767     first.offeredDraw = second.offeredDraw = 0;
7768
7769     /* If this is an ICS game, only ICS can really say it's done;
7770        if not, anyone can. */
7771     isIcsGame = (gameMode == IcsPlayingWhite || 
7772                  gameMode == IcsPlayingBlack || 
7773                  gameMode == IcsObserving    || 
7774                  gameMode == IcsExamining);
7775
7776     if (!isIcsGame || whosays == GE_ICS) {
7777         /* OK -- not an ICS game, or ICS said it was done */
7778         StopClocks();
7779         if (!isIcsGame && !appData.noChessProgram) 
7780           SetUserThinkingEnables();
7781     
7782         /* [HGM] if a machine claims the game end we verify this claim */
7783         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7784             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7785                 char claimer;
7786                 ChessMove trueResult = (ChessMove) -1;
7787
7788                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7789                                             first.twoMachinesColor[0] :
7790                                             second.twoMachinesColor[0] ;
7791
7792                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7793                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7794                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7795                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7796                 } else
7797                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7798                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7799                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7800                 } else
7801                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7802                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7803                 }
7804
7805                 // now verify win claims, but not in drop games, as we don't understand those yet
7806                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7807                                                  || gameInfo.variant == VariantGreat) &&
7808                     (result == WhiteWins && claimer == 'w' ||
7809                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7810                       if (appData.debugMode) {
7811                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7812                                 result, epStatus[forwardMostMove], forwardMostMove);
7813                       }
7814                       if(result != trueResult) {
7815                               sprintf(buf, "False win claim: '%s'", resultDetails);
7816                               result = claimer == 'w' ? BlackWins : WhiteWins;
7817                               resultDetails = buf;
7818                       }
7819                 } else
7820                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7821                     && (forwardMostMove <= backwardMostMove ||
7822                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7823                         (claimer=='b')==(forwardMostMove&1))
7824                                                                                   ) {
7825                       /* [HGM] verify: draws that were not flagged are false claims */
7826                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7827                       result = claimer == 'w' ? BlackWins : WhiteWins;
7828                       resultDetails = buf;
7829                 }
7830                 /* (Claiming a loss is accepted no questions asked!) */
7831             }
7832             /* [HGM] bare: don't allow bare King to win */
7833             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7834                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7835                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7836                && result != GameIsDrawn)
7837             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7838                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7839                         int p = (int)boards[forwardMostMove][i][j] - color;
7840                         if(p >= 0 && p <= (int)WhiteKing) k++;
7841                 }
7842                 if (appData.debugMode) {
7843                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7844                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7845                 }
7846                 if(k <= 1) {
7847                         result = GameIsDrawn;
7848                         sprintf(buf, "%s but bare king", resultDetails);
7849                         resultDetails = buf;
7850                 }
7851             }
7852         }
7853
7854
7855         if(serverMoves != NULL && !loadFlag) { char c = '=';
7856             if(result==WhiteWins) c = '+';
7857             if(result==BlackWins) c = '-';
7858             if(resultDetails != NULL)
7859                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7860         }
7861         if (resultDetails != NULL) {
7862             gameInfo.result = result;
7863             gameInfo.resultDetails = StrSave(resultDetails);
7864
7865             /* display last move only if game was not loaded from file */
7866             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7867                 DisplayMove(currentMove - 1);
7868     
7869             if (forwardMostMove != 0) {
7870                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7871                     if (*appData.saveGameFile != NULLCHAR) {
7872                         SaveGameToFile(appData.saveGameFile, TRUE);
7873                     } else if (appData.autoSaveGames) {
7874                         AutoSaveGame();
7875                     }
7876                     if (*appData.savePositionFile != NULLCHAR) {
7877                         SavePositionToFile(appData.savePositionFile);
7878                     }
7879                 }
7880             }
7881
7882             /* Tell program how game ended in case it is learning */
7883             /* [HGM] Moved this to after saving the PGN, just in case */
7884             /* engine died and we got here through time loss. In that */
7885             /* case we will get a fatal error writing the pipe, which */
7886             /* would otherwise lose us the PGN.                       */
7887             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7888             /* output during GameEnds should never be fatal anymore   */
7889             if (gameMode == MachinePlaysWhite ||
7890                 gameMode == MachinePlaysBlack ||
7891                 gameMode == TwoMachinesPlay ||
7892                 gameMode == IcsPlayingWhite ||
7893                 gameMode == IcsPlayingBlack ||
7894                 gameMode == BeginningOfGame) {
7895                 char buf[MSG_SIZ];
7896                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7897                         resultDetails);
7898                 if (first.pr != NoProc) {
7899                     SendToProgram(buf, &first);
7900                 }
7901                 if (second.pr != NoProc &&
7902                     gameMode == TwoMachinesPlay) {
7903                     SendToProgram(buf, &second);
7904                 }
7905             }
7906         }
7907
7908         if (appData.icsActive) {
7909             if (appData.quietPlay &&
7910                 (gameMode == IcsPlayingWhite ||
7911                  gameMode == IcsPlayingBlack)) {
7912                 SendToICS(ics_prefix);
7913                 SendToICS("set shout 1\n");
7914             }
7915             nextGameMode = IcsIdle;
7916             ics_user_moved = FALSE;
7917             /* clean up premove.  It's ugly when the game has ended and the
7918              * premove highlights are still on the board.
7919              */
7920             if (gotPremove) {
7921               gotPremove = FALSE;
7922               ClearPremoveHighlights();
7923               DrawPosition(FALSE, boards[currentMove]);
7924             }
7925             if (whosays == GE_ICS) {
7926                 switch (result) {
7927                 case WhiteWins:
7928                     if (gameMode == IcsPlayingWhite)
7929                         PlayIcsWinSound();
7930                     else if(gameMode == IcsPlayingBlack)
7931                         PlayIcsLossSound();
7932                     break;
7933                 case BlackWins:
7934                     if (gameMode == IcsPlayingBlack)
7935                         PlayIcsWinSound();
7936                     else if(gameMode == IcsPlayingWhite)
7937                         PlayIcsLossSound();
7938                     break;
7939                 case GameIsDrawn:
7940                     PlayIcsDrawSound();
7941                     break;
7942                 default:
7943                     PlayIcsUnfinishedSound();
7944                 }
7945             }
7946         } else if (gameMode == EditGame ||
7947                    gameMode == PlayFromGameFile || 
7948                    gameMode == AnalyzeMode || 
7949                    gameMode == AnalyzeFile) {
7950             nextGameMode = gameMode;
7951         } else {
7952             nextGameMode = EndOfGame;
7953         }
7954         pausing = FALSE;
7955         ModeHighlight();
7956     } else {
7957         nextGameMode = gameMode;
7958     }
7959
7960     if (appData.noChessProgram) {
7961         gameMode = nextGameMode;
7962         ModeHighlight();
7963         endingGame = 0; /* [HGM] crash */
7964         return;
7965     }
7966
7967     if (first.reuse) {
7968         /* Put first chess program into idle state */
7969         if (first.pr != NoProc &&
7970             (gameMode == MachinePlaysWhite ||
7971              gameMode == MachinePlaysBlack ||
7972              gameMode == TwoMachinesPlay ||
7973              gameMode == IcsPlayingWhite ||
7974              gameMode == IcsPlayingBlack ||
7975              gameMode == BeginningOfGame)) {
7976             SendToProgram("force\n", &first);
7977             if (first.usePing) {
7978               char buf[MSG_SIZ];
7979               sprintf(buf, "ping %d\n", ++first.lastPing);
7980               SendToProgram(buf, &first);
7981             }
7982         }
7983     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7984         /* Kill off first chess program */
7985         if (first.isr != NULL)
7986           RemoveInputSource(first.isr);
7987         first.isr = NULL;
7988     
7989         if (first.pr != NoProc) {
7990             ExitAnalyzeMode();
7991             DoSleep( appData.delayBeforeQuit );
7992             SendToProgram("quit\n", &first);
7993             DoSleep( appData.delayAfterQuit );
7994             DestroyChildProcess(first.pr, first.useSigterm);
7995         }
7996         first.pr = NoProc;
7997     }
7998     if (second.reuse) {
7999         /* Put second chess program into idle state */
8000         if (second.pr != NoProc &&
8001             gameMode == TwoMachinesPlay) {
8002             SendToProgram("force\n", &second);
8003             if (second.usePing) {
8004               char buf[MSG_SIZ];
8005               sprintf(buf, "ping %d\n", ++second.lastPing);
8006               SendToProgram(buf, &second);
8007             }
8008         }
8009     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8010         /* Kill off second chess program */
8011         if (second.isr != NULL)
8012           RemoveInputSource(second.isr);
8013         second.isr = NULL;
8014     
8015         if (second.pr != NoProc) {
8016             DoSleep( appData.delayBeforeQuit );
8017             SendToProgram("quit\n", &second);
8018             DoSleep( appData.delayAfterQuit );
8019             DestroyChildProcess(second.pr, second.useSigterm);
8020         }
8021         second.pr = NoProc;
8022     }
8023
8024     if (matchMode && gameMode == TwoMachinesPlay) {
8025         switch (result) {
8026         case WhiteWins:
8027           if (first.twoMachinesColor[0] == 'w') {
8028             first.matchWins++;
8029           } else {
8030             second.matchWins++;
8031           }
8032           break;
8033         case BlackWins:
8034           if (first.twoMachinesColor[0] == 'b') {
8035             first.matchWins++;
8036           } else {
8037             second.matchWins++;
8038           }
8039           break;
8040         default:
8041           break;
8042         }
8043         if (matchGame < appData.matchGames) {
8044             char *tmp;
8045             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8046                 tmp = first.twoMachinesColor;
8047                 first.twoMachinesColor = second.twoMachinesColor;
8048                 second.twoMachinesColor = tmp;
8049             }
8050             gameMode = nextGameMode;
8051             matchGame++;
8052             if(appData.matchPause>10000 || appData.matchPause<10)
8053                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8054             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8055             endingGame = 0; /* [HGM] crash */
8056             return;
8057         } else {
8058             char buf[MSG_SIZ];
8059             gameMode = nextGameMode;
8060             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8061                     first.tidy, second.tidy,
8062                     first.matchWins, second.matchWins,
8063                     appData.matchGames - (first.matchWins + second.matchWins));
8064             DisplayFatalError(buf, 0, 0);
8065         }
8066     }
8067     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8068         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8069       ExitAnalyzeMode();
8070     gameMode = nextGameMode;
8071     ModeHighlight();
8072     endingGame = 0;  /* [HGM] crash */
8073 }
8074
8075 /* Assumes program was just initialized (initString sent).
8076    Leaves program in force mode. */
8077 void
8078 FeedMovesToProgram(cps, upto) 
8079      ChessProgramState *cps;
8080      int upto;
8081 {
8082     int i;
8083     
8084     if (appData.debugMode)
8085       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8086               startedFromSetupPosition ? "position and " : "",
8087               backwardMostMove, upto, cps->which);
8088     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8089         // [HGM] variantswitch: make engine aware of new variant
8090         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8091                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8092         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8093         SendToProgram(buf, cps);
8094         currentlyInitializedVariant = gameInfo.variant;
8095     }
8096     SendToProgram("force\n", cps);
8097     if (startedFromSetupPosition) {
8098         SendBoard(cps, backwardMostMove);
8099     if (appData.debugMode) {
8100         fprintf(debugFP, "feedMoves\n");
8101     }
8102     }
8103     for (i = backwardMostMove; i < upto; i++) {
8104         SendMoveToProgram(i, cps);
8105     }
8106 }
8107
8108
8109 void
8110 ResurrectChessProgram()
8111 {
8112      /* The chess program may have exited.
8113         If so, restart it and feed it all the moves made so far. */
8114
8115     if (appData.noChessProgram || first.pr != NoProc) return;
8116     
8117     StartChessProgram(&first);
8118     InitChessProgram(&first, FALSE);
8119     FeedMovesToProgram(&first, currentMove);
8120
8121     if (!first.sendTime) {
8122         /* can't tell gnuchess what its clock should read,
8123            so we bow to its notion. */
8124         ResetClocks();
8125         timeRemaining[0][currentMove] = whiteTimeRemaining;
8126         timeRemaining[1][currentMove] = blackTimeRemaining;
8127     }
8128
8129     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8130                 appData.icsEngineAnalyze) && first.analysisSupport) {
8131       SendToProgram("analyze\n", &first);
8132       first.analyzing = TRUE;
8133     }
8134 }
8135
8136 /*
8137  * Button procedures
8138  */
8139 void
8140 Reset(redraw, init)
8141      int redraw, init;
8142 {
8143     int i;
8144
8145     if (appData.debugMode) {
8146         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8147                 redraw, init, gameMode);
8148     }
8149     pausing = pauseExamInvalid = FALSE;
8150     startedFromSetupPosition = blackPlaysFirst = FALSE;
8151     firstMove = TRUE;
8152     whiteFlag = blackFlag = FALSE;
8153     userOfferedDraw = FALSE;
8154     hintRequested = bookRequested = FALSE;
8155     first.maybeThinking = FALSE;
8156     second.maybeThinking = FALSE;
8157     first.bookSuspend = FALSE; // [HGM] book
8158     second.bookSuspend = FALSE;
8159     thinkOutput[0] = NULLCHAR;
8160     lastHint[0] = NULLCHAR;
8161     ClearGameInfo(&gameInfo);
8162     gameInfo.variant = StringToVariant(appData.variant);
8163     ics_user_moved = ics_clock_paused = FALSE;
8164     ics_getting_history = H_FALSE;
8165     ics_gamenum = -1;
8166     white_holding[0] = black_holding[0] = NULLCHAR;
8167     ClearProgramStats();
8168     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8169     
8170     ResetFrontEnd();
8171     ClearHighlights();
8172     flipView = appData.flipView;
8173     ClearPremoveHighlights();
8174     gotPremove = FALSE;
8175     alarmSounded = FALSE;
8176
8177     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8178     if(appData.serverMovesName != NULL) {
8179         /* [HGM] prepare to make moves file for broadcasting */
8180         clock_t t = clock();
8181         if(serverMoves != NULL) fclose(serverMoves);
8182         serverMoves = fopen(appData.serverMovesName, "r");
8183         if(serverMoves != NULL) {
8184             fclose(serverMoves);
8185             /* delay 15 sec before overwriting, so all clients can see end */
8186             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8187         }
8188         serverMoves = fopen(appData.serverMovesName, "w");
8189     }
8190
8191     ExitAnalyzeMode();
8192     gameMode = BeginningOfGame;
8193     ModeHighlight();
8194     if(appData.icsActive) gameInfo.variant = VariantNormal;
8195     InitPosition(redraw);
8196     for (i = 0; i < MAX_MOVES; i++) {
8197         if (commentList[i] != NULL) {
8198             free(commentList[i]);
8199             commentList[i] = NULL;
8200         }
8201     }
8202     ResetClocks();
8203     timeRemaining[0][0] = whiteTimeRemaining;
8204     timeRemaining[1][0] = blackTimeRemaining;
8205     if (first.pr == NULL) {
8206         StartChessProgram(&first);
8207     }
8208     if (init) {
8209             InitChessProgram(&first, startedFromSetupPosition);
8210     }
8211     DisplayTitle("");
8212     DisplayMessage("", "");
8213     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8214 }
8215
8216 void
8217 AutoPlayGameLoop()
8218 {
8219     for (;;) {
8220         if (!AutoPlayOneMove())
8221           return;
8222         if (matchMode || appData.timeDelay == 0)
8223           continue;
8224         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8225           return;
8226         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8227         break;
8228     }
8229 }
8230
8231
8232 int
8233 AutoPlayOneMove()
8234 {
8235     int fromX, fromY, toX, toY;
8236
8237     if (appData.debugMode) {
8238       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8239     }
8240
8241     if (gameMode != PlayFromGameFile)
8242       return FALSE;
8243
8244     if (currentMove >= forwardMostMove) {
8245       gameMode = EditGame;
8246       ModeHighlight();
8247
8248       /* [AS] Clear current move marker at the end of a game */
8249       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8250
8251       return FALSE;
8252     }
8253     
8254     toX = moveList[currentMove][2] - AAA;
8255     toY = moveList[currentMove][3] - ONE;
8256
8257     if (moveList[currentMove][1] == '@') {
8258         if (appData.highlightLastMove) {
8259             SetHighlights(-1, -1, toX, toY);
8260         }
8261     } else {
8262         fromX = moveList[currentMove][0] - AAA;
8263         fromY = moveList[currentMove][1] - ONE;
8264
8265         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8266
8267         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8268
8269         if (appData.highlightLastMove) {
8270             SetHighlights(fromX, fromY, toX, toY);
8271         }
8272     }
8273     DisplayMove(currentMove);
8274     SendMoveToProgram(currentMove++, &first);
8275     DisplayBothClocks();
8276     DrawPosition(FALSE, boards[currentMove]);
8277     // [HGM] PV info: always display, routine tests if empty
8278     DisplayComment(currentMove - 1, commentList[currentMove]);
8279     return TRUE;
8280 }
8281
8282
8283 int
8284 LoadGameOneMove(readAhead)
8285      ChessMove readAhead;
8286 {
8287     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8288     char promoChar = NULLCHAR;
8289     ChessMove moveType;
8290     char move[MSG_SIZ];
8291     char *p, *q;
8292     
8293     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8294         gameMode != AnalyzeMode && gameMode != Training) {
8295         gameFileFP = NULL;
8296         return FALSE;
8297     }
8298     
8299     yyboardindex = forwardMostMove;
8300     if (readAhead != (ChessMove)0) {
8301       moveType = readAhead;
8302     } else {
8303       if (gameFileFP == NULL)
8304           return FALSE;
8305       moveType = (ChessMove) yylex();
8306     }
8307     
8308     done = FALSE;
8309     switch (moveType) {
8310       case Comment:
8311         if (appData.debugMode) 
8312           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8313         p = yy_text;
8314         if (*p == '{' || *p == '[' || *p == '(') {
8315             p[strlen(p) - 1] = NULLCHAR;
8316             p++;
8317         }
8318
8319         /* append the comment but don't display it */
8320         while (*p == '\n') p++;
8321         AppendComment(currentMove, p);
8322         return TRUE;
8323
8324       case WhiteCapturesEnPassant:
8325       case BlackCapturesEnPassant:
8326       case WhitePromotionChancellor:
8327       case BlackPromotionChancellor:
8328       case WhitePromotionArchbishop:
8329       case BlackPromotionArchbishop:
8330       case WhitePromotionCentaur:
8331       case BlackPromotionCentaur:
8332       case WhitePromotionQueen:
8333       case BlackPromotionQueen:
8334       case WhitePromotionRook:
8335       case BlackPromotionRook:
8336       case WhitePromotionBishop:
8337       case BlackPromotionBishop:
8338       case WhitePromotionKnight:
8339       case BlackPromotionKnight:
8340       case WhitePromotionKing:
8341       case BlackPromotionKing:
8342       case NormalMove:
8343       case WhiteKingSideCastle:
8344       case WhiteQueenSideCastle:
8345       case BlackKingSideCastle:
8346       case BlackQueenSideCastle:
8347       case WhiteKingSideCastleWild:
8348       case WhiteQueenSideCastleWild:
8349       case BlackKingSideCastleWild:
8350       case BlackQueenSideCastleWild:
8351       /* PUSH Fabien */
8352       case WhiteHSideCastleFR:
8353       case WhiteASideCastleFR:
8354       case BlackHSideCastleFR:
8355       case BlackASideCastleFR:
8356       /* POP Fabien */
8357         if (appData.debugMode)
8358           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8359         fromX = currentMoveString[0] - AAA;
8360         fromY = currentMoveString[1] - ONE;
8361         toX = currentMoveString[2] - AAA;
8362         toY = currentMoveString[3] - ONE;
8363         promoChar = currentMoveString[4];
8364         break;
8365
8366       case WhiteDrop:
8367       case BlackDrop:
8368         if (appData.debugMode)
8369           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8370         fromX = moveType == WhiteDrop ?
8371           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8372         (int) CharToPiece(ToLower(currentMoveString[0]));
8373         fromY = DROP_RANK;
8374         toX = currentMoveString[2] - AAA;
8375         toY = currentMoveString[3] - ONE;
8376         break;
8377
8378       case WhiteWins:
8379       case BlackWins:
8380       case GameIsDrawn:
8381       case GameUnfinished:
8382         if (appData.debugMode)
8383           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8384         p = strchr(yy_text, '{');
8385         if (p == NULL) p = strchr(yy_text, '(');
8386         if (p == NULL) {
8387             p = yy_text;
8388             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8389         } else {
8390             q = strchr(p, *p == '{' ? '}' : ')');
8391             if (q != NULL) *q = NULLCHAR;
8392             p++;
8393         }
8394         GameEnds(moveType, p, GE_FILE);
8395         done = TRUE;
8396         if (cmailMsgLoaded) {
8397             ClearHighlights();
8398             flipView = WhiteOnMove(currentMove);
8399             if (moveType == GameUnfinished) flipView = !flipView;
8400             if (appData.debugMode)
8401               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8402         }
8403         break;
8404
8405       case (ChessMove) 0:       /* end of file */
8406         if (appData.debugMode)
8407           fprintf(debugFP, "Parser hit end of file\n");
8408         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8409                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8410           case MT_NONE:
8411           case MT_CHECK:
8412             break;
8413           case MT_CHECKMATE:
8414           case MT_STAINMATE:
8415             if (WhiteOnMove(currentMove)) {
8416                 GameEnds(BlackWins, "Black mates", GE_FILE);
8417             } else {
8418                 GameEnds(WhiteWins, "White mates", GE_FILE);
8419             }
8420             break;
8421           case MT_STALEMATE:
8422             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8423             break;
8424         }
8425         done = TRUE;
8426         break;
8427
8428       case MoveNumberOne:
8429         if (lastLoadGameStart == GNUChessGame) {
8430             /* GNUChessGames have numbers, but they aren't move numbers */
8431             if (appData.debugMode)
8432               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8433                       yy_text, (int) moveType);
8434             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8435         }
8436         /* else fall thru */
8437
8438       case XBoardGame:
8439       case GNUChessGame:
8440       case PGNTag:
8441         /* Reached start of next game in file */
8442         if (appData.debugMode)
8443           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8444         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8445                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8446           case MT_NONE:
8447           case MT_CHECK:
8448             break;
8449           case MT_CHECKMATE:
8450           case MT_STAINMATE:
8451             if (WhiteOnMove(currentMove)) {
8452                 GameEnds(BlackWins, "Black mates", GE_FILE);
8453             } else {
8454                 GameEnds(WhiteWins, "White mates", GE_FILE);
8455             }
8456             break;
8457           case MT_STALEMATE:
8458             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8459             break;
8460         }
8461         done = TRUE;
8462         break;
8463
8464       case PositionDiagram:     /* should not happen; ignore */
8465       case ElapsedTime:         /* ignore */
8466       case NAG:                 /* ignore */
8467         if (appData.debugMode)
8468           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8469                   yy_text, (int) moveType);
8470         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8471
8472       case IllegalMove:
8473         if (appData.testLegality) {
8474             if (appData.debugMode)
8475               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8476             sprintf(move, _("Illegal move: %d.%s%s"),
8477                     (forwardMostMove / 2) + 1,
8478                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8479             DisplayError(move, 0);
8480             done = TRUE;
8481         } else {
8482             if (appData.debugMode)
8483               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8484                       yy_text, currentMoveString);
8485             fromX = currentMoveString[0] - AAA;
8486             fromY = currentMoveString[1] - ONE;
8487             toX = currentMoveString[2] - AAA;
8488             toY = currentMoveString[3] - ONE;
8489             promoChar = currentMoveString[4];
8490         }
8491         break;
8492
8493       case AmbiguousMove:
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8496         sprintf(move, _("Ambiguous move: %d.%s%s"),
8497                 (forwardMostMove / 2) + 1,
8498                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8499         DisplayError(move, 0);
8500         done = TRUE;
8501         break;
8502
8503       default:
8504       case ImpossibleMove:
8505         if (appData.debugMode)
8506           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8507         sprintf(move, _("Illegal move: %d.%s%s"),
8508                 (forwardMostMove / 2) + 1,
8509                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8510         DisplayError(move, 0);
8511         done = TRUE;
8512         break;
8513     }
8514
8515     if (done) {
8516         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8517             DrawPosition(FALSE, boards[currentMove]);
8518             DisplayBothClocks();
8519             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8520               DisplayComment(currentMove - 1, commentList[currentMove]);
8521         }
8522         (void) StopLoadGameTimer();
8523         gameFileFP = NULL;
8524         cmailOldMove = forwardMostMove;
8525         return FALSE;
8526     } else {
8527         /* currentMoveString is set as a side-effect of yylex */
8528         strcat(currentMoveString, "\n");
8529         strcpy(moveList[forwardMostMove], currentMoveString);
8530         
8531         thinkOutput[0] = NULLCHAR;
8532         MakeMove(fromX, fromY, toX, toY, promoChar);
8533         currentMove = forwardMostMove;
8534         return TRUE;
8535     }
8536 }
8537
8538 /* Load the nth game from the given file */
8539 int
8540 LoadGameFromFile(filename, n, title, useList)
8541      char *filename;
8542      int n;
8543      char *title;
8544      /*Boolean*/ int useList;
8545 {
8546     FILE *f;
8547     char buf[MSG_SIZ];
8548
8549     if (strcmp(filename, "-") == 0) {
8550         f = stdin;
8551         title = "stdin";
8552     } else {
8553         f = fopen(filename, "rb");
8554         if (f == NULL) {
8555           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8556             DisplayError(buf, errno);
8557             return FALSE;
8558         }
8559     }
8560     if (fseek(f, 0, 0) == -1) {
8561         /* f is not seekable; probably a pipe */
8562         useList = FALSE;
8563     }
8564     if (useList && n == 0) {
8565         int error = GameListBuild(f);
8566         if (error) {
8567             DisplayError(_("Cannot build game list"), error);
8568         } else if (!ListEmpty(&gameList) &&
8569                    ((ListGame *) gameList.tailPred)->number > 1) {
8570             GameListPopUp(f, title);
8571             return TRUE;
8572         }
8573         GameListDestroy();
8574         n = 1;
8575     }
8576     if (n == 0) n = 1;
8577     return LoadGame(f, n, title, FALSE);
8578 }
8579
8580
8581 void
8582 MakeRegisteredMove()
8583 {
8584     int fromX, fromY, toX, toY;
8585     char promoChar;
8586     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8587         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8588           case CMAIL_MOVE:
8589           case CMAIL_DRAW:
8590             if (appData.debugMode)
8591               fprintf(debugFP, "Restoring %s for game %d\n",
8592                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8593     
8594             thinkOutput[0] = NULLCHAR;
8595             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8596             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8597             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8598             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8599             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8600             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8601             MakeMove(fromX, fromY, toX, toY, promoChar);
8602             ShowMove(fromX, fromY, toX, toY);
8603               
8604             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8605                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8606               case MT_NONE:
8607               case MT_CHECK:
8608                 break;
8609                 
8610               case MT_CHECKMATE:
8611               case MT_STAINMATE:
8612                 if (WhiteOnMove(currentMove)) {
8613                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8614                 } else {
8615                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8616                 }
8617                 break;
8618                 
8619               case MT_STALEMATE:
8620                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8621                 break;
8622             }
8623
8624             break;
8625             
8626           case CMAIL_RESIGN:
8627             if (WhiteOnMove(currentMove)) {
8628                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8629             } else {
8630                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8631             }
8632             break;
8633             
8634           case CMAIL_ACCEPT:
8635             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8636             break;
8637               
8638           default:
8639             break;
8640         }
8641     }
8642
8643     return;
8644 }
8645
8646 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8647 int
8648 CmailLoadGame(f, gameNumber, title, useList)
8649      FILE *f;
8650      int gameNumber;
8651      char *title;
8652      int useList;
8653 {
8654     int retVal;
8655
8656     if (gameNumber > nCmailGames) {
8657         DisplayError(_("No more games in this message"), 0);
8658         return FALSE;
8659     }
8660     if (f == lastLoadGameFP) {
8661         int offset = gameNumber - lastLoadGameNumber;
8662         if (offset == 0) {
8663             cmailMsg[0] = NULLCHAR;
8664             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8665                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8666                 nCmailMovesRegistered--;
8667             }
8668             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8669             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8670                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8671             }
8672         } else {
8673             if (! RegisterMove()) return FALSE;
8674         }
8675     }
8676
8677     retVal = LoadGame(f, gameNumber, title, useList);
8678
8679     /* Make move registered during previous look at this game, if any */
8680     MakeRegisteredMove();
8681
8682     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8683         commentList[currentMove]
8684           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8685         DisplayComment(currentMove - 1, commentList[currentMove]);
8686     }
8687
8688     return retVal;
8689 }
8690
8691 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8692 int
8693 ReloadGame(offset)
8694      int offset;
8695 {
8696     int gameNumber = lastLoadGameNumber + offset;
8697     if (lastLoadGameFP == NULL) {
8698         DisplayError(_("No game has been loaded yet"), 0);
8699         return FALSE;
8700     }
8701     if (gameNumber <= 0) {
8702         DisplayError(_("Can't back up any further"), 0);
8703         return FALSE;
8704     }
8705     if (cmailMsgLoaded) {
8706         return CmailLoadGame(lastLoadGameFP, gameNumber,
8707                              lastLoadGameTitle, lastLoadGameUseList);
8708     } else {
8709         return LoadGame(lastLoadGameFP, gameNumber,
8710                         lastLoadGameTitle, lastLoadGameUseList);
8711     }
8712 }
8713
8714
8715
8716 /* Load the nth game from open file f */
8717 int
8718 LoadGame(f, gameNumber, title, useList)
8719      FILE *f;
8720      int gameNumber;
8721      char *title;
8722      int useList;
8723 {
8724     ChessMove cm;
8725     char buf[MSG_SIZ];
8726     int gn = gameNumber;
8727     ListGame *lg = NULL;
8728     int numPGNTags = 0;
8729     int err;
8730     GameMode oldGameMode;
8731     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8732
8733     if (appData.debugMode) 
8734         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8735
8736     if (gameMode == Training )
8737         SetTrainingModeOff();
8738
8739     oldGameMode = gameMode;
8740     if (gameMode != BeginningOfGame) {
8741       Reset(FALSE, TRUE);
8742     }
8743
8744     gameFileFP = f;
8745     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8746         fclose(lastLoadGameFP);
8747     }
8748
8749     if (useList) {
8750         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8751         
8752         if (lg) {
8753             fseek(f, lg->offset, 0);
8754             GameListHighlight(gameNumber);
8755             gn = 1;
8756         }
8757         else {
8758             DisplayError(_("Game number out of range"), 0);
8759             return FALSE;
8760         }
8761     } else {
8762         GameListDestroy();
8763         if (fseek(f, 0, 0) == -1) {
8764             if (f == lastLoadGameFP ?
8765                 gameNumber == lastLoadGameNumber + 1 :
8766                 gameNumber == 1) {
8767                 gn = 1;
8768             } else {
8769                 DisplayError(_("Can't seek on game file"), 0);
8770                 return FALSE;
8771             }
8772         }
8773     }
8774     lastLoadGameFP = f;
8775     lastLoadGameNumber = gameNumber;
8776     strcpy(lastLoadGameTitle, title);
8777     lastLoadGameUseList = useList;
8778
8779     yynewfile(f);
8780
8781     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8782       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8783                 lg->gameInfo.black);
8784             DisplayTitle(buf);
8785     } else if (*title != NULLCHAR) {
8786         if (gameNumber > 1) {
8787             sprintf(buf, "%s %d", title, gameNumber);
8788             DisplayTitle(buf);
8789         } else {
8790             DisplayTitle(title);
8791         }
8792     }
8793
8794     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8795         gameMode = PlayFromGameFile;
8796         ModeHighlight();
8797     }
8798
8799     currentMove = forwardMostMove = backwardMostMove = 0;
8800     CopyBoard(boards[0], initialPosition);
8801     StopClocks();
8802
8803     /*
8804      * Skip the first gn-1 games in the file.
8805      * Also skip over anything that precedes an identifiable 
8806      * start of game marker, to avoid being confused by 
8807      * garbage at the start of the file.  Currently 
8808      * recognized start of game markers are the move number "1",
8809      * the pattern "gnuchess .* game", the pattern
8810      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8811      * A game that starts with one of the latter two patterns
8812      * will also have a move number 1, possibly
8813      * following a position diagram.
8814      * 5-4-02: Let's try being more lenient and allowing a game to
8815      * start with an unnumbered move.  Does that break anything?
8816      */
8817     cm = lastLoadGameStart = (ChessMove) 0;
8818     while (gn > 0) {
8819         yyboardindex = forwardMostMove;
8820         cm = (ChessMove) yylex();
8821         switch (cm) {
8822           case (ChessMove) 0:
8823             if (cmailMsgLoaded) {
8824                 nCmailGames = CMAIL_MAX_GAMES - gn;
8825             } else {
8826                 Reset(TRUE, TRUE);
8827                 DisplayError(_("Game not found in file"), 0);
8828             }
8829             return FALSE;
8830
8831           case GNUChessGame:
8832           case XBoardGame:
8833             gn--;
8834             lastLoadGameStart = cm;
8835             break;
8836             
8837           case MoveNumberOne:
8838             switch (lastLoadGameStart) {
8839               case GNUChessGame:
8840               case XBoardGame:
8841               case PGNTag:
8842                 break;
8843               case MoveNumberOne:
8844               case (ChessMove) 0:
8845                 gn--;           /* count this game */
8846                 lastLoadGameStart = cm;
8847                 break;
8848               default:
8849                 /* impossible */
8850                 break;
8851             }
8852             break;
8853
8854           case PGNTag:
8855             switch (lastLoadGameStart) {
8856               case GNUChessGame:
8857               case PGNTag:
8858               case MoveNumberOne:
8859               case (ChessMove) 0:
8860                 gn--;           /* count this game */
8861                 lastLoadGameStart = cm;
8862                 break;
8863               case XBoardGame:
8864                 lastLoadGameStart = cm; /* game counted already */
8865                 break;
8866               default:
8867                 /* impossible */
8868                 break;
8869             }
8870             if (gn > 0) {
8871                 do {
8872                     yyboardindex = forwardMostMove;
8873                     cm = (ChessMove) yylex();
8874                 } while (cm == PGNTag || cm == Comment);
8875             }
8876             break;
8877
8878           case WhiteWins:
8879           case BlackWins:
8880           case GameIsDrawn:
8881             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8882                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8883                     != CMAIL_OLD_RESULT) {
8884                     nCmailResults ++ ;
8885                     cmailResult[  CMAIL_MAX_GAMES
8886                                 - gn - 1] = CMAIL_OLD_RESULT;
8887                 }
8888             }
8889             break;
8890
8891           case NormalMove:
8892             /* Only a NormalMove can be at the start of a game
8893              * without a position diagram. */
8894             if (lastLoadGameStart == (ChessMove) 0) {
8895               gn--;
8896               lastLoadGameStart = MoveNumberOne;
8897             }
8898             break;
8899
8900           default:
8901             break;
8902         }
8903     }
8904     
8905     if (appData.debugMode)
8906       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8907
8908     if (cm == XBoardGame) {
8909         /* Skip any header junk before position diagram and/or move 1 */
8910         for (;;) {
8911             yyboardindex = forwardMostMove;
8912             cm = (ChessMove) yylex();
8913
8914             if (cm == (ChessMove) 0 ||
8915                 cm == GNUChessGame || cm == XBoardGame) {
8916                 /* Empty game; pretend end-of-file and handle later */
8917                 cm = (ChessMove) 0;
8918                 break;
8919             }
8920
8921             if (cm == MoveNumberOne || cm == PositionDiagram ||
8922                 cm == PGNTag || cm == Comment)
8923               break;
8924         }
8925     } else if (cm == GNUChessGame) {
8926         if (gameInfo.event != NULL) {
8927             free(gameInfo.event);
8928         }
8929         gameInfo.event = StrSave(yy_text);
8930     }   
8931
8932     startedFromSetupPosition = FALSE;
8933     while (cm == PGNTag) {
8934         if (appData.debugMode) 
8935           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8936         err = ParsePGNTag(yy_text, &gameInfo);
8937         if (!err) numPGNTags++;
8938
8939         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8940         if(gameInfo.variant != oldVariant) {
8941             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8942             InitPosition(TRUE);
8943             oldVariant = gameInfo.variant;
8944             if (appData.debugMode) 
8945               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8946         }
8947
8948
8949         if (gameInfo.fen != NULL) {
8950           Board initial_position;
8951           startedFromSetupPosition = TRUE;
8952           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8953             Reset(TRUE, TRUE);
8954             DisplayError(_("Bad FEN position in file"), 0);
8955             return FALSE;
8956           }
8957           CopyBoard(boards[0], initial_position);
8958           if (blackPlaysFirst) {
8959             currentMove = forwardMostMove = backwardMostMove = 1;
8960             CopyBoard(boards[1], initial_position);
8961             strcpy(moveList[0], "");
8962             strcpy(parseList[0], "");
8963             timeRemaining[0][1] = whiteTimeRemaining;
8964             timeRemaining[1][1] = blackTimeRemaining;
8965             if (commentList[0] != NULL) {
8966               commentList[1] = commentList[0];
8967               commentList[0] = NULL;
8968             }
8969           } else {
8970             currentMove = forwardMostMove = backwardMostMove = 0;
8971           }
8972           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8973           {   int i;
8974               initialRulePlies = FENrulePlies;
8975               epStatus[forwardMostMove] = FENepStatus;
8976               for( i=0; i< nrCastlingRights; i++ )
8977                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8978           }
8979           yyboardindex = forwardMostMove;
8980           free(gameInfo.fen);
8981           gameInfo.fen = NULL;
8982         }
8983
8984         yyboardindex = forwardMostMove;
8985         cm = (ChessMove) yylex();
8986
8987         /* Handle comments interspersed among the tags */
8988         while (cm == Comment) {
8989             char *p;
8990             if (appData.debugMode) 
8991               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8992             p = yy_text;
8993             if (*p == '{' || *p == '[' || *p == '(') {
8994                 p[strlen(p) - 1] = NULLCHAR;
8995                 p++;
8996             }
8997             while (*p == '\n') p++;
8998             AppendComment(currentMove, p);
8999             yyboardindex = forwardMostMove;
9000             cm = (ChessMove) yylex();
9001         }
9002     }
9003
9004     /* don't rely on existence of Event tag since if game was
9005      * pasted from clipboard the Event tag may not exist
9006      */
9007     if (numPGNTags > 0){
9008         char *tags;
9009         if (gameInfo.variant == VariantNormal) {
9010           gameInfo.variant = StringToVariant(gameInfo.event);
9011         }
9012         if (!matchMode) {
9013           if( appData.autoDisplayTags ) {
9014             tags = PGNTags(&gameInfo);
9015             TagsPopUp(tags, CmailMsg());
9016             free(tags);
9017           }
9018         }
9019     } else {
9020         /* Make something up, but don't display it now */
9021         SetGameInfo();
9022         TagsPopDown();
9023     }
9024
9025     if (cm == PositionDiagram) {
9026         int i, j;
9027         char *p;
9028         Board initial_position;
9029
9030         if (appData.debugMode)
9031           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9032
9033         if (!startedFromSetupPosition) {
9034             p = yy_text;
9035             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9036               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9037                 switch (*p) {
9038                   case '[':
9039                   case '-':
9040                   case ' ':
9041                   case '\t':
9042                   case '\n':
9043                   case '\r':
9044                     break;
9045                   default:
9046                     initial_position[i][j++] = CharToPiece(*p);
9047                     break;
9048                 }
9049             while (*p == ' ' || *p == '\t' ||
9050                    *p == '\n' || *p == '\r') p++;
9051         
9052             if (strncmp(p, "black", strlen("black"))==0)
9053               blackPlaysFirst = TRUE;
9054             else
9055               blackPlaysFirst = FALSE;
9056             startedFromSetupPosition = TRUE;
9057         
9058             CopyBoard(boards[0], initial_position);
9059             if (blackPlaysFirst) {
9060                 currentMove = forwardMostMove = backwardMostMove = 1;
9061                 CopyBoard(boards[1], initial_position);
9062                 strcpy(moveList[0], "");
9063                 strcpy(parseList[0], "");
9064                 timeRemaining[0][1] = whiteTimeRemaining;
9065                 timeRemaining[1][1] = blackTimeRemaining;
9066                 if (commentList[0] != NULL) {
9067                     commentList[1] = commentList[0];
9068                     commentList[0] = NULL;
9069                 }
9070             } else {
9071                 currentMove = forwardMostMove = backwardMostMove = 0;
9072             }
9073         }
9074         yyboardindex = forwardMostMove;
9075         cm = (ChessMove) yylex();
9076     }
9077
9078     if (first.pr == NoProc) {
9079         StartChessProgram(&first);
9080     }
9081     InitChessProgram(&first, FALSE);
9082     SendToProgram("force\n", &first);
9083     if (startedFromSetupPosition) {
9084         SendBoard(&first, forwardMostMove);
9085     if (appData.debugMode) {
9086         fprintf(debugFP, "Load Game\n");
9087     }
9088         DisplayBothClocks();
9089     }      
9090
9091     /* [HGM] server: flag to write setup moves in broadcast file as one */
9092     loadFlag = appData.suppressLoadMoves;
9093
9094     while (cm == Comment) {
9095         char *p;
9096         if (appData.debugMode) 
9097           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9098         p = yy_text;
9099         if (*p == '{' || *p == '[' || *p == '(') {
9100             p[strlen(p) - 1] = NULLCHAR;
9101             p++;
9102         }
9103         while (*p == '\n') p++;
9104         AppendComment(currentMove, p);
9105         yyboardindex = forwardMostMove;
9106         cm = (ChessMove) yylex();
9107     }
9108
9109     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9110         cm == WhiteWins || cm == BlackWins ||
9111         cm == GameIsDrawn || cm == GameUnfinished) {
9112         DisplayMessage("", _("No moves in game"));
9113         if (cmailMsgLoaded) {
9114             if (appData.debugMode)
9115               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9116             ClearHighlights();
9117             flipView = FALSE;
9118         }
9119         DrawPosition(FALSE, boards[currentMove]);
9120         DisplayBothClocks();
9121         gameMode = EditGame;
9122         ModeHighlight();
9123         gameFileFP = NULL;
9124         cmailOldMove = 0;
9125         return TRUE;
9126     }
9127
9128     // [HGM] PV info: routine tests if comment empty
9129     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9130         DisplayComment(currentMove - 1, commentList[currentMove]);
9131     }
9132     if (!matchMode && appData.timeDelay != 0) 
9133       DrawPosition(FALSE, boards[currentMove]);
9134
9135     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9136       programStats.ok_to_send = 1;
9137     }
9138
9139     /* if the first token after the PGN tags is a move
9140      * and not move number 1, retrieve it from the parser 
9141      */
9142     if (cm != MoveNumberOne)
9143         LoadGameOneMove(cm);
9144
9145     /* load the remaining moves from the file */
9146     while (LoadGameOneMove((ChessMove)0)) {
9147       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9148       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9149     }
9150
9151     /* rewind to the start of the game */
9152     currentMove = backwardMostMove;
9153
9154     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9155
9156     if (oldGameMode == AnalyzeFile ||
9157         oldGameMode == AnalyzeMode) {
9158       AnalyzeFileEvent();
9159     }
9160
9161     if (matchMode || appData.timeDelay == 0) {
9162       ToEndEvent();
9163       gameMode = EditGame;
9164       ModeHighlight();
9165     } else if (appData.timeDelay > 0) {
9166       AutoPlayGameLoop();
9167     }
9168
9169     if (appData.debugMode) 
9170         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9171
9172     loadFlag = 0; /* [HGM] true game starts */
9173     return TRUE;
9174 }
9175
9176 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9177 int
9178 ReloadPosition(offset)
9179      int offset;
9180 {
9181     int positionNumber = lastLoadPositionNumber + offset;
9182     if (lastLoadPositionFP == NULL) {
9183         DisplayError(_("No position has been loaded yet"), 0);
9184         return FALSE;
9185     }
9186     if (positionNumber <= 0) {
9187         DisplayError(_("Can't back up any further"), 0);
9188         return FALSE;
9189     }
9190     return LoadPosition(lastLoadPositionFP, positionNumber,
9191                         lastLoadPositionTitle);
9192 }
9193
9194 /* Load the nth position from the given file */
9195 int
9196 LoadPositionFromFile(filename, n, title)
9197      char *filename;
9198      int n;
9199      char *title;
9200 {
9201     FILE *f;
9202     char buf[MSG_SIZ];
9203
9204     if (strcmp(filename, "-") == 0) {
9205         return LoadPosition(stdin, n, "stdin");
9206     } else {
9207         f = fopen(filename, "rb");
9208         if (f == NULL) {
9209             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9210             DisplayError(buf, errno);
9211             return FALSE;
9212         } else {
9213             return LoadPosition(f, n, title);
9214         }
9215     }
9216 }
9217
9218 /* Load the nth position from the given open file, and close it */
9219 int
9220 LoadPosition(f, positionNumber, title)
9221      FILE *f;
9222      int positionNumber;
9223      char *title;
9224 {
9225     char *p, line[MSG_SIZ];
9226     Board initial_position;
9227     int i, j, fenMode, pn;
9228     
9229     if (gameMode == Training )
9230         SetTrainingModeOff();
9231
9232     if (gameMode != BeginningOfGame) {
9233         Reset(FALSE, TRUE);
9234     }
9235     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9236         fclose(lastLoadPositionFP);
9237     }
9238     if (positionNumber == 0) positionNumber = 1;
9239     lastLoadPositionFP = f;
9240     lastLoadPositionNumber = positionNumber;
9241     strcpy(lastLoadPositionTitle, title);
9242     if (first.pr == NoProc) {
9243       StartChessProgram(&first);
9244       InitChessProgram(&first, FALSE);
9245     }    
9246     pn = positionNumber;
9247     if (positionNumber < 0) {
9248         /* Negative position number means to seek to that byte offset */
9249         if (fseek(f, -positionNumber, 0) == -1) {
9250             DisplayError(_("Can't seek on position file"), 0);
9251             return FALSE;
9252         };
9253         pn = 1;
9254     } else {
9255         if (fseek(f, 0, 0) == -1) {
9256             if (f == lastLoadPositionFP ?
9257                 positionNumber == lastLoadPositionNumber + 1 :
9258                 positionNumber == 1) {
9259                 pn = 1;
9260             } else {
9261                 DisplayError(_("Can't seek on position file"), 0);
9262                 return FALSE;
9263             }
9264         }
9265     }
9266     /* See if this file is FEN or old-style xboard */
9267     if (fgets(line, MSG_SIZ, f) == NULL) {
9268         DisplayError(_("Position not found in file"), 0);
9269         return FALSE;
9270     }
9271 #if 0
9272     switch (line[0]) {
9273       case '#':  case 'x':
9274       default:
9275         fenMode = FALSE;
9276         break;
9277       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9278       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9279       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9280       case '7':  case '8':  case '9':
9281       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9282       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9283       case 'C':  case 'W':             case 'c':  case 'w': 
9284         fenMode = TRUE;
9285         break;
9286     }
9287 #else
9288     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9289     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9290 #endif
9291
9292     if (pn >= 2) {
9293         if (fenMode || line[0] == '#') pn--;
9294         while (pn > 0) {
9295             /* skip positions before number pn */
9296             if (fgets(line, MSG_SIZ, f) == NULL) {
9297                 Reset(TRUE, TRUE);
9298                 DisplayError(_("Position not found in file"), 0);
9299                 return FALSE;
9300             }
9301             if (fenMode || line[0] == '#') pn--;
9302         }
9303     }
9304
9305     if (fenMode) {
9306         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9307             DisplayError(_("Bad FEN position in file"), 0);
9308             return FALSE;
9309         }
9310     } else {
9311         (void) fgets(line, MSG_SIZ, f);
9312         (void) fgets(line, MSG_SIZ, f);
9313     
9314         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9315             (void) fgets(line, MSG_SIZ, f);
9316             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9317                 if (*p == ' ')
9318                   continue;
9319                 initial_position[i][j++] = CharToPiece(*p);
9320             }
9321         }
9322     
9323         blackPlaysFirst = FALSE;
9324         if (!feof(f)) {
9325             (void) fgets(line, MSG_SIZ, f);
9326             if (strncmp(line, "black", strlen("black"))==0)
9327               blackPlaysFirst = TRUE;
9328         }
9329     }
9330     startedFromSetupPosition = TRUE;
9331     
9332     SendToProgram("force\n", &first);
9333     CopyBoard(boards[0], initial_position);
9334     if (blackPlaysFirst) {
9335         currentMove = forwardMostMove = backwardMostMove = 1;
9336         strcpy(moveList[0], "");
9337         strcpy(parseList[0], "");
9338         CopyBoard(boards[1], initial_position);
9339         DisplayMessage("", _("Black to play"));
9340     } else {
9341         currentMove = forwardMostMove = backwardMostMove = 0;
9342         DisplayMessage("", _("White to play"));
9343     }
9344           /* [HGM] copy FEN attributes as well */
9345           {   int i;
9346               initialRulePlies = FENrulePlies;
9347               epStatus[forwardMostMove] = FENepStatus;
9348               for( i=0; i< nrCastlingRights; i++ )
9349                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9350           }
9351     SendBoard(&first, forwardMostMove);
9352     if (appData.debugMode) {
9353 int i, j;
9354   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9355   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9356         fprintf(debugFP, "Load Position\n");
9357     }
9358
9359     if (positionNumber > 1) {
9360         sprintf(line, "%s %d", title, positionNumber);
9361         DisplayTitle(line);
9362     } else {
9363         DisplayTitle(title);
9364     }
9365     gameMode = EditGame;
9366     ModeHighlight();
9367     ResetClocks();
9368     timeRemaining[0][1] = whiteTimeRemaining;
9369     timeRemaining[1][1] = blackTimeRemaining;
9370     DrawPosition(FALSE, boards[currentMove]);
9371    
9372     return TRUE;
9373 }
9374
9375
9376 void
9377 CopyPlayerNameIntoFileName(dest, src)
9378      char **dest, *src;
9379 {
9380     while (*src != NULLCHAR && *src != ',') {
9381         if (*src == ' ') {
9382             *(*dest)++ = '_';
9383             src++;
9384         } else {
9385             *(*dest)++ = *src++;
9386         }
9387     }
9388 }
9389
9390 char *DefaultFileName(ext)
9391      char *ext;
9392 {
9393     static char def[MSG_SIZ];
9394     char *p;
9395
9396     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9397         p = def;
9398         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9399         *p++ = '-';
9400         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9401         *p++ = '.';
9402         strcpy(p, ext);
9403     } else {
9404         def[0] = NULLCHAR;
9405     }
9406     return def;
9407 }
9408
9409 /* Save the current game to the given file */
9410 int
9411 SaveGameToFile(filename, append)
9412      char *filename;
9413      int append;
9414 {
9415     FILE *f;
9416     char buf[MSG_SIZ];
9417
9418     if (strcmp(filename, "-") == 0) {
9419         return SaveGame(stdout, 0, NULL);
9420     } else {
9421         f = fopen(filename, append ? "a" : "w");
9422         if (f == NULL) {
9423             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9424             DisplayError(buf, errno);
9425             return FALSE;
9426         } else {
9427             return SaveGame(f, 0, NULL);
9428         }
9429     }
9430 }
9431
9432 char *
9433 SavePart(str)
9434      char *str;
9435 {
9436     static char buf[MSG_SIZ];
9437     char *p;
9438     
9439     p = strchr(str, ' ');
9440     if (p == NULL) return str;
9441     strncpy(buf, str, p - str);
9442     buf[p - str] = NULLCHAR;
9443     return buf;
9444 }
9445
9446 #define PGN_MAX_LINE 75
9447
9448 #define PGN_SIDE_WHITE  0
9449 #define PGN_SIDE_BLACK  1
9450
9451 /* [AS] */
9452 static int FindFirstMoveOutOfBook( int side )
9453 {
9454     int result = -1;
9455
9456     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9457         int index = backwardMostMove;
9458         int has_book_hit = 0;
9459
9460         if( (index % 2) != side ) {
9461             index++;
9462         }
9463
9464         while( index < forwardMostMove ) {
9465             /* Check to see if engine is in book */
9466             int depth = pvInfoList[index].depth;
9467             int score = pvInfoList[index].score;
9468             int in_book = 0;
9469
9470             if( depth <= 2 ) {
9471                 in_book = 1;
9472             }
9473             else if( score == 0 && depth == 63 ) {
9474                 in_book = 1; /* Zappa */
9475             }
9476             else if( score == 2 && depth == 99 ) {
9477                 in_book = 1; /* Abrok */
9478             }
9479
9480             has_book_hit += in_book;
9481
9482             if( ! in_book ) {
9483                 result = index;
9484
9485                 break;
9486             }
9487
9488             index += 2;
9489         }
9490     }
9491
9492     return result;
9493 }
9494
9495 /* [AS] */
9496 void GetOutOfBookInfo( char * buf )
9497 {
9498     int oob[2];
9499     int i;
9500     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9501
9502     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9503     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9504
9505     *buf = '\0';
9506
9507     if( oob[0] >= 0 || oob[1] >= 0 ) {
9508         for( i=0; i<2; i++ ) {
9509             int idx = oob[i];
9510
9511             if( idx >= 0 ) {
9512                 if( i > 0 && oob[0] >= 0 ) {
9513                     strcat( buf, "   " );
9514                 }
9515
9516                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9517                 sprintf( buf+strlen(buf), "%s%.2f", 
9518                     pvInfoList[idx].score >= 0 ? "+" : "",
9519                     pvInfoList[idx].score / 100.0 );
9520             }
9521         }
9522     }
9523 }
9524
9525 /* Save game in PGN style and close the file */
9526 int
9527 SaveGamePGN(f)
9528      FILE *f;
9529 {
9530     int i, offset, linelen, newblock;
9531     time_t tm;
9532 //    char *movetext;
9533     char numtext[32];
9534     int movelen, numlen, blank;
9535     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9536
9537     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9538     
9539     tm = time((time_t *) NULL);
9540     
9541     PrintPGNTags(f, &gameInfo);
9542     
9543     if (backwardMostMove > 0 || startedFromSetupPosition) {
9544         char *fen = PositionToFEN(backwardMostMove, NULL);
9545         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9546         fprintf(f, "\n{--------------\n");
9547         PrintPosition(f, backwardMostMove);
9548         fprintf(f, "--------------}\n");
9549         free(fen);
9550     }
9551     else {
9552         /* [AS] Out of book annotation */
9553         if( appData.saveOutOfBookInfo ) {
9554             char buf[64];
9555
9556             GetOutOfBookInfo( buf );
9557
9558             if( buf[0] != '\0' ) {
9559                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9560             }
9561         }
9562
9563         fprintf(f, "\n");
9564     }
9565
9566     i = backwardMostMove;
9567     linelen = 0;
9568     newblock = TRUE;
9569
9570     while (i < forwardMostMove) {
9571         /* Print comments preceding this move */
9572         if (commentList[i] != NULL) {
9573             if (linelen > 0) fprintf(f, "\n");
9574             fprintf(f, "{\n%s}\n", commentList[i]);
9575             linelen = 0;
9576             newblock = TRUE;
9577         }
9578
9579         /* Format move number */
9580         if ((i % 2) == 0) {
9581             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9582         } else {
9583             if (newblock) {
9584                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9585             } else {
9586                 numtext[0] = NULLCHAR;
9587             }
9588         }
9589         numlen = strlen(numtext);
9590         newblock = FALSE;
9591
9592         /* Print move number */
9593         blank = linelen > 0 && numlen > 0;
9594         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9595             fprintf(f, "\n");
9596             linelen = 0;
9597             blank = 0;
9598         }
9599         if (blank) {
9600             fprintf(f, " ");
9601             linelen++;
9602         }
9603         fprintf(f, numtext);
9604         linelen += numlen;
9605
9606         /* Get move */
9607         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9608         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9609 #if 0
9610         // SavePart already does this!
9611         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9612                 int p = movelen - 1;
9613                 if(move_buffer[p] == ' ') p--;
9614                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9615                     while(p && move_buffer[--p] != '(');
9616                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9617                 }
9618         }
9619 #endif
9620         /* Print move */
9621         blank = linelen > 0 && movelen > 0;
9622         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9623             fprintf(f, "\n");
9624             linelen = 0;
9625             blank = 0;
9626         }
9627         if (blank) {
9628             fprintf(f, " ");
9629             linelen++;
9630         }
9631         fprintf(f, move_buffer);
9632         linelen += movelen;
9633
9634         /* [AS] Add PV info if present */
9635         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9636             /* [HGM] add time */
9637             char buf[MSG_SIZ]; int seconds = 0;
9638
9639 #if 1
9640             if(i >= backwardMostMove) {
9641                 if(WhiteOnMove(i))
9642                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9643                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9644                 else
9645                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9646                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9647             }
9648             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9649 #else
9650             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9651 #endif
9652
9653             if( seconds <= 0) buf[0] = 0; else
9654             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9655                 seconds = (seconds + 4)/10; // round to full seconds
9656                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9657                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9658             }
9659
9660             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9661                 pvInfoList[i].score >= 0 ? "+" : "",
9662                 pvInfoList[i].score / 100.0,
9663                 pvInfoList[i].depth,
9664                 buf );
9665
9666             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9667
9668             /* Print score/depth */
9669             blank = linelen > 0 && movelen > 0;
9670             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9671                 fprintf(f, "\n");
9672                 linelen = 0;
9673                 blank = 0;
9674             }
9675             if (blank) {
9676                 fprintf(f, " ");
9677                 linelen++;
9678             }
9679             fprintf(f, move_buffer);
9680             linelen += movelen;
9681         }
9682
9683         i++;
9684     }
9685     
9686     /* Start a new line */
9687     if (linelen > 0) fprintf(f, "\n");
9688
9689     /* Print comments after last move */
9690     if (commentList[i] != NULL) {
9691         fprintf(f, "{\n%s}\n", commentList[i]);
9692     }
9693
9694     /* Print result */
9695     if (gameInfo.resultDetails != NULL &&
9696         gameInfo.resultDetails[0] != NULLCHAR) {
9697         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9698                 PGNResult(gameInfo.result));
9699     } else {
9700         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9701     }
9702
9703     fclose(f);
9704     return TRUE;
9705 }
9706
9707 /* Save game in old style and close the file */
9708 int
9709 SaveGameOldStyle(f)
9710      FILE *f;
9711 {
9712     int i, offset;
9713     time_t tm;
9714     
9715     tm = time((time_t *) NULL);
9716     
9717     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9718     PrintOpponents(f);
9719     
9720     if (backwardMostMove > 0 || startedFromSetupPosition) {
9721         fprintf(f, "\n[--------------\n");
9722         PrintPosition(f, backwardMostMove);
9723         fprintf(f, "--------------]\n");
9724     } else {
9725         fprintf(f, "\n");
9726     }
9727
9728     i = backwardMostMove;
9729     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9730
9731     while (i < forwardMostMove) {
9732         if (commentList[i] != NULL) {
9733             fprintf(f, "[%s]\n", commentList[i]);
9734         }
9735
9736         if ((i % 2) == 1) {
9737             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9738             i++;
9739         } else {
9740             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9741             i++;
9742             if (commentList[i] != NULL) {
9743                 fprintf(f, "\n");
9744                 continue;
9745             }
9746             if (i >= forwardMostMove) {
9747                 fprintf(f, "\n");
9748                 break;
9749             }
9750             fprintf(f, "%s\n", parseList[i]);
9751             i++;
9752         }
9753     }
9754     
9755     if (commentList[i] != NULL) {
9756         fprintf(f, "[%s]\n", commentList[i]);
9757     }
9758
9759     /* This isn't really the old style, but it's close enough */
9760     if (gameInfo.resultDetails != NULL &&
9761         gameInfo.resultDetails[0] != NULLCHAR) {
9762         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9763                 gameInfo.resultDetails);
9764     } else {
9765         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9766     }
9767
9768     fclose(f);
9769     return TRUE;
9770 }
9771
9772 /* Save the current game to open file f and close the file */
9773 int
9774 SaveGame(f, dummy, dummy2)
9775      FILE *f;
9776      int dummy;
9777      char *dummy2;
9778 {
9779     if (gameMode == EditPosition) EditPositionDone();
9780     if (appData.oldSaveStyle)
9781       return SaveGameOldStyle(f);
9782     else
9783       return SaveGamePGN(f);
9784 }
9785
9786 /* Save the current position to the given file */
9787 int
9788 SavePositionToFile(filename)
9789      char *filename;
9790 {
9791     FILE *f;
9792     char buf[MSG_SIZ];
9793
9794     if (strcmp(filename, "-") == 0) {
9795         return SavePosition(stdout, 0, NULL);
9796     } else {
9797         f = fopen(filename, "a");
9798         if (f == NULL) {
9799             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9800             DisplayError(buf, errno);
9801             return FALSE;
9802         } else {
9803             SavePosition(f, 0, NULL);
9804             return TRUE;
9805         }
9806     }
9807 }
9808
9809 /* Save the current position to the given open file and close the file */
9810 int
9811 SavePosition(f, dummy, dummy2)
9812      FILE *f;
9813      int dummy;
9814      char *dummy2;
9815 {
9816     time_t tm;
9817     char *fen;
9818     
9819     if (appData.oldSaveStyle) {
9820         tm = time((time_t *) NULL);
9821     
9822         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9823         PrintOpponents(f);
9824         fprintf(f, "[--------------\n");
9825         PrintPosition(f, currentMove);
9826         fprintf(f, "--------------]\n");
9827     } else {
9828         fen = PositionToFEN(currentMove, NULL);
9829         fprintf(f, "%s\n", fen);
9830         free(fen);
9831     }
9832     fclose(f);
9833     return TRUE;
9834 }
9835
9836 void
9837 ReloadCmailMsgEvent(unregister)
9838      int unregister;
9839 {
9840 #if !WIN32
9841     static char *inFilename = NULL;
9842     static char *outFilename;
9843     int i;
9844     struct stat inbuf, outbuf;
9845     int status;
9846     
9847     /* Any registered moves are unregistered if unregister is set, */
9848     /* i.e. invoked by the signal handler */
9849     if (unregister) {
9850         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9851             cmailMoveRegistered[i] = FALSE;
9852             if (cmailCommentList[i] != NULL) {
9853                 free(cmailCommentList[i]);
9854                 cmailCommentList[i] = NULL;
9855             }
9856         }
9857         nCmailMovesRegistered = 0;
9858     }
9859
9860     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9861         cmailResult[i] = CMAIL_NOT_RESULT;
9862     }
9863     nCmailResults = 0;
9864
9865     if (inFilename == NULL) {
9866         /* Because the filenames are static they only get malloced once  */
9867         /* and they never get freed                                      */
9868         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9869         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9870
9871         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9872         sprintf(outFilename, "%s.out", appData.cmailGameName);
9873     }
9874     
9875     status = stat(outFilename, &outbuf);
9876     if (status < 0) {
9877         cmailMailedMove = FALSE;
9878     } else {
9879         status = stat(inFilename, &inbuf);
9880         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9881     }
9882     
9883     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9884        counts the games, notes how each one terminated, etc.
9885        
9886        It would be nice to remove this kludge and instead gather all
9887        the information while building the game list.  (And to keep it
9888        in the game list nodes instead of having a bunch of fixed-size
9889        parallel arrays.)  Note this will require getting each game's
9890        termination from the PGN tags, as the game list builder does
9891        not process the game moves.  --mann
9892        */
9893     cmailMsgLoaded = TRUE;
9894     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9895     
9896     /* Load first game in the file or popup game menu */
9897     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9898
9899 #endif /* !WIN32 */
9900     return;
9901 }
9902
9903 int
9904 RegisterMove()
9905 {
9906     FILE *f;
9907     char string[MSG_SIZ];
9908
9909     if (   cmailMailedMove
9910         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9911         return TRUE;            /* Allow free viewing  */
9912     }
9913
9914     /* Unregister move to ensure that we don't leave RegisterMove        */
9915     /* with the move registered when the conditions for registering no   */
9916     /* longer hold                                                       */
9917     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9918         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9919         nCmailMovesRegistered --;
9920
9921         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9922           {
9923               free(cmailCommentList[lastLoadGameNumber - 1]);
9924               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9925           }
9926     }
9927
9928     if (cmailOldMove == -1) {
9929         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9930         return FALSE;
9931     }
9932
9933     if (currentMove > cmailOldMove + 1) {
9934         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9935         return FALSE;
9936     }
9937
9938     if (currentMove < cmailOldMove) {
9939         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9940         return FALSE;
9941     }
9942
9943     if (forwardMostMove > currentMove) {
9944         /* Silently truncate extra moves */
9945         TruncateGame();
9946     }
9947
9948     if (   (currentMove == cmailOldMove + 1)
9949         || (   (currentMove == cmailOldMove)
9950             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9951                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9952         if (gameInfo.result != GameUnfinished) {
9953             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9954         }
9955
9956         if (commentList[currentMove] != NULL) {
9957             cmailCommentList[lastLoadGameNumber - 1]
9958               = StrSave(commentList[currentMove]);
9959         }
9960         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9961
9962         if (appData.debugMode)
9963           fprintf(debugFP, "Saving %s for game %d\n",
9964                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9965
9966         sprintf(string,
9967                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9968         
9969         f = fopen(string, "w");
9970         if (appData.oldSaveStyle) {
9971             SaveGameOldStyle(f); /* also closes the file */
9972             
9973             sprintf(string, "%s.pos.out", appData.cmailGameName);
9974             f = fopen(string, "w");
9975             SavePosition(f, 0, NULL); /* also closes the file */
9976         } else {
9977             fprintf(f, "{--------------\n");
9978             PrintPosition(f, currentMove);
9979             fprintf(f, "--------------}\n\n");
9980             
9981             SaveGame(f, 0, NULL); /* also closes the file*/
9982         }
9983         
9984         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9985         nCmailMovesRegistered ++;
9986     } else if (nCmailGames == 1) {
9987         DisplayError(_("You have not made a move yet"), 0);
9988         return FALSE;
9989     }
9990
9991     return TRUE;
9992 }
9993
9994 void
9995 MailMoveEvent()
9996 {
9997 #if !WIN32
9998     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9999     FILE *commandOutput;
10000     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10001     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10002     int nBuffers;
10003     int i;
10004     int archived;
10005     char *arcDir;
10006
10007     if (! cmailMsgLoaded) {
10008         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10009         return;
10010     }
10011
10012     if (nCmailGames == nCmailResults) {
10013         DisplayError(_("No unfinished games"), 0);
10014         return;
10015     }
10016
10017 #if CMAIL_PROHIBIT_REMAIL
10018     if (cmailMailedMove) {
10019         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);
10020         DisplayError(msg, 0);
10021         return;
10022     }
10023 #endif
10024
10025     if (! (cmailMailedMove || RegisterMove())) return;
10026     
10027     if (   cmailMailedMove
10028         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10029         sprintf(string, partCommandString,
10030                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10031         commandOutput = popen(string, "r");
10032
10033         if (commandOutput == NULL) {
10034             DisplayError(_("Failed to invoke cmail"), 0);
10035         } else {
10036             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10037                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10038             }
10039             if (nBuffers > 1) {
10040                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10041                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10042                 nBytes = MSG_SIZ - 1;
10043             } else {
10044                 (void) memcpy(msg, buffer, nBytes);
10045             }
10046             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10047
10048             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10049                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10050
10051                 archived = TRUE;
10052                 for (i = 0; i < nCmailGames; i ++) {
10053                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10054                         archived = FALSE;
10055                     }
10056                 }
10057                 if (   archived
10058                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10059                         != NULL)) {
10060                     sprintf(buffer, "%s/%s.%s.archive",
10061                             arcDir,
10062                             appData.cmailGameName,
10063                             gameInfo.date);
10064                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10065                     cmailMsgLoaded = FALSE;
10066                 }
10067             }
10068
10069             DisplayInformation(msg);
10070             pclose(commandOutput);
10071         }
10072     } else {
10073         if ((*cmailMsg) != '\0') {
10074             DisplayInformation(cmailMsg);
10075         }
10076     }
10077
10078     return;
10079 #endif /* !WIN32 */
10080 }
10081
10082 char *
10083 CmailMsg()
10084 {
10085 #if WIN32
10086     return NULL;
10087 #else
10088     int  prependComma = 0;
10089     char number[5];
10090     char string[MSG_SIZ];       /* Space for game-list */
10091     int  i;
10092     
10093     if (!cmailMsgLoaded) return "";
10094
10095     if (cmailMailedMove) {
10096         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10097     } else {
10098         /* Create a list of games left */
10099         sprintf(string, "[");
10100         for (i = 0; i < nCmailGames; i ++) {
10101             if (! (   cmailMoveRegistered[i]
10102                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10103                 if (prependComma) {
10104                     sprintf(number, ",%d", i + 1);
10105                 } else {
10106                     sprintf(number, "%d", i + 1);
10107                     prependComma = 1;
10108                 }
10109                 
10110                 strcat(string, number);
10111             }
10112         }
10113         strcat(string, "]");
10114
10115         if (nCmailMovesRegistered + nCmailResults == 0) {
10116             switch (nCmailGames) {
10117               case 1:
10118                 sprintf(cmailMsg,
10119                         _("Still need to make move for game\n"));
10120                 break;
10121                 
10122               case 2:
10123                 sprintf(cmailMsg,
10124                         _("Still need to make moves for both games\n"));
10125                 break;
10126                 
10127               default:
10128                 sprintf(cmailMsg,
10129                         _("Still need to make moves for all %d games\n"),
10130                         nCmailGames);
10131                 break;
10132             }
10133         } else {
10134             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10135               case 1:
10136                 sprintf(cmailMsg,
10137                         _("Still need to make a move for game %s\n"),
10138                         string);
10139                 break;
10140                 
10141               case 0:
10142                 if (nCmailResults == nCmailGames) {
10143                     sprintf(cmailMsg, _("No unfinished games\n"));
10144                 } else {
10145                     sprintf(cmailMsg, _("Ready to send mail\n"));
10146                 }
10147                 break;
10148                 
10149               default:
10150                 sprintf(cmailMsg,
10151                         _("Still need to make moves for games %s\n"),
10152                         string);
10153             }
10154         }
10155     }
10156     return cmailMsg;
10157 #endif /* WIN32 */
10158 }
10159
10160 void
10161 ResetGameEvent()
10162 {
10163     if (gameMode == Training)
10164       SetTrainingModeOff();
10165
10166     Reset(TRUE, TRUE);
10167     cmailMsgLoaded = FALSE;
10168     if (appData.icsActive) {
10169       SendToICS(ics_prefix);
10170       SendToICS("refresh\n");
10171     }
10172 }
10173
10174 void
10175 ExitEvent(status)
10176      int status;
10177 {
10178     exiting++;
10179     if (exiting > 2) {
10180       /* Give up on clean exit */
10181       exit(status);
10182     }
10183     if (exiting > 1) {
10184       /* Keep trying for clean exit */
10185       return;
10186     }
10187
10188     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10189
10190     if (telnetISR != NULL) {
10191       RemoveInputSource(telnetISR);
10192     }
10193     if (icsPR != NoProc) {
10194       DestroyChildProcess(icsPR, TRUE);
10195     }
10196 #if 0
10197     /* Save game if resource set and not already saved by GameEnds() */
10198     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10199                              && forwardMostMove > 0) {
10200       if (*appData.saveGameFile != NULLCHAR) {
10201         SaveGameToFile(appData.saveGameFile, TRUE);
10202       } else if (appData.autoSaveGames) {
10203         AutoSaveGame();
10204       }
10205       if (*appData.savePositionFile != NULLCHAR) {
10206         SavePositionToFile(appData.savePositionFile);
10207       }
10208     }
10209     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10210 #else
10211     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10212     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10213 #endif
10214     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10215     /* make sure this other one finishes before killing it!                  */
10216     if(endingGame) { int count = 0;
10217         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10218         while(endingGame && count++ < 10) DoSleep(1);
10219         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10220     }
10221
10222     /* Kill off chess programs */
10223     if (first.pr != NoProc) {
10224         ExitAnalyzeMode();
10225         
10226         DoSleep( appData.delayBeforeQuit );
10227         SendToProgram("quit\n", &first);
10228         DoSleep( appData.delayAfterQuit );
10229         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10230     }
10231     if (second.pr != NoProc) {
10232         DoSleep( appData.delayBeforeQuit );
10233         SendToProgram("quit\n", &second);
10234         DoSleep( appData.delayAfterQuit );
10235         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10236     }
10237     if (first.isr != NULL) {
10238         RemoveInputSource(first.isr);
10239     }
10240     if (second.isr != NULL) {
10241         RemoveInputSource(second.isr);
10242     }
10243
10244     ShutDownFrontEnd();
10245     exit(status);
10246 }
10247
10248 void
10249 PauseEvent()
10250 {
10251     if (appData.debugMode)
10252         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10253     if (pausing) {
10254         pausing = FALSE;
10255         ModeHighlight();
10256         if (gameMode == MachinePlaysWhite ||
10257             gameMode == MachinePlaysBlack) {
10258             StartClocks();
10259         } else {
10260             DisplayBothClocks();
10261         }
10262         if (gameMode == PlayFromGameFile) {
10263             if (appData.timeDelay >= 0) 
10264                 AutoPlayGameLoop();
10265         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10266             Reset(FALSE, TRUE);
10267             SendToICS(ics_prefix);
10268             SendToICS("refresh\n");
10269         } else if (currentMove < forwardMostMove) {
10270             ForwardInner(forwardMostMove);
10271         }
10272         pauseExamInvalid = FALSE;
10273     } else {
10274         switch (gameMode) {
10275           default:
10276             return;
10277           case IcsExamining:
10278             pauseExamForwardMostMove = forwardMostMove;
10279             pauseExamInvalid = FALSE;
10280             /* fall through */
10281           case IcsObserving:
10282           case IcsPlayingWhite:
10283           case IcsPlayingBlack:
10284             pausing = TRUE;
10285             ModeHighlight();
10286             return;
10287           case PlayFromGameFile:
10288             (void) StopLoadGameTimer();
10289             pausing = TRUE;
10290             ModeHighlight();
10291             break;
10292           case BeginningOfGame:
10293             if (appData.icsActive) return;
10294             /* else fall through */
10295           case MachinePlaysWhite:
10296           case MachinePlaysBlack:
10297           case TwoMachinesPlay:
10298             if (forwardMostMove == 0)
10299               return;           /* don't pause if no one has moved */
10300             if ((gameMode == MachinePlaysWhite &&
10301                  !WhiteOnMove(forwardMostMove)) ||
10302                 (gameMode == MachinePlaysBlack &&
10303                  WhiteOnMove(forwardMostMove))) {
10304                 StopClocks();
10305             }
10306             pausing = TRUE;
10307             ModeHighlight();
10308             break;
10309         }
10310     }
10311 }
10312
10313 void
10314 EditCommentEvent()
10315 {
10316     char title[MSG_SIZ];
10317
10318     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10319         strcpy(title, _("Edit comment"));
10320     } else {
10321         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10322                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10323                 parseList[currentMove - 1]);
10324     }
10325
10326     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10327 }
10328
10329
10330 void
10331 EditTagsEvent()
10332 {
10333     char *tags = PGNTags(&gameInfo);
10334     EditTagsPopUp(tags);
10335     free(tags);
10336 }
10337
10338 void
10339 AnalyzeModeEvent()
10340 {
10341     if (appData.noChessProgram || gameMode == AnalyzeMode)
10342       return;
10343
10344     if (gameMode != AnalyzeFile) {
10345         if (!appData.icsEngineAnalyze) {
10346                EditGameEvent();
10347                if (gameMode != EditGame) return;
10348         }
10349         ResurrectChessProgram();
10350         SendToProgram("analyze\n", &first);
10351         first.analyzing = TRUE;
10352         /*first.maybeThinking = TRUE;*/
10353         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10354         AnalysisPopUp(_("Analysis"),
10355                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10356     }
10357     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10358     pausing = FALSE;
10359     ModeHighlight();
10360     SetGameInfo();
10361
10362     StartAnalysisClock();
10363     GetTimeMark(&lastNodeCountTime);
10364     lastNodeCount = 0;
10365 }
10366
10367 void
10368 AnalyzeFileEvent()
10369 {
10370     if (appData.noChessProgram || gameMode == AnalyzeFile)
10371       return;
10372
10373     if (gameMode != AnalyzeMode) {
10374         EditGameEvent();
10375         if (gameMode != EditGame) return;
10376         ResurrectChessProgram();
10377         SendToProgram("analyze\n", &first);
10378         first.analyzing = TRUE;
10379         /*first.maybeThinking = TRUE;*/
10380         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10381         AnalysisPopUp(_("Analysis"),
10382                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10383     }
10384     gameMode = AnalyzeFile;
10385     pausing = FALSE;
10386     ModeHighlight();
10387     SetGameInfo();
10388
10389     StartAnalysisClock();
10390     GetTimeMark(&lastNodeCountTime);
10391     lastNodeCount = 0;
10392 }
10393
10394 void
10395 MachineWhiteEvent()
10396 {
10397     char buf[MSG_SIZ];
10398     char *bookHit = NULL;
10399
10400     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10401       return;
10402
10403
10404     if (gameMode == PlayFromGameFile || 
10405         gameMode == TwoMachinesPlay  || 
10406         gameMode == Training         || 
10407         gameMode == AnalyzeMode      || 
10408         gameMode == EndOfGame)
10409         EditGameEvent();
10410
10411     if (gameMode == EditPosition) 
10412         EditPositionDone();
10413
10414     if (!WhiteOnMove(currentMove)) {
10415         DisplayError(_("It is not White's turn"), 0);
10416         return;
10417     }
10418   
10419     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10420       ExitAnalyzeMode();
10421
10422     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10423         gameMode == AnalyzeFile)
10424         TruncateGame();
10425
10426     ResurrectChessProgram();    /* in case it isn't running */
10427     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10428         gameMode = MachinePlaysWhite;
10429         ResetClocks();
10430     } else
10431     gameMode = MachinePlaysWhite;
10432     pausing = FALSE;
10433     ModeHighlight();
10434     SetGameInfo();
10435     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10436     DisplayTitle(buf);
10437     if (first.sendName) {
10438       sprintf(buf, "name %s\n", gameInfo.black);
10439       SendToProgram(buf, &first);
10440     }
10441     if (first.sendTime) {
10442       if (first.useColors) {
10443         SendToProgram("black\n", &first); /*gnu kludge*/
10444       }
10445       SendTimeRemaining(&first, TRUE);
10446     }
10447     if (first.useColors) {
10448       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10449     }
10450     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10451     SetMachineThinkingEnables();
10452     first.maybeThinking = TRUE;
10453     StartClocks();
10454
10455     if (appData.autoFlipView && !flipView) {
10456       flipView = !flipView;
10457       DrawPosition(FALSE, NULL);
10458       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10459     }
10460
10461     if(bookHit) { // [HGM] book: simulate book reply
10462         static char bookMove[MSG_SIZ]; // a bit generous?
10463
10464         programStats.nodes = programStats.depth = programStats.time = 
10465         programStats.score = programStats.got_only_move = 0;
10466         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10467
10468         strcpy(bookMove, "move ");
10469         strcat(bookMove, bookHit);
10470         HandleMachineMove(bookMove, &first);
10471     }
10472 }
10473
10474 void
10475 MachineBlackEvent()
10476 {
10477     char buf[MSG_SIZ];
10478    char *bookHit = NULL;
10479
10480     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10481         return;
10482
10483
10484     if (gameMode == PlayFromGameFile || 
10485         gameMode == TwoMachinesPlay  || 
10486         gameMode == Training         || 
10487         gameMode == AnalyzeMode      || 
10488         gameMode == EndOfGame)
10489         EditGameEvent();
10490
10491     if (gameMode == EditPosition) 
10492         EditPositionDone();
10493
10494     if (WhiteOnMove(currentMove)) {
10495         DisplayError(_("It is not Black's turn"), 0);
10496         return;
10497     }
10498     
10499     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10500       ExitAnalyzeMode();
10501
10502     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10503         gameMode == AnalyzeFile)
10504         TruncateGame();
10505
10506     ResurrectChessProgram();    /* in case it isn't running */
10507     gameMode = MachinePlaysBlack;
10508     pausing = FALSE;
10509     ModeHighlight();
10510     SetGameInfo();
10511     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10512     DisplayTitle(buf);
10513     if (first.sendName) {
10514       sprintf(buf, "name %s\n", gameInfo.white);
10515       SendToProgram(buf, &first);
10516     }
10517     if (first.sendTime) {
10518       if (first.useColors) {
10519         SendToProgram("white\n", &first); /*gnu kludge*/
10520       }
10521       SendTimeRemaining(&first, FALSE);
10522     }
10523     if (first.useColors) {
10524       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10525     }
10526     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10527     SetMachineThinkingEnables();
10528     first.maybeThinking = TRUE;
10529     StartClocks();
10530
10531     if (appData.autoFlipView && flipView) {
10532       flipView = !flipView;
10533       DrawPosition(FALSE, NULL);
10534       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10535     }
10536     if(bookHit) { // [HGM] book: simulate book reply
10537         static char bookMove[MSG_SIZ]; // a bit generous?
10538
10539         programStats.nodes = programStats.depth = programStats.time = 
10540         programStats.score = programStats.got_only_move = 0;
10541         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10542
10543         strcpy(bookMove, "move ");
10544         strcat(bookMove, bookHit);
10545         HandleMachineMove(bookMove, &first);
10546     }
10547 }
10548
10549
10550 void
10551 DisplayTwoMachinesTitle()
10552 {
10553     char buf[MSG_SIZ];
10554     if (appData.matchGames > 0) {
10555         if (first.twoMachinesColor[0] == 'w') {
10556             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10557                     gameInfo.white, gameInfo.black,
10558                     first.matchWins, second.matchWins,
10559                     matchGame - 1 - (first.matchWins + second.matchWins));
10560         } else {
10561             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10562                     gameInfo.white, gameInfo.black,
10563                     second.matchWins, first.matchWins,
10564                     matchGame - 1 - (first.matchWins + second.matchWins));
10565         }
10566     } else {
10567         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10568     }
10569     DisplayTitle(buf);
10570 }
10571
10572 void
10573 TwoMachinesEvent P((void))
10574 {
10575     int i;
10576     char buf[MSG_SIZ];
10577     ChessProgramState *onmove;
10578     char *bookHit = NULL;
10579     
10580     if (appData.noChessProgram) return;
10581
10582     switch (gameMode) {
10583       case TwoMachinesPlay:
10584         return;
10585       case MachinePlaysWhite:
10586       case MachinePlaysBlack:
10587         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10588             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10589             return;
10590         }
10591         /* fall through */
10592       case BeginningOfGame:
10593       case PlayFromGameFile:
10594       case EndOfGame:
10595         EditGameEvent();
10596         if (gameMode != EditGame) return;
10597         break;
10598       case EditPosition:
10599         EditPositionDone();
10600         break;
10601       case AnalyzeMode:
10602       case AnalyzeFile:
10603         ExitAnalyzeMode();
10604         break;
10605       case EditGame:
10606       default:
10607         break;
10608     }
10609
10610     forwardMostMove = currentMove;
10611     ResurrectChessProgram();    /* in case first program isn't running */
10612
10613     if (second.pr == NULL) {
10614         StartChessProgram(&second);
10615         if (second.protocolVersion == 1) {
10616           TwoMachinesEventIfReady();
10617         } else {
10618           /* kludge: allow timeout for initial "feature" command */
10619           FreezeUI();
10620           DisplayMessage("", _("Starting second chess program"));
10621           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10622         }
10623         return;
10624     }
10625     DisplayMessage("", "");
10626     InitChessProgram(&second, FALSE);
10627     SendToProgram("force\n", &second);
10628     if (startedFromSetupPosition) {
10629         SendBoard(&second, backwardMostMove);
10630     if (appData.debugMode) {
10631         fprintf(debugFP, "Two Machines\n");
10632     }
10633     }
10634     for (i = backwardMostMove; i < forwardMostMove; i++) {
10635         SendMoveToProgram(i, &second);
10636     }
10637
10638     gameMode = TwoMachinesPlay;
10639     pausing = FALSE;
10640     ModeHighlight();
10641     SetGameInfo();
10642     DisplayTwoMachinesTitle();
10643     firstMove = TRUE;
10644     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10645         onmove = &first;
10646     } else {
10647         onmove = &second;
10648     }
10649
10650     SendToProgram(first.computerString, &first);
10651     if (first.sendName) {
10652       sprintf(buf, "name %s\n", second.tidy);
10653       SendToProgram(buf, &first);
10654     }
10655     SendToProgram(second.computerString, &second);
10656     if (second.sendName) {
10657       sprintf(buf, "name %s\n", first.tidy);
10658       SendToProgram(buf, &second);
10659     }
10660
10661     ResetClocks();
10662     if (!first.sendTime || !second.sendTime) {
10663         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10664         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10665     }
10666     if (onmove->sendTime) {
10667       if (onmove->useColors) {
10668         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10669       }
10670       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10671     }
10672     if (onmove->useColors) {
10673       SendToProgram(onmove->twoMachinesColor, onmove);
10674     }
10675     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10676 //    SendToProgram("go\n", onmove);
10677     onmove->maybeThinking = TRUE;
10678     SetMachineThinkingEnables();
10679
10680     StartClocks();
10681
10682     if(bookHit) { // [HGM] book: simulate book reply
10683         static char bookMove[MSG_SIZ]; // a bit generous?
10684
10685         programStats.nodes = programStats.depth = programStats.time = 
10686         programStats.score = programStats.got_only_move = 0;
10687         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10688
10689         strcpy(bookMove, "move ");
10690         strcat(bookMove, bookHit);
10691         HandleMachineMove(bookMove, &first);
10692     }
10693 }
10694
10695 void
10696 TrainingEvent()
10697 {
10698     if (gameMode == Training) {
10699       SetTrainingModeOff();
10700       gameMode = PlayFromGameFile;
10701       DisplayMessage("", _("Training mode off"));
10702     } else {
10703       gameMode = Training;
10704       animateTraining = appData.animate;
10705
10706       /* make sure we are not already at the end of the game */
10707       if (currentMove < forwardMostMove) {
10708         SetTrainingModeOn();
10709         DisplayMessage("", _("Training mode on"));
10710       } else {
10711         gameMode = PlayFromGameFile;
10712         DisplayError(_("Already at end of game"), 0);
10713       }
10714     }
10715     ModeHighlight();
10716 }
10717
10718 void
10719 IcsClientEvent()
10720 {
10721     if (!appData.icsActive) return;
10722     switch (gameMode) {
10723       case IcsPlayingWhite:
10724       case IcsPlayingBlack:
10725       case IcsObserving:
10726       case IcsIdle:
10727       case BeginningOfGame:
10728       case IcsExamining:
10729         return;
10730
10731       case EditGame:
10732         break;
10733
10734       case EditPosition:
10735         EditPositionDone();
10736         break;
10737
10738       case AnalyzeMode:
10739       case AnalyzeFile:
10740         ExitAnalyzeMode();
10741         break;
10742         
10743       default:
10744         EditGameEvent();
10745         break;
10746     }
10747
10748     gameMode = IcsIdle;
10749     ModeHighlight();
10750     return;
10751 }
10752
10753
10754 void
10755 EditGameEvent()
10756 {
10757     int i;
10758
10759     switch (gameMode) {
10760       case Training:
10761         SetTrainingModeOff();
10762         break;
10763       case MachinePlaysWhite:
10764       case MachinePlaysBlack:
10765       case BeginningOfGame:
10766         SendToProgram("force\n", &first);
10767         SetUserThinkingEnables();
10768         break;
10769       case PlayFromGameFile:
10770         (void) StopLoadGameTimer();
10771         if (gameFileFP != NULL) {
10772             gameFileFP = NULL;
10773         }
10774         break;
10775       case EditPosition:
10776         EditPositionDone();
10777         break;
10778       case AnalyzeMode:
10779       case AnalyzeFile:
10780         ExitAnalyzeMode();
10781         SendToProgram("force\n", &first);
10782         break;
10783       case TwoMachinesPlay:
10784         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10785         ResurrectChessProgram();
10786         SetUserThinkingEnables();
10787         break;
10788       case EndOfGame:
10789         ResurrectChessProgram();
10790         break;
10791       case IcsPlayingBlack:
10792       case IcsPlayingWhite:
10793         DisplayError(_("Warning: You are still playing a game"), 0);
10794         break;
10795       case IcsObserving:
10796         DisplayError(_("Warning: You are still observing a game"), 0);
10797         break;
10798       case IcsExamining:
10799         DisplayError(_("Warning: You are still examining a game"), 0);
10800         break;
10801       case IcsIdle:
10802         break;
10803       case EditGame:
10804       default:
10805         return;
10806     }
10807     
10808     pausing = FALSE;
10809     StopClocks();
10810     first.offeredDraw = second.offeredDraw = 0;
10811
10812     if (gameMode == PlayFromGameFile) {
10813         whiteTimeRemaining = timeRemaining[0][currentMove];
10814         blackTimeRemaining = timeRemaining[1][currentMove];
10815         DisplayTitle("");
10816     }
10817
10818     if (gameMode == MachinePlaysWhite ||
10819         gameMode == MachinePlaysBlack ||
10820         gameMode == TwoMachinesPlay ||
10821         gameMode == EndOfGame) {
10822         i = forwardMostMove;
10823         while (i > currentMove) {
10824             SendToProgram("undo\n", &first);
10825             i--;
10826         }
10827         whiteTimeRemaining = timeRemaining[0][currentMove];
10828         blackTimeRemaining = timeRemaining[1][currentMove];
10829         DisplayBothClocks();
10830         if (whiteFlag || blackFlag) {
10831             whiteFlag = blackFlag = 0;
10832         }
10833         DisplayTitle("");
10834     }           
10835     
10836     gameMode = EditGame;
10837     ModeHighlight();
10838     SetGameInfo();
10839 }
10840
10841
10842 void
10843 EditPositionEvent()
10844 {
10845     if (gameMode == EditPosition) {
10846         EditGameEvent();
10847         return;
10848     }
10849     
10850     EditGameEvent();
10851     if (gameMode != EditGame) return;
10852     
10853     gameMode = EditPosition;
10854     ModeHighlight();
10855     SetGameInfo();
10856     if (currentMove > 0)
10857       CopyBoard(boards[0], boards[currentMove]);
10858     
10859     blackPlaysFirst = !WhiteOnMove(currentMove);
10860     ResetClocks();
10861     currentMove = forwardMostMove = backwardMostMove = 0;
10862     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10863     DisplayMove(-1);
10864 }
10865
10866 void
10867 ExitAnalyzeMode()
10868 {
10869     /* [DM] icsEngineAnalyze - possible call from other functions */
10870     if (appData.icsEngineAnalyze) {
10871         appData.icsEngineAnalyze = FALSE;
10872
10873         DisplayMessage("",_("Close ICS engine analyze..."));
10874     }
10875     if (first.analysisSupport && first.analyzing) {
10876       SendToProgram("exit\n", &first);
10877       first.analyzing = FALSE;
10878     }
10879     AnalysisPopDown();
10880     thinkOutput[0] = NULLCHAR;
10881 }
10882
10883 void
10884 EditPositionDone()
10885 {
10886     startedFromSetupPosition = TRUE;
10887     InitChessProgram(&first, FALSE);
10888     SendToProgram("force\n", &first);
10889     if (blackPlaysFirst) {
10890         strcpy(moveList[0], "");
10891         strcpy(parseList[0], "");
10892         currentMove = forwardMostMove = backwardMostMove = 1;
10893         CopyBoard(boards[1], boards[0]);
10894         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10895         { int i;
10896           epStatus[1] = epStatus[0];
10897           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10898         }
10899     } else {
10900         currentMove = forwardMostMove = backwardMostMove = 0;
10901     }
10902     SendBoard(&first, forwardMostMove);
10903     if (appData.debugMode) {
10904         fprintf(debugFP, "EditPosDone\n");
10905     }
10906     DisplayTitle("");
10907     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10908     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10909     gameMode = EditGame;
10910     ModeHighlight();
10911     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10912     ClearHighlights(); /* [AS] */
10913 }
10914
10915 /* Pause for `ms' milliseconds */
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10917 void
10918 TimeDelay(ms)
10919      long ms;
10920 {
10921     TimeMark m1, m2;
10922
10923     GetTimeMark(&m1);
10924     do {
10925         GetTimeMark(&m2);
10926     } while (SubtractTimeMarks(&m2, &m1) < ms);
10927 }
10928
10929 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10930 void
10931 SendMultiLineToICS(buf)
10932      char *buf;
10933 {
10934     char temp[MSG_SIZ+1], *p;
10935     int len;
10936
10937     len = strlen(buf);
10938     if (len > MSG_SIZ)
10939       len = MSG_SIZ;
10940   
10941     strncpy(temp, buf, len);
10942     temp[len] = 0;
10943
10944     p = temp;
10945     while (*p) {
10946         if (*p == '\n' || *p == '\r')
10947           *p = ' ';
10948         ++p;
10949     }
10950
10951     strcat(temp, "\n");
10952     SendToICS(temp);
10953     SendToPlayer(temp, strlen(temp));
10954 }
10955
10956 void
10957 SetWhiteToPlayEvent()
10958 {
10959     if (gameMode == EditPosition) {
10960         blackPlaysFirst = FALSE;
10961         DisplayBothClocks();    /* works because currentMove is 0 */
10962     } else if (gameMode == IcsExamining) {
10963         SendToICS(ics_prefix);
10964         SendToICS("tomove white\n");
10965     }
10966 }
10967
10968 void
10969 SetBlackToPlayEvent()
10970 {
10971     if (gameMode == EditPosition) {
10972         blackPlaysFirst = TRUE;
10973         currentMove = 1;        /* kludge */
10974         DisplayBothClocks();
10975         currentMove = 0;
10976     } else if (gameMode == IcsExamining) {
10977         SendToICS(ics_prefix);
10978         SendToICS("tomove black\n");
10979     }
10980 }
10981
10982 void
10983 EditPositionMenuEvent(selection, x, y)
10984      ChessSquare selection;
10985      int x, y;
10986 {
10987     char buf[MSG_SIZ];
10988     ChessSquare piece = boards[0][y][x];
10989
10990     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10991
10992     switch (selection) {
10993       case ClearBoard:
10994         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10995             SendToICS(ics_prefix);
10996             SendToICS("bsetup clear\n");
10997         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10998             SendToICS(ics_prefix);
10999             SendToICS("clearboard\n");
11000         } else {
11001             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11002                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11003                 for (y = 0; y < BOARD_HEIGHT; y++) {
11004                     if (gameMode == IcsExamining) {
11005                         if (boards[currentMove][y][x] != EmptySquare) {
11006                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11007                                     AAA + x, ONE + y);
11008                             SendToICS(buf);
11009                         }
11010                     } else {
11011                         boards[0][y][x] = p;
11012                     }
11013                 }
11014             }
11015         }
11016         if (gameMode == EditPosition) {
11017             DrawPosition(FALSE, boards[0]);
11018         }
11019         break;
11020
11021       case WhitePlay:
11022         SetWhiteToPlayEvent();
11023         break;
11024
11025       case BlackPlay:
11026         SetBlackToPlayEvent();
11027         break;
11028
11029       case EmptySquare:
11030         if (gameMode == IcsExamining) {
11031             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11032             SendToICS(buf);
11033         } else {
11034             boards[0][y][x] = EmptySquare;
11035             DrawPosition(FALSE, boards[0]);
11036         }
11037         break;
11038
11039       case PromotePiece:
11040         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11041            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11042             selection = (ChessSquare) (PROMOTED piece);
11043         } else if(piece == EmptySquare) selection = WhiteSilver;
11044         else selection = (ChessSquare)((int)piece - 1);
11045         goto defaultlabel;
11046
11047       case DemotePiece:
11048         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11049            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11050             selection = (ChessSquare) (DEMOTED piece);
11051         } else if(piece == EmptySquare) selection = BlackSilver;
11052         else selection = (ChessSquare)((int)piece + 1);       
11053         goto defaultlabel;
11054
11055       case WhiteQueen:
11056       case BlackQueen:
11057         if(gameInfo.variant == VariantShatranj ||
11058            gameInfo.variant == VariantXiangqi  ||
11059            gameInfo.variant == VariantCourier    )
11060             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11061         goto defaultlabel;
11062
11063       case WhiteKing:
11064       case BlackKing:
11065         if(gameInfo.variant == VariantXiangqi)
11066             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11067         if(gameInfo.variant == VariantKnightmate)
11068             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11069       default:
11070         defaultlabel:
11071         if (gameMode == IcsExamining) {
11072             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11073                     PieceToChar(selection), AAA + x, ONE + y);
11074             SendToICS(buf);
11075         } else {
11076             boards[0][y][x] = selection;
11077             DrawPosition(FALSE, boards[0]);
11078         }
11079         break;
11080     }
11081 }
11082
11083
11084 void
11085 DropMenuEvent(selection, x, y)
11086      ChessSquare selection;
11087      int x, y;
11088 {
11089     ChessMove moveType;
11090
11091     switch (gameMode) {
11092       case IcsPlayingWhite:
11093       case MachinePlaysBlack:
11094         if (!WhiteOnMove(currentMove)) {
11095             DisplayMoveError(_("It is Black's turn"));
11096             return;
11097         }
11098         moveType = WhiteDrop;
11099         break;
11100       case IcsPlayingBlack:
11101       case MachinePlaysWhite:
11102         if (WhiteOnMove(currentMove)) {
11103             DisplayMoveError(_("It is White's turn"));
11104             return;
11105         }
11106         moveType = BlackDrop;
11107         break;
11108       case EditGame:
11109         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11110         break;
11111       default:
11112         return;
11113     }
11114
11115     if (moveType == BlackDrop && selection < BlackPawn) {
11116       selection = (ChessSquare) ((int) selection
11117                                  + (int) BlackPawn - (int) WhitePawn);
11118     }
11119     if (boards[currentMove][y][x] != EmptySquare) {
11120         DisplayMoveError(_("That square is occupied"));
11121         return;
11122     }
11123
11124     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11125 }
11126
11127 void
11128 AcceptEvent()
11129 {
11130     /* Accept a pending offer of any kind from opponent */
11131     
11132     if (appData.icsActive) {
11133         SendToICS(ics_prefix);
11134         SendToICS("accept\n");
11135     } else if (cmailMsgLoaded) {
11136         if (currentMove == cmailOldMove &&
11137             commentList[cmailOldMove] != NULL &&
11138             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11139                    "Black offers a draw" : "White offers a draw")) {
11140             TruncateGame();
11141             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11142             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11143         } else {
11144             DisplayError(_("There is no pending offer on this move"), 0);
11145             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11146         }
11147     } else {
11148         /* Not used for offers from chess program */
11149     }
11150 }
11151
11152 void
11153 DeclineEvent()
11154 {
11155     /* Decline a pending offer of any kind from opponent */
11156     
11157     if (appData.icsActive) {
11158         SendToICS(ics_prefix);
11159         SendToICS("decline\n");
11160     } else if (cmailMsgLoaded) {
11161         if (currentMove == cmailOldMove &&
11162             commentList[cmailOldMove] != NULL &&
11163             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11164                    "Black offers a draw" : "White offers a draw")) {
11165 #ifdef NOTDEF
11166             AppendComment(cmailOldMove, "Draw declined");
11167             DisplayComment(cmailOldMove - 1, "Draw declined");
11168 #endif /*NOTDEF*/
11169         } else {
11170             DisplayError(_("There is no pending offer on this move"), 0);
11171         }
11172     } else {
11173         /* Not used for offers from chess program */
11174     }
11175 }
11176
11177 void
11178 RematchEvent()
11179 {
11180     /* Issue ICS rematch command */
11181     if (appData.icsActive) {
11182         SendToICS(ics_prefix);
11183         SendToICS("rematch\n");
11184     }
11185 }
11186
11187 void
11188 CallFlagEvent()
11189 {
11190     /* Call your opponent's flag (claim a win on time) */
11191     if (appData.icsActive) {
11192         SendToICS(ics_prefix);
11193         SendToICS("flag\n");
11194     } else {
11195         switch (gameMode) {
11196           default:
11197             return;
11198           case MachinePlaysWhite:
11199             if (whiteFlag) {
11200                 if (blackFlag)
11201                   GameEnds(GameIsDrawn, "Both players ran out of time",
11202                            GE_PLAYER);
11203                 else
11204                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11205             } else {
11206                 DisplayError(_("Your opponent is not out of time"), 0);
11207             }
11208             break;
11209           case MachinePlaysBlack:
11210             if (blackFlag) {
11211                 if (whiteFlag)
11212                   GameEnds(GameIsDrawn, "Both players ran out of time",
11213                            GE_PLAYER);
11214                 else
11215                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11216             } else {
11217                 DisplayError(_("Your opponent is not out of time"), 0);
11218             }
11219             break;
11220         }
11221     }
11222 }
11223
11224 void
11225 DrawEvent()
11226 {
11227     /* Offer draw or accept pending draw offer from opponent */
11228     
11229     if (appData.icsActive) {
11230         /* Note: tournament rules require draw offers to be
11231            made after you make your move but before you punch
11232            your clock.  Currently ICS doesn't let you do that;
11233            instead, you immediately punch your clock after making
11234            a move, but you can offer a draw at any time. */
11235         
11236         SendToICS(ics_prefix);
11237         SendToICS("draw\n");
11238     } else if (cmailMsgLoaded) {
11239         if (currentMove == cmailOldMove &&
11240             commentList[cmailOldMove] != NULL &&
11241             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11242                    "Black offers a draw" : "White offers a draw")) {
11243             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11244             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11245         } else if (currentMove == cmailOldMove + 1) {
11246             char *offer = WhiteOnMove(cmailOldMove) ?
11247               "White offers a draw" : "Black offers a draw";
11248             AppendComment(currentMove, offer);
11249             DisplayComment(currentMove - 1, offer);
11250             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11251         } else {
11252             DisplayError(_("You must make your move before offering a draw"), 0);
11253             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11254         }
11255     } else if (first.offeredDraw) {
11256         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11257     } else {
11258         if (first.sendDrawOffers) {
11259             SendToProgram("draw\n", &first);
11260             userOfferedDraw = TRUE;
11261         }
11262     }
11263 }
11264
11265 void
11266 AdjournEvent()
11267 {
11268     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11269     
11270     if (appData.icsActive) {
11271         SendToICS(ics_prefix);
11272         SendToICS("adjourn\n");
11273     } else {
11274         /* Currently GNU Chess doesn't offer or accept Adjourns */
11275     }
11276 }
11277
11278
11279 void
11280 AbortEvent()
11281 {
11282     /* Offer Abort or accept pending Abort offer from opponent */
11283     
11284     if (appData.icsActive) {
11285         SendToICS(ics_prefix);
11286         SendToICS("abort\n");
11287     } else {
11288         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11289     }
11290 }
11291
11292 void
11293 ResignEvent()
11294 {
11295     /* Resign.  You can do this even if it's not your turn. */
11296     
11297     if (appData.icsActive) {
11298         SendToICS(ics_prefix);
11299         SendToICS("resign\n");
11300     } else {
11301         switch (gameMode) {
11302           case MachinePlaysWhite:
11303             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11304             break;
11305           case MachinePlaysBlack:
11306             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11307             break;
11308           case EditGame:
11309             if (cmailMsgLoaded) {
11310                 TruncateGame();
11311                 if (WhiteOnMove(cmailOldMove)) {
11312                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11313                 } else {
11314                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11315                 }
11316                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11317             }
11318             break;
11319           default:
11320             break;
11321         }
11322     }
11323 }
11324
11325
11326 void
11327 StopObservingEvent()
11328 {
11329     /* Stop observing current games */
11330     SendToICS(ics_prefix);
11331     SendToICS("unobserve\n");
11332 }
11333
11334 void
11335 StopExaminingEvent()
11336 {
11337     /* Stop observing current game */
11338     SendToICS(ics_prefix);
11339     SendToICS("unexamine\n");
11340 }
11341
11342 void
11343 ForwardInner(target)
11344      int target;
11345 {
11346     int limit;
11347
11348     if (appData.debugMode)
11349         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11350                 target, currentMove, forwardMostMove);
11351
11352     if (gameMode == EditPosition)
11353       return;
11354
11355     if (gameMode == PlayFromGameFile && !pausing)
11356       PauseEvent();
11357     
11358     if (gameMode == IcsExamining && pausing)
11359       limit = pauseExamForwardMostMove;
11360     else
11361       limit = forwardMostMove;
11362     
11363     if (target > limit) target = limit;
11364
11365     if (target > 0 && moveList[target - 1][0]) {
11366         int fromX, fromY, toX, toY;
11367         toX = moveList[target - 1][2] - AAA;
11368         toY = moveList[target - 1][3] - ONE;
11369         if (moveList[target - 1][1] == '@') {
11370             if (appData.highlightLastMove) {
11371                 SetHighlights(-1, -1, toX, toY);
11372             }
11373         } else {
11374             fromX = moveList[target - 1][0] - AAA;
11375             fromY = moveList[target - 1][1] - ONE;
11376             if (target == currentMove + 1) {
11377                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11378             }
11379             if (appData.highlightLastMove) {
11380                 SetHighlights(fromX, fromY, toX, toY);
11381             }
11382         }
11383     }
11384     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11385         gameMode == Training || gameMode == PlayFromGameFile || 
11386         gameMode == AnalyzeFile) {
11387         while (currentMove < target) {
11388             SendMoveToProgram(currentMove++, &first);
11389         }
11390     } else {
11391         currentMove = target;
11392     }
11393     
11394     if (gameMode == EditGame || gameMode == EndOfGame) {
11395         whiteTimeRemaining = timeRemaining[0][currentMove];
11396         blackTimeRemaining = timeRemaining[1][currentMove];
11397     }
11398     DisplayBothClocks();
11399     DisplayMove(currentMove - 1);
11400     DrawPosition(FALSE, boards[currentMove]);
11401     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11402     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11403         DisplayComment(currentMove - 1, commentList[currentMove]);
11404     }
11405 }
11406
11407
11408 void
11409 ForwardEvent()
11410 {
11411     if (gameMode == IcsExamining && !pausing) {
11412         SendToICS(ics_prefix);
11413         SendToICS("forward\n");
11414     } else {
11415         ForwardInner(currentMove + 1);
11416     }
11417 }
11418
11419 void
11420 ToEndEvent()
11421 {
11422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11423         /* to optimze, we temporarily turn off analysis mode while we feed
11424          * the remaining moves to the engine. Otherwise we get analysis output
11425          * after each move.
11426          */ 
11427         if (first.analysisSupport) {
11428           SendToProgram("exit\nforce\n", &first);
11429           first.analyzing = FALSE;
11430         }
11431     }
11432         
11433     if (gameMode == IcsExamining && !pausing) {
11434         SendToICS(ics_prefix);
11435         SendToICS("forward 999999\n");
11436     } else {
11437         ForwardInner(forwardMostMove);
11438     }
11439
11440     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11441         /* we have fed all the moves, so reactivate analysis mode */
11442         SendToProgram("analyze\n", &first);
11443         first.analyzing = TRUE;
11444         /*first.maybeThinking = TRUE;*/
11445         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11446     }
11447 }
11448
11449 void
11450 BackwardInner(target)
11451      int target;
11452 {
11453     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11454
11455     if (appData.debugMode)
11456         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11457                 target, currentMove, forwardMostMove);
11458
11459     if (gameMode == EditPosition) return;
11460     if (currentMove <= backwardMostMove) {
11461         ClearHighlights();
11462         DrawPosition(full_redraw, boards[currentMove]);
11463         return;
11464     }
11465     if (gameMode == PlayFromGameFile && !pausing)
11466       PauseEvent();
11467     
11468     if (moveList[target][0]) {
11469         int fromX, fromY, toX, toY;
11470         toX = moveList[target][2] - AAA;
11471         toY = moveList[target][3] - ONE;
11472         if (moveList[target][1] == '@') {
11473             if (appData.highlightLastMove) {
11474                 SetHighlights(-1, -1, toX, toY);
11475             }
11476         } else {
11477             fromX = moveList[target][0] - AAA;
11478             fromY = moveList[target][1] - ONE;
11479             if (target == currentMove - 1) {
11480                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11481             }
11482             if (appData.highlightLastMove) {
11483                 SetHighlights(fromX, fromY, toX, toY);
11484             }
11485         }
11486     }
11487     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11488         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11489         while (currentMove > target) {
11490             SendToProgram("undo\n", &first);
11491             currentMove--;
11492         }
11493     } else {
11494         currentMove = target;
11495     }
11496     
11497     if (gameMode == EditGame || gameMode == EndOfGame) {
11498         whiteTimeRemaining = timeRemaining[0][currentMove];
11499         blackTimeRemaining = timeRemaining[1][currentMove];
11500     }
11501     DisplayBothClocks();
11502     DisplayMove(currentMove - 1);
11503     DrawPosition(full_redraw, boards[currentMove]);
11504     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11505     // [HGM] PV info: routine tests if comment empty
11506     DisplayComment(currentMove - 1, commentList[currentMove]);
11507 }
11508
11509 void
11510 BackwardEvent()
11511 {
11512     if (gameMode == IcsExamining && !pausing) {
11513         SendToICS(ics_prefix);
11514         SendToICS("backward\n");
11515     } else {
11516         BackwardInner(currentMove - 1);
11517     }
11518 }
11519
11520 void
11521 ToStartEvent()
11522 {
11523     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11524         /* to optimze, we temporarily turn off analysis mode while we undo
11525          * all the moves. Otherwise we get analysis output after each undo.
11526          */ 
11527         if (first.analysisSupport) {
11528           SendToProgram("exit\nforce\n", &first);
11529           first.analyzing = FALSE;
11530         }
11531     }
11532
11533     if (gameMode == IcsExamining && !pausing) {
11534         SendToICS(ics_prefix);
11535         SendToICS("backward 999999\n");
11536     } else {
11537         BackwardInner(backwardMostMove);
11538     }
11539
11540     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11541         /* we have fed all the moves, so reactivate analysis mode */
11542         SendToProgram("analyze\n", &first);
11543         first.analyzing = TRUE;
11544         /*first.maybeThinking = TRUE;*/
11545         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11546     }
11547 }
11548
11549 void
11550 ToNrEvent(int to)
11551 {
11552   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11553   if (to >= forwardMostMove) to = forwardMostMove;
11554   if (to <= backwardMostMove) to = backwardMostMove;
11555   if (to < currentMove) {
11556     BackwardInner(to);
11557   } else {
11558     ForwardInner(to);
11559   }
11560 }
11561
11562 void
11563 RevertEvent()
11564 {
11565     if (gameMode != IcsExamining) {
11566         DisplayError(_("You are not examining a game"), 0);
11567         return;
11568     }
11569     if (pausing) {
11570         DisplayError(_("You can't revert while pausing"), 0);
11571         return;
11572     }
11573     SendToICS(ics_prefix);
11574     SendToICS("revert\n");
11575 }
11576
11577 void
11578 RetractMoveEvent()
11579 {
11580     switch (gameMode) {
11581       case MachinePlaysWhite:
11582       case MachinePlaysBlack:
11583         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11584             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11585             return;
11586         }
11587         if (forwardMostMove < 2) return;
11588         currentMove = forwardMostMove = forwardMostMove - 2;
11589         whiteTimeRemaining = timeRemaining[0][currentMove];
11590         blackTimeRemaining = timeRemaining[1][currentMove];
11591         DisplayBothClocks();
11592         DisplayMove(currentMove - 1);
11593         ClearHighlights();/*!! could figure this out*/
11594         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11595         SendToProgram("remove\n", &first);
11596         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11597         break;
11598
11599       case BeginningOfGame:
11600       default:
11601         break;
11602
11603       case IcsPlayingWhite:
11604       case IcsPlayingBlack:
11605         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11606             SendToICS(ics_prefix);
11607             SendToICS("takeback 2\n");
11608         } else {
11609             SendToICS(ics_prefix);
11610             SendToICS("takeback 1\n");
11611         }
11612         break;
11613     }
11614 }
11615
11616 void
11617 MoveNowEvent()
11618 {
11619     ChessProgramState *cps;
11620
11621     switch (gameMode) {
11622       case MachinePlaysWhite:
11623         if (!WhiteOnMove(forwardMostMove)) {
11624             DisplayError(_("It is your turn"), 0);
11625             return;
11626         }
11627         cps = &first;
11628         break;
11629       case MachinePlaysBlack:
11630         if (WhiteOnMove(forwardMostMove)) {
11631             DisplayError(_("It is your turn"), 0);
11632             return;
11633         }
11634         cps = &first;
11635         break;
11636       case TwoMachinesPlay:
11637         if (WhiteOnMove(forwardMostMove) ==
11638             (first.twoMachinesColor[0] == 'w')) {
11639             cps = &first;
11640         } else {
11641             cps = &second;
11642         }
11643         break;
11644       case BeginningOfGame:
11645       default:
11646         return;
11647     }
11648     SendToProgram("?\n", cps);
11649 }
11650
11651 void
11652 TruncateGameEvent()
11653 {
11654     EditGameEvent();
11655     if (gameMode != EditGame) return;
11656     TruncateGame();
11657 }
11658
11659 void
11660 TruncateGame()
11661 {
11662     if (forwardMostMove > currentMove) {
11663         if (gameInfo.resultDetails != NULL) {
11664             free(gameInfo.resultDetails);
11665             gameInfo.resultDetails = NULL;
11666             gameInfo.result = GameUnfinished;
11667         }
11668         forwardMostMove = currentMove;
11669         HistorySet(parseList, backwardMostMove, forwardMostMove,
11670                    currentMove-1);
11671     }
11672 }
11673
11674 void
11675 HintEvent()
11676 {
11677     if (appData.noChessProgram) return;
11678     switch (gameMode) {
11679       case MachinePlaysWhite:
11680         if (WhiteOnMove(forwardMostMove)) {
11681             DisplayError(_("Wait until your turn"), 0);
11682             return;
11683         }
11684         break;
11685       case BeginningOfGame:
11686       case MachinePlaysBlack:
11687         if (!WhiteOnMove(forwardMostMove)) {
11688             DisplayError(_("Wait until your turn"), 0);
11689             return;
11690         }
11691         break;
11692       default:
11693         DisplayError(_("No hint available"), 0);
11694         return;
11695     }
11696     SendToProgram("hint\n", &first);
11697     hintRequested = TRUE;
11698 }
11699
11700 void
11701 BookEvent()
11702 {
11703     if (appData.noChessProgram) return;
11704     switch (gameMode) {
11705       case MachinePlaysWhite:
11706         if (WhiteOnMove(forwardMostMove)) {
11707             DisplayError(_("Wait until your turn"), 0);
11708             return;
11709         }
11710         break;
11711       case BeginningOfGame:
11712       case MachinePlaysBlack:
11713         if (!WhiteOnMove(forwardMostMove)) {
11714             DisplayError(_("Wait until your turn"), 0);
11715             return;
11716         }
11717         break;
11718       case EditPosition:
11719         EditPositionDone();
11720         break;
11721       case TwoMachinesPlay:
11722         return;
11723       default:
11724         break;
11725     }
11726     SendToProgram("bk\n", &first);
11727     bookOutput[0] = NULLCHAR;
11728     bookRequested = TRUE;
11729 }
11730
11731 void
11732 AboutGameEvent()
11733 {
11734     char *tags = PGNTags(&gameInfo);
11735     TagsPopUp(tags, CmailMsg());
11736     free(tags);
11737 }
11738
11739 /* end button procedures */
11740
11741 void
11742 PrintPosition(fp, move)
11743      FILE *fp;
11744      int move;
11745 {
11746     int i, j;
11747     
11748     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11749         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11750             char c = PieceToChar(boards[move][i][j]);
11751             fputc(c == 'x' ? '.' : c, fp);
11752             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11753         }
11754     }
11755     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11756       fprintf(fp, "white to play\n");
11757     else
11758       fprintf(fp, "black to play\n");
11759 }
11760
11761 void
11762 PrintOpponents(fp)
11763      FILE *fp;
11764 {
11765     if (gameInfo.white != NULL) {
11766         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11767     } else {
11768         fprintf(fp, "\n");
11769     }
11770 }
11771
11772 /* Find last component of program's own name, using some heuristics */
11773 void
11774 TidyProgramName(prog, host, buf)
11775      char *prog, *host, buf[MSG_SIZ];
11776 {
11777     char *p, *q;
11778     int local = (strcmp(host, "localhost") == 0);
11779     while (!local && (p = strchr(prog, ';')) != NULL) {
11780         p++;
11781         while (*p == ' ') p++;
11782         prog = p;
11783     }
11784     if (*prog == '"' || *prog == '\'') {
11785         q = strchr(prog + 1, *prog);
11786     } else {
11787         q = strchr(prog, ' ');
11788     }
11789     if (q == NULL) q = prog + strlen(prog);
11790     p = q;
11791     while (p >= prog && *p != '/' && *p != '\\') p--;
11792     p++;
11793     if(p == prog && *p == '"') p++;
11794     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11795     memcpy(buf, p, q - p);
11796     buf[q - p] = NULLCHAR;
11797     if (!local) {
11798         strcat(buf, "@");
11799         strcat(buf, host);
11800     }
11801 }
11802
11803 char *
11804 TimeControlTagValue()
11805 {
11806     char buf[MSG_SIZ];
11807     if (!appData.clockMode) {
11808         strcpy(buf, "-");
11809     } else if (movesPerSession > 0) {
11810         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11811     } else if (timeIncrement == 0) {
11812         sprintf(buf, "%ld", timeControl/1000);
11813     } else {
11814         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11815     }
11816     return StrSave(buf);
11817 }
11818
11819 void
11820 SetGameInfo()
11821 {
11822     /* This routine is used only for certain modes */
11823     VariantClass v = gameInfo.variant;
11824     ClearGameInfo(&gameInfo);
11825     gameInfo.variant = v;
11826
11827     switch (gameMode) {
11828       case MachinePlaysWhite:
11829         gameInfo.event = StrSave( appData.pgnEventHeader );
11830         gameInfo.site = StrSave(HostName());
11831         gameInfo.date = PGNDate();
11832         gameInfo.round = StrSave("-");
11833         gameInfo.white = StrSave(first.tidy);
11834         gameInfo.black = StrSave(UserName());
11835         gameInfo.timeControl = TimeControlTagValue();
11836         break;
11837
11838       case MachinePlaysBlack:
11839         gameInfo.event = StrSave( appData.pgnEventHeader );
11840         gameInfo.site = StrSave(HostName());
11841         gameInfo.date = PGNDate();
11842         gameInfo.round = StrSave("-");
11843         gameInfo.white = StrSave(UserName());
11844         gameInfo.black = StrSave(first.tidy);
11845         gameInfo.timeControl = TimeControlTagValue();
11846         break;
11847
11848       case TwoMachinesPlay:
11849         gameInfo.event = StrSave( appData.pgnEventHeader );
11850         gameInfo.site = StrSave(HostName());
11851         gameInfo.date = PGNDate();
11852         if (matchGame > 0) {
11853             char buf[MSG_SIZ];
11854             sprintf(buf, "%d", matchGame);
11855             gameInfo.round = StrSave(buf);
11856         } else {
11857             gameInfo.round = StrSave("-");
11858         }
11859         if (first.twoMachinesColor[0] == 'w') {
11860             gameInfo.white = StrSave(first.tidy);
11861             gameInfo.black = StrSave(second.tidy);
11862         } else {
11863             gameInfo.white = StrSave(second.tidy);
11864             gameInfo.black = StrSave(first.tidy);
11865         }
11866         gameInfo.timeControl = TimeControlTagValue();
11867         break;
11868
11869       case EditGame:
11870         gameInfo.event = StrSave("Edited game");
11871         gameInfo.site = StrSave(HostName());
11872         gameInfo.date = PGNDate();
11873         gameInfo.round = StrSave("-");
11874         gameInfo.white = StrSave("-");
11875         gameInfo.black = StrSave("-");
11876         break;
11877
11878       case EditPosition:
11879         gameInfo.event = StrSave("Edited position");
11880         gameInfo.site = StrSave(HostName());
11881         gameInfo.date = PGNDate();
11882         gameInfo.round = StrSave("-");
11883         gameInfo.white = StrSave("-");
11884         gameInfo.black = StrSave("-");
11885         break;
11886
11887       case IcsPlayingWhite:
11888       case IcsPlayingBlack:
11889       case IcsObserving:
11890       case IcsExamining:
11891         break;
11892
11893       case PlayFromGameFile:
11894         gameInfo.event = StrSave("Game from non-PGN file");
11895         gameInfo.site = StrSave(HostName());
11896         gameInfo.date = PGNDate();
11897         gameInfo.round = StrSave("-");
11898         gameInfo.white = StrSave("?");
11899         gameInfo.black = StrSave("?");
11900         break;
11901
11902       default:
11903         break;
11904     }
11905 }
11906
11907 void
11908 ReplaceComment(index, text)
11909      int index;
11910      char *text;
11911 {
11912     int len;
11913
11914     while (*text == '\n') text++;
11915     len = strlen(text);
11916     while (len > 0 && text[len - 1] == '\n') len--;
11917
11918     if (commentList[index] != NULL)
11919       free(commentList[index]);
11920
11921     if (len == 0) {
11922         commentList[index] = NULL;
11923         return;
11924     }
11925     commentList[index] = (char *) malloc(len + 2);
11926     strncpy(commentList[index], text, len);
11927     commentList[index][len] = '\n';
11928     commentList[index][len + 1] = NULLCHAR;
11929 }
11930
11931 void
11932 CrushCRs(text)
11933      char *text;
11934 {
11935   char *p = text;
11936   char *q = text;
11937   char ch;
11938
11939   do {
11940     ch = *p++;
11941     if (ch == '\r') continue;
11942     *q++ = ch;
11943   } while (ch != '\0');
11944 }
11945
11946 void
11947 AppendComment(index, text)
11948      int index;
11949      char *text;
11950 {
11951     int oldlen, len;
11952     char *old;
11953
11954     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11955
11956     CrushCRs(text);
11957     while (*text == '\n') text++;
11958     len = strlen(text);
11959     while (len > 0 && text[len - 1] == '\n') len--;
11960
11961     if (len == 0) return;
11962
11963     if (commentList[index] != NULL) {
11964         old = commentList[index];
11965         oldlen = strlen(old);
11966         commentList[index] = (char *) malloc(oldlen + len + 2);
11967         strcpy(commentList[index], old);
11968         free(old);
11969         strncpy(&commentList[index][oldlen], text, len);
11970         commentList[index][oldlen + len] = '\n';
11971         commentList[index][oldlen + len + 1] = NULLCHAR;
11972     } else {
11973         commentList[index] = (char *) malloc(len + 2);
11974         strncpy(commentList[index], text, len);
11975         commentList[index][len] = '\n';
11976         commentList[index][len + 1] = NULLCHAR;
11977     }
11978 }
11979
11980 static char * FindStr( char * text, char * sub_text )
11981 {
11982     char * result = strstr( text, sub_text );
11983
11984     if( result != NULL ) {
11985         result += strlen( sub_text );
11986     }
11987
11988     return result;
11989 }
11990
11991 /* [AS] Try to extract PV info from PGN comment */
11992 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11993 char *GetInfoFromComment( int index, char * text )
11994 {
11995     char * sep = text;
11996
11997     if( text != NULL && index > 0 ) {
11998         int score = 0;
11999         int depth = 0;
12000         int time = -1, sec = 0, deci;
12001         char * s_eval = FindStr( text, "[%eval " );
12002         char * s_emt = FindStr( text, "[%emt " );
12003
12004         if( s_eval != NULL || s_emt != NULL ) {
12005             /* New style */
12006             char delim;
12007
12008             if( s_eval != NULL ) {
12009                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12010                     return text;
12011                 }
12012
12013                 if( delim != ']' ) {
12014                     return text;
12015                 }
12016             }
12017
12018             if( s_emt != NULL ) {
12019             }
12020         }
12021         else {
12022             /* We expect something like: [+|-]nnn.nn/dd */
12023             int score_lo = 0;
12024
12025             sep = strchr( text, '/' );
12026             if( sep == NULL || sep < (text+4) ) {
12027                 return text;
12028             }
12029
12030             time = -1; sec = -1; deci = -1;
12031             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12032                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12033                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12034                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12035                 return text;
12036             }
12037
12038             if( score_lo < 0 || score_lo >= 100 ) {
12039                 return text;
12040             }
12041
12042             if(sec >= 0) time = 600*time + 10*sec; else
12043             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12044
12045             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12046
12047             /* [HGM] PV time: now locate end of PV info */
12048             while( *++sep >= '0' && *sep <= '9'); // strip depth
12049             if(time >= 0)
12050             while( *++sep >= '0' && *sep <= '9'); // strip time
12051             if(sec >= 0)
12052             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12053             if(deci >= 0)
12054             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12055             while(*sep == ' ') sep++;
12056         }
12057
12058         if( depth <= 0 ) {
12059             return text;
12060         }
12061
12062         if( time < 0 ) {
12063             time = -1;
12064         }
12065
12066         pvInfoList[index-1].depth = depth;
12067         pvInfoList[index-1].score = score;
12068         pvInfoList[index-1].time  = 10*time; // centi-sec
12069     }
12070     return sep;
12071 }
12072
12073 void
12074 SendToProgram(message, cps)
12075      char *message;
12076      ChessProgramState *cps;
12077 {
12078     int count, outCount, error;
12079     char buf[MSG_SIZ];
12080
12081     if (cps->pr == NULL) return;
12082     Attention(cps);
12083     
12084     if (appData.debugMode) {
12085         TimeMark now;
12086         GetTimeMark(&now);
12087         fprintf(debugFP, "%ld >%-6s: %s", 
12088                 SubtractTimeMarks(&now, &programStartTime),
12089                 cps->which, message);
12090     }
12091     
12092     count = strlen(message);
12093     outCount = OutputToProcess(cps->pr, message, count, &error);
12094     if (outCount < count && !exiting 
12095                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12096         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12097         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12098             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12099                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12100                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12101             } else {
12102                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12103             }
12104             gameInfo.resultDetails = buf;
12105         }
12106         DisplayFatalError(buf, error, 1);
12107     }
12108 }
12109
12110 void
12111 ReceiveFromProgram(isr, closure, message, count, error)
12112      InputSourceRef isr;
12113      VOIDSTAR closure;
12114      char *message;
12115      int count;
12116      int error;
12117 {
12118     char *end_str;
12119     char buf[MSG_SIZ];
12120     ChessProgramState *cps = (ChessProgramState *)closure;
12121
12122     if (isr != cps->isr) return; /* Killed intentionally */
12123     if (count <= 0) {
12124         if (count == 0) {
12125             sprintf(buf,
12126                     _("Error: %s chess program (%s) exited unexpectedly"),
12127                     cps->which, cps->program);
12128         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12129                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12130                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12131                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12132                 } else {
12133                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12134                 }
12135                 gameInfo.resultDetails = buf;
12136             }
12137             RemoveInputSource(cps->isr);
12138             DisplayFatalError(buf, 0, 1);
12139         } else {
12140             sprintf(buf,
12141                     _("Error reading from %s chess program (%s)"),
12142                     cps->which, cps->program);
12143             RemoveInputSource(cps->isr);
12144
12145             /* [AS] Program is misbehaving badly... kill it */
12146             if( count == -2 ) {
12147                 DestroyChildProcess( cps->pr, 9 );
12148                 cps->pr = NoProc;
12149             }
12150
12151             DisplayFatalError(buf, error, 1);
12152         }
12153         return;
12154     }
12155     
12156     if ((end_str = strchr(message, '\r')) != NULL)
12157       *end_str = NULLCHAR;
12158     if ((end_str = strchr(message, '\n')) != NULL)
12159       *end_str = NULLCHAR;
12160     
12161     if (appData.debugMode) {
12162         TimeMark now; int print = 1;
12163         char *quote = ""; char c; int i;
12164
12165         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12166                 char start = message[0];
12167                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12168                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12169                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12170                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12171                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12172                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12173                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12174                         { quote = "# "; print = (appData.engineComments == 2); }
12175                 message[0] = start; // restore original message
12176         }
12177         if(print) {
12178                 GetTimeMark(&now);
12179                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12180                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12181                         quote,
12182                         message);
12183         }
12184     }
12185
12186     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12187     if (appData.icsEngineAnalyze) {
12188         if (strstr(message, "whisper") != NULL ||
12189              strstr(message, "kibitz") != NULL || 
12190             strstr(message, "tellics") != NULL) return;
12191     }
12192
12193     HandleMachineMove(message, cps);
12194 }
12195
12196
12197 void
12198 SendTimeControl(cps, mps, tc, inc, sd, st)
12199      ChessProgramState *cps;
12200      int mps, inc, sd, st;
12201      long tc;
12202 {
12203     char buf[MSG_SIZ];
12204     int seconds;
12205
12206     if( timeControl_2 > 0 ) {
12207         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12208             tc = timeControl_2;
12209         }
12210     }
12211     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12212     inc /= cps->timeOdds;
12213     st  /= cps->timeOdds;
12214
12215     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12216
12217     if (st > 0) {
12218       /* Set exact time per move, normally using st command */
12219       if (cps->stKludge) {
12220         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12221         seconds = st % 60;
12222         if (seconds == 0) {
12223           sprintf(buf, "level 1 %d\n", st/60);
12224         } else {
12225           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12226         }
12227       } else {
12228         sprintf(buf, "st %d\n", st);
12229       }
12230     } else {
12231       /* Set conventional or incremental time control, using level command */
12232       if (seconds == 0) {
12233         /* Note old gnuchess bug -- minutes:seconds used to not work.
12234            Fixed in later versions, but still avoid :seconds
12235            when seconds is 0. */
12236         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12237       } else {
12238         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12239                 seconds, inc/1000);
12240       }
12241     }
12242     SendToProgram(buf, cps);
12243
12244     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12245     /* Orthogonally, limit search to given depth */
12246     if (sd > 0) {
12247       if (cps->sdKludge) {
12248         sprintf(buf, "depth\n%d\n", sd);
12249       } else {
12250         sprintf(buf, "sd %d\n", sd);
12251       }
12252       SendToProgram(buf, cps);
12253     }
12254
12255     if(cps->nps > 0) { /* [HGM] nps */
12256         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12257         else {
12258                 sprintf(buf, "nps %d\n", cps->nps);
12259               SendToProgram(buf, cps);
12260         }
12261     }
12262 }
12263
12264 ChessProgramState *WhitePlayer()
12265 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12266 {
12267     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12268        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12269         return &second;
12270     return &first;
12271 }
12272
12273 void
12274 SendTimeRemaining(cps, machineWhite)
12275      ChessProgramState *cps;
12276      int /*boolean*/ machineWhite;
12277 {
12278     char message[MSG_SIZ];
12279     long time, otime;
12280
12281     /* Note: this routine must be called when the clocks are stopped
12282        or when they have *just* been set or switched; otherwise
12283        it will be off by the time since the current tick started.
12284     */
12285     if (machineWhite) {
12286         time = whiteTimeRemaining / 10;
12287         otime = blackTimeRemaining / 10;
12288     } else {
12289         time = blackTimeRemaining / 10;
12290         otime = whiteTimeRemaining / 10;
12291     }
12292     /* [HGM] translate opponent's time by time-odds factor */
12293     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12294     if (appData.debugMode) {
12295         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12296     }
12297
12298     if (time <= 0) time = 1;
12299     if (otime <= 0) otime = 1;
12300     
12301     sprintf(message, "time %ld\n", time);
12302     SendToProgram(message, cps);
12303
12304     sprintf(message, "otim %ld\n", otime);
12305     SendToProgram(message, cps);
12306 }
12307
12308 int
12309 BoolFeature(p, name, loc, cps)
12310      char **p;
12311      char *name;
12312      int *loc;
12313      ChessProgramState *cps;
12314 {
12315   char buf[MSG_SIZ];
12316   int len = strlen(name);
12317   int val;
12318   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12319     (*p) += len + 1;
12320     sscanf(*p, "%d", &val);
12321     *loc = (val != 0);
12322     while (**p && **p != ' ') (*p)++;
12323     sprintf(buf, "accepted %s\n", name);
12324     SendToProgram(buf, cps);
12325     return TRUE;
12326   }
12327   return FALSE;
12328 }
12329
12330 int
12331 IntFeature(p, name, loc, cps)
12332      char **p;
12333      char *name;
12334      int *loc;
12335      ChessProgramState *cps;
12336 {
12337   char buf[MSG_SIZ];
12338   int len = strlen(name);
12339   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12340     (*p) += len + 1;
12341     sscanf(*p, "%d", loc);
12342     while (**p && **p != ' ') (*p)++;
12343     sprintf(buf, "accepted %s\n", name);
12344     SendToProgram(buf, cps);
12345     return TRUE;
12346   }
12347   return FALSE;
12348 }
12349
12350 int
12351 StringFeature(p, name, loc, cps)
12352      char **p;
12353      char *name;
12354      char loc[];
12355      ChessProgramState *cps;
12356 {
12357   char buf[MSG_SIZ];
12358   int len = strlen(name);
12359   if (strncmp((*p), name, len) == 0
12360       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12361     (*p) += len + 2;
12362     sscanf(*p, "%[^\"]", loc);
12363     while (**p && **p != '\"') (*p)++;
12364     if (**p == '\"') (*p)++;
12365     sprintf(buf, "accepted %s\n", name);
12366     SendToProgram(buf, cps);
12367     return TRUE;
12368   }
12369   return FALSE;
12370 }
12371
12372 int 
12373 ParseOption(Option *opt, ChessProgramState *cps)
12374 // [HGM] options: process the string that defines an engine option, and determine
12375 // name, type, default value, and allowed value range
12376 {
12377         char *p, *q, buf[MSG_SIZ];
12378         int n, min = (-1)<<31, max = 1<<31, def;
12379
12380         if(p = strstr(opt->name, " -spin ")) {
12381             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12382             if(max < min) max = min; // enforce consistency
12383             if(def < min) def = min;
12384             if(def > max) def = max;
12385             opt->value = def;
12386             opt->min = min;
12387             opt->max = max;
12388             opt->type = Spin;
12389         } else if((p = strstr(opt->name, " -slider "))) {
12390             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12391             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12392             if(max < min) max = min; // enforce consistency
12393             if(def < min) def = min;
12394             if(def > max) def = max;
12395             opt->value = def;
12396             opt->min = min;
12397             opt->max = max;
12398             opt->type = Spin; // Slider;
12399         } else if((p = strstr(opt->name, " -string "))) {
12400             opt->textValue = p+9;
12401             opt->type = TextBox;
12402         } else if((p = strstr(opt->name, " -file "))) {
12403             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12404             opt->textValue = p+7;
12405             opt->type = TextBox; // FileName;
12406         } else if((p = strstr(opt->name, " -path "))) {
12407             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12408             opt->textValue = p+7;
12409             opt->type = TextBox; // PathName;
12410         } else if(p = strstr(opt->name, " -check ")) {
12411             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12412             opt->value = (def != 0);
12413             opt->type = CheckBox;
12414         } else if(p = strstr(opt->name, " -combo ")) {
12415             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12416             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12417             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12418             opt->value = n = 0;
12419             while(q = StrStr(q, " /// ")) {
12420                 n++; *q = 0;    // count choices, and null-terminate each of them
12421                 q += 5;
12422                 if(*q == '*') { // remember default, which is marked with * prefix
12423                     q++;
12424                     opt->value = n;
12425                 }
12426                 cps->comboList[cps->comboCnt++] = q;
12427             }
12428             cps->comboList[cps->comboCnt++] = NULL;
12429             opt->max = n + 1;
12430             opt->type = ComboBox;
12431         } else if(p = strstr(opt->name, " -button")) {
12432             opt->type = Button;
12433         } else if(p = strstr(opt->name, " -save")) {
12434             opt->type = SaveButton;
12435         } else return FALSE;
12436         *p = 0; // terminate option name
12437         // now look if the command-line options define a setting for this engine option.
12438         if(cps->optionSettings && cps->optionSettings[0])
12439             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12440         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12441                 sprintf(buf, "option %s", p);
12442                 if(p = strstr(buf, ",")) *p = 0;
12443                 strcat(buf, "\n");
12444                 SendToProgram(buf, cps);
12445         }
12446         return TRUE;
12447 }
12448
12449 void
12450 FeatureDone(cps, val)
12451      ChessProgramState* cps;
12452      int val;
12453 {
12454   DelayedEventCallback cb = GetDelayedEvent();
12455   if ((cb == InitBackEnd3 && cps == &first) ||
12456       (cb == TwoMachinesEventIfReady && cps == &second)) {
12457     CancelDelayedEvent();
12458     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12459   }
12460   cps->initDone = val;
12461 }
12462
12463 /* Parse feature command from engine */
12464 void
12465 ParseFeatures(args, cps)
12466      char* args;
12467      ChessProgramState *cps;  
12468 {
12469   char *p = args;
12470   char *q;
12471   int val;
12472   char buf[MSG_SIZ];
12473
12474   for (;;) {
12475     while (*p == ' ') p++;
12476     if (*p == NULLCHAR) return;
12477
12478     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12479     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12480     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12481     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12482     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12483     if (BoolFeature(&p, "reuse", &val, cps)) {
12484       /* Engine can disable reuse, but can't enable it if user said no */
12485       if (!val) cps->reuse = FALSE;
12486       continue;
12487     }
12488     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12489     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12490       if (gameMode == TwoMachinesPlay) {
12491         DisplayTwoMachinesTitle();
12492       } else {
12493         DisplayTitle("");
12494       }
12495       continue;
12496     }
12497     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12498     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12499     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12500     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12501     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12502     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12503     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12504     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12505     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12506     if (IntFeature(&p, "done", &val, cps)) {
12507       FeatureDone(cps, val);
12508       continue;
12509     }
12510     /* Added by Tord: */
12511     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12512     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12513     /* End of additions by Tord */
12514
12515     /* [HGM] added features: */
12516     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12517     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12518     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12519     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12520     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12521     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12522     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12523         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12524         if(cps->nrOptions >= MAX_OPTIONS) {
12525             cps->nrOptions--;
12526             sprintf(buf, "%s engine has too many options\n", cps->which);
12527             DisplayError(buf, 0);
12528         }
12529         continue;
12530     }
12531     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12532     /* End of additions by HGM */
12533
12534     /* unknown feature: complain and skip */
12535     q = p;
12536     while (*q && *q != '=') q++;
12537     sprintf(buf, "rejected %.*s\n", q-p, p);
12538     SendToProgram(buf, cps);
12539     p = q;
12540     if (*p == '=') {
12541       p++;
12542       if (*p == '\"') {
12543         p++;
12544         while (*p && *p != '\"') p++;
12545         if (*p == '\"') p++;
12546       } else {
12547         while (*p && *p != ' ') p++;
12548       }
12549     }
12550   }
12551
12552 }
12553
12554 void
12555 PeriodicUpdatesEvent(newState)
12556      int newState;
12557 {
12558     if (newState == appData.periodicUpdates)
12559       return;
12560
12561     appData.periodicUpdates=newState;
12562
12563     /* Display type changes, so update it now */
12564     DisplayAnalysis();
12565
12566     /* Get the ball rolling again... */
12567     if (newState) {
12568         AnalysisPeriodicEvent(1);
12569         StartAnalysisClock();
12570     }
12571 }
12572
12573 void
12574 PonderNextMoveEvent(newState)
12575      int newState;
12576 {
12577     if (newState == appData.ponderNextMove) return;
12578     if (gameMode == EditPosition) EditPositionDone();
12579     if (newState) {
12580         SendToProgram("hard\n", &first);
12581         if (gameMode == TwoMachinesPlay) {
12582             SendToProgram("hard\n", &second);
12583         }
12584     } else {
12585         SendToProgram("easy\n", &first);
12586         thinkOutput[0] = NULLCHAR;
12587         if (gameMode == TwoMachinesPlay) {
12588             SendToProgram("easy\n", &second);
12589         }
12590     }
12591     appData.ponderNextMove = newState;
12592 }
12593
12594 void
12595 NewSettingEvent(option, command, value)
12596      char *command;
12597      int option, value;
12598 {
12599     char buf[MSG_SIZ];
12600
12601     if (gameMode == EditPosition) EditPositionDone();
12602     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12603     SendToProgram(buf, &first);
12604     if (gameMode == TwoMachinesPlay) {
12605         SendToProgram(buf, &second);
12606     }
12607 }
12608
12609 void
12610 ShowThinkingEvent()
12611 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12612 {
12613     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12614     int newState = appData.showThinking
12615         // [HGM] thinking: other features now need thinking output as well
12616         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12617     
12618     if (oldState == newState) return;
12619     oldState = newState;
12620     if (gameMode == EditPosition) EditPositionDone();
12621     if (oldState) {
12622         SendToProgram("post\n", &first);
12623         if (gameMode == TwoMachinesPlay) {
12624             SendToProgram("post\n", &second);
12625         }
12626     } else {
12627         SendToProgram("nopost\n", &first);
12628         thinkOutput[0] = NULLCHAR;
12629         if (gameMode == TwoMachinesPlay) {
12630             SendToProgram("nopost\n", &second);
12631         }
12632     }
12633 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12634 }
12635
12636 void
12637 AskQuestionEvent(title, question, replyPrefix, which)
12638      char *title; char *question; char *replyPrefix; char *which;
12639 {
12640   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12641   if (pr == NoProc) return;
12642   AskQuestion(title, question, replyPrefix, pr);
12643 }
12644
12645 void
12646 DisplayMove(moveNumber)
12647      int moveNumber;
12648 {
12649     char message[MSG_SIZ];
12650     char res[MSG_SIZ];
12651     char cpThinkOutput[MSG_SIZ];
12652
12653     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12654     
12655     if (moveNumber == forwardMostMove - 1 || 
12656         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12657
12658         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12659
12660         if (strchr(cpThinkOutput, '\n')) {
12661             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12662         }
12663     } else {
12664         *cpThinkOutput = NULLCHAR;
12665     }
12666
12667     /* [AS] Hide thinking from human user */
12668     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12669         *cpThinkOutput = NULLCHAR;
12670         if( thinkOutput[0] != NULLCHAR ) {
12671             int i;
12672
12673             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12674                 cpThinkOutput[i] = '.';
12675             }
12676             cpThinkOutput[i] = NULLCHAR;
12677             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12678         }
12679     }
12680
12681     if (moveNumber == forwardMostMove - 1 &&
12682         gameInfo.resultDetails != NULL) {
12683         if (gameInfo.resultDetails[0] == NULLCHAR) {
12684             sprintf(res, " %s", PGNResult(gameInfo.result));
12685         } else {
12686             sprintf(res, " {%s} %s",
12687                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12688         }
12689     } else {
12690         res[0] = NULLCHAR;
12691     }
12692
12693     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12694         DisplayMessage(res, cpThinkOutput);
12695     } else {
12696         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12697                 WhiteOnMove(moveNumber) ? " " : ".. ",
12698                 parseList[moveNumber], res);
12699         DisplayMessage(message, cpThinkOutput);
12700     }
12701 }
12702
12703 void
12704 DisplayAnalysisText(text)
12705      char *text;
12706 {
12707     char buf[MSG_SIZ];
12708
12709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12710                || appData.icsEngineAnalyze) {
12711         sprintf(buf, "Analysis (%s)", first.tidy);
12712         AnalysisPopUp(buf, text);
12713     }
12714 }
12715
12716 static int
12717 only_one_move(str)
12718      char *str;
12719 {
12720     while (*str && isspace(*str)) ++str;
12721     while (*str && !isspace(*str)) ++str;
12722     if (!*str) return 1;
12723     while (*str && isspace(*str)) ++str;
12724     if (!*str) return 1;
12725     return 0;
12726 }
12727
12728 void
12729 DisplayAnalysis()
12730 {
12731     char buf[MSG_SIZ];
12732     char lst[MSG_SIZ / 2];
12733     double nps;
12734     static char *xtra[] = { "", " (--)", " (++)" };
12735     int h, m, s, cs;
12736   
12737     if (programStats.time == 0) {
12738         programStats.time = 1;
12739     }
12740   
12741     if (programStats.got_only_move) {
12742         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12743     } else {
12744         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12745
12746         nps = (u64ToDouble(programStats.nodes) /
12747              ((double)programStats.time /100.0));
12748
12749         cs = programStats.time % 100;
12750         s = programStats.time / 100;
12751         h = (s / (60*60));
12752         s = s - h*60*60;
12753         m = (s/60);
12754         s = s - m*60;
12755
12756         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12757           if (programStats.move_name[0] != NULLCHAR) {
12758             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12759                     programStats.depth,
12760                     programStats.nr_moves-programStats.moves_left,
12761                     programStats.nr_moves, programStats.move_name,
12762                     ((float)programStats.score)/100.0, lst,
12763                     only_one_move(lst)?
12764                     xtra[programStats.got_fail] : "",
12765                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12766           } else {
12767             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12768                     programStats.depth,
12769                     programStats.nr_moves-programStats.moves_left,
12770                     programStats.nr_moves, ((float)programStats.score)/100.0,
12771                     lst,
12772                     only_one_move(lst)?
12773                     xtra[programStats.got_fail] : "",
12774                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12775           }
12776         } else {
12777             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12778                     programStats.depth,
12779                     ((float)programStats.score)/100.0,
12780                     lst,
12781                     only_one_move(lst)?
12782                     xtra[programStats.got_fail] : "",
12783                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12784         }
12785     }
12786     DisplayAnalysisText(buf);
12787 }
12788
12789 void
12790 DisplayComment(moveNumber, text)
12791      int moveNumber;
12792      char *text;
12793 {
12794     char title[MSG_SIZ];
12795     char buf[8000]; // comment can be long!
12796     int score, depth;
12797
12798     if( appData.autoDisplayComment ) {
12799         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12800             strcpy(title, "Comment");
12801         } else {
12802             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12803                     WhiteOnMove(moveNumber) ? " " : ".. ",
12804                     parseList[moveNumber]);
12805         }
12806         // [HGM] PV info: display PV info together with (or as) comment
12807         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12808             if(text == NULL) text = "";                                           
12809             score = pvInfoList[moveNumber].score;
12810             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12811                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12812             text = buf;
12813         }
12814     } else title[0] = 0;
12815
12816     if (text != NULL)
12817         CommentPopUp(title, text);
12818 }
12819
12820 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12821  * might be busy thinking or pondering.  It can be omitted if your
12822  * gnuchess is configured to stop thinking immediately on any user
12823  * input.  However, that gnuchess feature depends on the FIONREAD
12824  * ioctl, which does not work properly on some flavors of Unix.
12825  */
12826 void
12827 Attention(cps)
12828      ChessProgramState *cps;
12829 {
12830 #if ATTENTION
12831     if (!cps->useSigint) return;
12832     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12833     switch (gameMode) {
12834       case MachinePlaysWhite:
12835       case MachinePlaysBlack:
12836       case TwoMachinesPlay:
12837       case IcsPlayingWhite:
12838       case IcsPlayingBlack:
12839       case AnalyzeMode:
12840       case AnalyzeFile:
12841         /* Skip if we know it isn't thinking */
12842         if (!cps->maybeThinking) return;
12843         if (appData.debugMode)
12844           fprintf(debugFP, "Interrupting %s\n", cps->which);
12845         InterruptChildProcess(cps->pr);
12846         cps->maybeThinking = FALSE;
12847         break;
12848       default:
12849         break;
12850     }
12851 #endif /*ATTENTION*/
12852 }
12853
12854 int
12855 CheckFlags()
12856 {
12857     if (whiteTimeRemaining <= 0) {
12858         if (!whiteFlag) {
12859             whiteFlag = TRUE;
12860             if (appData.icsActive) {
12861                 if (appData.autoCallFlag &&
12862                     gameMode == IcsPlayingBlack && !blackFlag) {
12863                   SendToICS(ics_prefix);
12864                   SendToICS("flag\n");
12865                 }
12866             } else {
12867                 if (blackFlag) {
12868                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12869                 } else {
12870                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12871                     if (appData.autoCallFlag) {
12872                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12873                         return TRUE;
12874                     }
12875                 }
12876             }
12877         }
12878     }
12879     if (blackTimeRemaining <= 0) {
12880         if (!blackFlag) {
12881             blackFlag = TRUE;
12882             if (appData.icsActive) {
12883                 if (appData.autoCallFlag &&
12884                     gameMode == IcsPlayingWhite && !whiteFlag) {
12885                   SendToICS(ics_prefix);
12886                   SendToICS("flag\n");
12887                 }
12888             } else {
12889                 if (whiteFlag) {
12890                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12891                 } else {
12892                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12893                     if (appData.autoCallFlag) {
12894                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12895                         return TRUE;
12896                     }
12897                 }
12898             }
12899         }
12900     }
12901     return FALSE;
12902 }
12903
12904 void
12905 CheckTimeControl()
12906 {
12907     if (!appData.clockMode || appData.icsActive ||
12908         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12909
12910     /*
12911      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12912      */
12913     if ( !WhiteOnMove(forwardMostMove) )
12914         /* White made time control */
12915         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12916         /* [HGM] time odds: correct new time quota for time odds! */
12917                                             / WhitePlayer()->timeOdds;
12918       else
12919         /* Black made time control */
12920         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12921                                             / WhitePlayer()->other->timeOdds;
12922 }
12923
12924 void
12925 DisplayBothClocks()
12926 {
12927     int wom = gameMode == EditPosition ?
12928       !blackPlaysFirst : WhiteOnMove(currentMove);
12929     DisplayWhiteClock(whiteTimeRemaining, wom);
12930     DisplayBlackClock(blackTimeRemaining, !wom);
12931 }
12932
12933
12934 /* Timekeeping seems to be a portability nightmare.  I think everyone
12935    has ftime(), but I'm really not sure, so I'm including some ifdefs
12936    to use other calls if you don't.  Clocks will be less accurate if
12937    you have neither ftime nor gettimeofday.
12938 */
12939
12940 /* VS 2008 requires the #include outside of the function */
12941 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12942 #include <sys/timeb.h>
12943 #endif
12944
12945 /* Get the current time as a TimeMark */
12946 void
12947 GetTimeMark(tm)
12948      TimeMark *tm;
12949 {
12950 #if HAVE_GETTIMEOFDAY
12951
12952     struct timeval timeVal;
12953     struct timezone timeZone;
12954
12955     gettimeofday(&timeVal, &timeZone);
12956     tm->sec = (long) timeVal.tv_sec; 
12957     tm->ms = (int) (timeVal.tv_usec / 1000L);
12958
12959 #else /*!HAVE_GETTIMEOFDAY*/
12960 #if HAVE_FTIME
12961
12962 // include <sys/timeb.h> / moved to just above start of function
12963     struct timeb timeB;
12964
12965     ftime(&timeB);
12966     tm->sec = (long) timeB.time;
12967     tm->ms = (int) timeB.millitm;
12968
12969 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12970     tm->sec = (long) time(NULL);
12971     tm->ms = 0;
12972 #endif
12973 #endif
12974 }
12975
12976 /* Return the difference in milliseconds between two
12977    time marks.  We assume the difference will fit in a long!
12978 */
12979 long
12980 SubtractTimeMarks(tm2, tm1)
12981      TimeMark *tm2, *tm1;
12982 {
12983     return 1000L*(tm2->sec - tm1->sec) +
12984            (long) (tm2->ms - tm1->ms);
12985 }
12986
12987
12988 /*
12989  * Code to manage the game clocks.
12990  *
12991  * In tournament play, black starts the clock and then white makes a move.
12992  * We give the human user a slight advantage if he is playing white---the
12993  * clocks don't run until he makes his first move, so it takes zero time.
12994  * Also, we don't account for network lag, so we could get out of sync
12995  * with GNU Chess's clock -- but then, referees are always right.  
12996  */
12997
12998 static TimeMark tickStartTM;
12999 static long intendedTickLength;
13000
13001 long
13002 NextTickLength(timeRemaining)
13003      long timeRemaining;
13004 {
13005     long nominalTickLength, nextTickLength;
13006
13007     if (timeRemaining > 0L && timeRemaining <= 10000L)
13008       nominalTickLength = 100L;
13009     else
13010       nominalTickLength = 1000L;
13011     nextTickLength = timeRemaining % nominalTickLength;
13012     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13013
13014     return nextTickLength;
13015 }
13016
13017 /* Adjust clock one minute up or down */
13018 void
13019 AdjustClock(Boolean which, int dir)
13020 {
13021     if(which) blackTimeRemaining += 60000*dir;
13022     else      whiteTimeRemaining += 60000*dir;
13023     DisplayBothClocks();
13024 }
13025
13026 /* Stop clocks and reset to a fresh time control */
13027 void
13028 ResetClocks() 
13029 {
13030     (void) StopClockTimer();
13031     if (appData.icsActive) {
13032         whiteTimeRemaining = blackTimeRemaining = 0;
13033     } else { /* [HGM] correct new time quote for time odds */
13034         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13035         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13036     }
13037     if (whiteFlag || blackFlag) {
13038         DisplayTitle("");
13039         whiteFlag = blackFlag = FALSE;
13040     }
13041     DisplayBothClocks();
13042 }
13043
13044 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13045
13046 /* Decrement running clock by amount of time that has passed */
13047 void
13048 DecrementClocks()
13049 {
13050     long timeRemaining;
13051     long lastTickLength, fudge;
13052     TimeMark now;
13053
13054     if (!appData.clockMode) return;
13055     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13056         
13057     GetTimeMark(&now);
13058
13059     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13060
13061     /* Fudge if we woke up a little too soon */
13062     fudge = intendedTickLength - lastTickLength;
13063     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13064
13065     if (WhiteOnMove(forwardMostMove)) {
13066         if(whiteNPS >= 0) lastTickLength = 0;
13067         timeRemaining = whiteTimeRemaining -= lastTickLength;
13068         DisplayWhiteClock(whiteTimeRemaining - fudge,
13069                           WhiteOnMove(currentMove));
13070     } else {
13071         if(blackNPS >= 0) lastTickLength = 0;
13072         timeRemaining = blackTimeRemaining -= lastTickLength;
13073         DisplayBlackClock(blackTimeRemaining - fudge,
13074                           !WhiteOnMove(currentMove));
13075     }
13076
13077     if (CheckFlags()) return;
13078         
13079     tickStartTM = now;
13080     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13081     StartClockTimer(intendedTickLength);
13082
13083     /* if the time remaining has fallen below the alarm threshold, sound the
13084      * alarm. if the alarm has sounded and (due to a takeback or time control
13085      * with increment) the time remaining has increased to a level above the
13086      * threshold, reset the alarm so it can sound again. 
13087      */
13088     
13089     if (appData.icsActive && appData.icsAlarm) {
13090
13091         /* make sure we are dealing with the user's clock */
13092         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13093                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13094            )) return;
13095
13096         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13097             alarmSounded = FALSE;
13098         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13099             PlayAlarmSound();
13100             alarmSounded = TRUE;
13101         }
13102     }
13103 }
13104
13105
13106 /* A player has just moved, so stop the previously running
13107    clock and (if in clock mode) start the other one.
13108    We redisplay both clocks in case we're in ICS mode, because
13109    ICS gives us an update to both clocks after every move.
13110    Note that this routine is called *after* forwardMostMove
13111    is updated, so the last fractional tick must be subtracted
13112    from the color that is *not* on move now.
13113 */
13114 void
13115 SwitchClocks()
13116 {
13117     long lastTickLength;
13118     TimeMark now;
13119     int flagged = FALSE;
13120
13121     GetTimeMark(&now);
13122
13123     if (StopClockTimer() && appData.clockMode) {
13124         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13125         if (WhiteOnMove(forwardMostMove)) {
13126             if(blackNPS >= 0) lastTickLength = 0;
13127             blackTimeRemaining -= lastTickLength;
13128            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13129 //         if(pvInfoList[forwardMostMove-1].time == -1)
13130                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13131                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13132         } else {
13133            if(whiteNPS >= 0) lastTickLength = 0;
13134            whiteTimeRemaining -= lastTickLength;
13135            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13136 //         if(pvInfoList[forwardMostMove-1].time == -1)
13137                  pvInfoList[forwardMostMove-1].time = 
13138                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13139         }
13140         flagged = CheckFlags();
13141     }
13142     CheckTimeControl();
13143
13144     if (flagged || !appData.clockMode) return;
13145
13146     switch (gameMode) {
13147       case MachinePlaysBlack:
13148       case MachinePlaysWhite:
13149       case BeginningOfGame:
13150         if (pausing) return;
13151         break;
13152
13153       case EditGame:
13154       case PlayFromGameFile:
13155       case IcsExamining:
13156         return;
13157
13158       default:
13159         break;
13160     }
13161
13162     tickStartTM = now;
13163     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13164       whiteTimeRemaining : blackTimeRemaining);
13165     StartClockTimer(intendedTickLength);
13166 }
13167         
13168
13169 /* Stop both clocks */
13170 void
13171 StopClocks()
13172 {       
13173     long lastTickLength;
13174     TimeMark now;
13175
13176     if (!StopClockTimer()) return;
13177     if (!appData.clockMode) return;
13178
13179     GetTimeMark(&now);
13180
13181     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13182     if (WhiteOnMove(forwardMostMove)) {
13183         if(whiteNPS >= 0) lastTickLength = 0;
13184         whiteTimeRemaining -= lastTickLength;
13185         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13186     } else {
13187         if(blackNPS >= 0) lastTickLength = 0;
13188         blackTimeRemaining -= lastTickLength;
13189         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13190     }
13191     CheckFlags();
13192 }
13193         
13194 /* Start clock of player on move.  Time may have been reset, so
13195    if clock is already running, stop and restart it. */
13196 void
13197 StartClocks()
13198 {
13199     (void) StopClockTimer(); /* in case it was running already */
13200     DisplayBothClocks();
13201     if (CheckFlags()) return;
13202
13203     if (!appData.clockMode) return;
13204     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13205
13206     GetTimeMark(&tickStartTM);
13207     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13208       whiteTimeRemaining : blackTimeRemaining);
13209
13210    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13211     whiteNPS = blackNPS = -1; 
13212     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13213        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13214         whiteNPS = first.nps;
13215     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13216        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13217         blackNPS = first.nps;
13218     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13219         whiteNPS = second.nps;
13220     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13221         blackNPS = second.nps;
13222     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13223
13224     StartClockTimer(intendedTickLength);
13225 }
13226
13227 char *
13228 TimeString(ms)
13229      long ms;
13230 {
13231     long second, minute, hour, day;
13232     char *sign = "";
13233     static char buf[32];
13234     
13235     if (ms > 0 && ms <= 9900) {
13236       /* convert milliseconds to tenths, rounding up */
13237       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13238
13239       sprintf(buf, " %03.1f ", tenths/10.0);
13240       return buf;
13241     }
13242
13243     /* convert milliseconds to seconds, rounding up */
13244     /* use floating point to avoid strangeness of integer division
13245        with negative dividends on many machines */
13246     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13247
13248     if (second < 0) {
13249         sign = "-";
13250         second = -second;
13251     }
13252     
13253     day = second / (60 * 60 * 24);
13254     second = second % (60 * 60 * 24);
13255     hour = second / (60 * 60);
13256     second = second % (60 * 60);
13257     minute = second / 60;
13258     second = second % 60;
13259     
13260     if (day > 0)
13261       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13262               sign, day, hour, minute, second);
13263     else if (hour > 0)
13264       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13265     else
13266       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13267     
13268     return buf;
13269 }
13270
13271
13272 /*
13273  * This is necessary because some C libraries aren't ANSI C compliant yet.
13274  */
13275 char *
13276 StrStr(string, match)
13277      char *string, *match;
13278 {
13279     int i, length;
13280     
13281     length = strlen(match);
13282     
13283     for (i = strlen(string) - length; i >= 0; i--, string++)
13284       if (!strncmp(match, string, length))
13285         return string;
13286     
13287     return NULL;
13288 }
13289
13290 char *
13291 StrCaseStr(string, match)
13292      char *string, *match;
13293 {
13294     int i, j, length;
13295     
13296     length = strlen(match);
13297     
13298     for (i = strlen(string) - length; i >= 0; i--, string++) {
13299         for (j = 0; j < length; j++) {
13300             if (ToLower(match[j]) != ToLower(string[j]))
13301               break;
13302         }
13303         if (j == length) return string;
13304     }
13305
13306     return NULL;
13307 }
13308
13309 #ifndef _amigados
13310 int
13311 StrCaseCmp(s1, s2)
13312      char *s1, *s2;
13313 {
13314     char c1, c2;
13315     
13316     for (;;) {
13317         c1 = ToLower(*s1++);
13318         c2 = ToLower(*s2++);
13319         if (c1 > c2) return 1;
13320         if (c1 < c2) return -1;
13321         if (c1 == NULLCHAR) return 0;
13322     }
13323 }
13324
13325
13326 int
13327 ToLower(c)
13328      int c;
13329 {
13330     return isupper(c) ? tolower(c) : c;
13331 }
13332
13333
13334 int
13335 ToUpper(c)
13336      int c;
13337 {
13338     return islower(c) ? toupper(c) : c;
13339 }
13340 #endif /* !_amigados    */
13341
13342 char *
13343 StrSave(s)
13344      char *s;
13345 {
13346     char *ret;
13347
13348     if ((ret = (char *) malloc(strlen(s) + 1))) {
13349         strcpy(ret, s);
13350     }
13351     return ret;
13352 }
13353
13354 char *
13355 StrSavePtr(s, savePtr)
13356      char *s, **savePtr;
13357 {
13358     if (*savePtr) {
13359         free(*savePtr);
13360     }
13361     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13362         strcpy(*savePtr, s);
13363     }
13364     return(*savePtr);
13365 }
13366
13367 char *
13368 PGNDate()
13369 {
13370     time_t clock;
13371     struct tm *tm;
13372     char buf[MSG_SIZ];
13373
13374     clock = time((time_t *)NULL);
13375     tm = localtime(&clock);
13376     sprintf(buf, "%04d.%02d.%02d",
13377             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13378     return StrSave(buf);
13379 }
13380
13381
13382 char *
13383 PositionToFEN(move, overrideCastling)
13384      int move;
13385      char *overrideCastling;
13386 {
13387     int i, j, fromX, fromY, toX, toY;
13388     int whiteToPlay;
13389     char buf[128];
13390     char *p, *q;
13391     int emptycount;
13392     ChessSquare piece;
13393
13394     whiteToPlay = (gameMode == EditPosition) ?
13395       !blackPlaysFirst : (move % 2 == 0);
13396     p = buf;
13397
13398     /* Piece placement data */
13399     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13400         emptycount = 0;
13401         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13402             if (boards[move][i][j] == EmptySquare) {
13403                 emptycount++;
13404             } else { ChessSquare piece = boards[move][i][j];
13405                 if (emptycount > 0) {
13406                     if(emptycount<10) /* [HGM] can be >= 10 */
13407                         *p++ = '0' + emptycount;
13408                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13409                     emptycount = 0;
13410                 }
13411                 if(PieceToChar(piece) == '+') {
13412                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13413                     *p++ = '+';
13414                     piece = (ChessSquare)(DEMOTED piece);
13415                 } 
13416                 *p++ = PieceToChar(piece);
13417                 if(p[-1] == '~') {
13418                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13419                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13420                     *p++ = '~';
13421                 }
13422             }
13423         }
13424         if (emptycount > 0) {
13425             if(emptycount<10) /* [HGM] can be >= 10 */
13426                 *p++ = '0' + emptycount;
13427             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13428             emptycount = 0;
13429         }
13430         *p++ = '/';
13431     }
13432     *(p - 1) = ' ';
13433
13434     /* [HGM] print Crazyhouse or Shogi holdings */
13435     if( gameInfo.holdingsWidth ) {
13436         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13437         q = p;
13438         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13439             piece = boards[move][i][BOARD_WIDTH-1];
13440             if( piece != EmptySquare )
13441               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13442                   *p++ = PieceToChar(piece);
13443         }
13444         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13445             piece = boards[move][BOARD_HEIGHT-i-1][0];
13446             if( piece != EmptySquare )
13447               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13448                   *p++ = PieceToChar(piece);
13449         }
13450
13451         if( q == p ) *p++ = '-';
13452         *p++ = ']';
13453         *p++ = ' ';
13454     }
13455
13456     /* Active color */
13457     *p++ = whiteToPlay ? 'w' : 'b';
13458     *p++ = ' ';
13459
13460   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13461     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13462   } else {
13463   if(nrCastlingRights) {
13464      q = p;
13465      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13466        /* [HGM] write directly from rights */
13467            if(castlingRights[move][2] >= 0 &&
13468               castlingRights[move][0] >= 0   )
13469                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13470            if(castlingRights[move][2] >= 0 &&
13471               castlingRights[move][1] >= 0   )
13472                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13473            if(castlingRights[move][5] >= 0 &&
13474               castlingRights[move][3] >= 0   )
13475                 *p++ = castlingRights[move][3] + AAA;
13476            if(castlingRights[move][5] >= 0 &&
13477               castlingRights[move][4] >= 0   )
13478                 *p++ = castlingRights[move][4] + AAA;
13479      } else {
13480
13481         /* [HGM] write true castling rights */
13482         if( nrCastlingRights == 6 ) {
13483             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13484                castlingRights[move][2] >= 0  ) *p++ = 'K';
13485             if(castlingRights[move][1] == BOARD_LEFT &&
13486                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13487             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13488                castlingRights[move][5] >= 0  ) *p++ = 'k';
13489             if(castlingRights[move][4] == BOARD_LEFT &&
13490                castlingRights[move][5] >= 0  ) *p++ = 'q';
13491         }
13492      }
13493      if (q == p) *p++ = '-'; /* No castling rights */
13494      *p++ = ' ';
13495   }
13496
13497   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13498      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13499     /* En passant target square */
13500     if (move > backwardMostMove) {
13501         fromX = moveList[move - 1][0] - AAA;
13502         fromY = moveList[move - 1][1] - ONE;
13503         toX = moveList[move - 1][2] - AAA;
13504         toY = moveList[move - 1][3] - ONE;
13505         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13506             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13507             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13508             fromX == toX) {
13509             /* 2-square pawn move just happened */
13510             *p++ = toX + AAA;
13511             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13512         } else {
13513             *p++ = '-';
13514         }
13515     } else {
13516         *p++ = '-';
13517     }
13518     *p++ = ' ';
13519   }
13520   }
13521
13522     /* [HGM] find reversible plies */
13523     {   int i = 0, j=move;
13524
13525         if (appData.debugMode) { int k;
13526             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13527             for(k=backwardMostMove; k<=forwardMostMove; k++)
13528                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13529
13530         }
13531
13532         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13533         if( j == backwardMostMove ) i += initialRulePlies;
13534         sprintf(p, "%d ", i);
13535         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13536     }
13537     /* Fullmove number */
13538     sprintf(p, "%d", (move / 2) + 1);
13539     
13540     return StrSave(buf);
13541 }
13542
13543 Boolean
13544 ParseFEN(board, blackPlaysFirst, fen)
13545     Board board;
13546      int *blackPlaysFirst;
13547      char *fen;
13548 {
13549     int i, j;
13550     char *p;
13551     int emptycount;
13552     ChessSquare piece;
13553
13554     p = fen;
13555
13556     /* [HGM] by default clear Crazyhouse holdings, if present */
13557     if(gameInfo.holdingsWidth) {
13558        for(i=0; i<BOARD_HEIGHT; i++) {
13559            board[i][0]             = EmptySquare; /* black holdings */
13560            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13561            board[i][1]             = (ChessSquare) 0; /* black counts */
13562            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13563        }
13564     }
13565
13566     /* Piece placement data */
13567     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13568         j = 0;
13569         for (;;) {
13570             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13571                 if (*p == '/') p++;
13572                 emptycount = gameInfo.boardWidth - j;
13573                 while (emptycount--)
13574                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13575                 break;
13576 #if(BOARD_SIZE >= 10)
13577             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13578                 p++; emptycount=10;
13579                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13580                 while (emptycount--)
13581                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13582 #endif
13583             } else if (isdigit(*p)) {
13584                 emptycount = *p++ - '0';
13585                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13586                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13587                 while (emptycount--)
13588                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13589             } else if (*p == '+' || isalpha(*p)) {
13590                 if (j >= gameInfo.boardWidth) return FALSE;
13591                 if(*p=='+') {
13592                     piece = CharToPiece(*++p);
13593                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13594                     piece = (ChessSquare) (PROMOTED piece ); p++;
13595                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13596                 } else piece = CharToPiece(*p++);
13597
13598                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13599                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13600                     piece = (ChessSquare) (PROMOTED piece);
13601                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13602                     p++;
13603                 }
13604                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13605             } else {
13606                 return FALSE;
13607             }
13608         }
13609     }
13610     while (*p == '/' || *p == ' ') p++;
13611
13612     /* [HGM] look for Crazyhouse holdings here */
13613     while(*p==' ') p++;
13614     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13615         if(*p == '[') p++;
13616         if(*p == '-' ) *p++; /* empty holdings */ else {
13617             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13618             /* if we would allow FEN reading to set board size, we would   */
13619             /* have to add holdings and shift the board read so far here   */
13620             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13621                 *p++;
13622                 if((int) piece >= (int) BlackPawn ) {
13623                     i = (int)piece - (int)BlackPawn;
13624                     i = PieceToNumber((ChessSquare)i);
13625                     if( i >= gameInfo.holdingsSize ) return FALSE;
13626                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13627                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13628                 } else {
13629                     i = (int)piece - (int)WhitePawn;
13630                     i = PieceToNumber((ChessSquare)i);
13631                     if( i >= gameInfo.holdingsSize ) return FALSE;
13632                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13633                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13634                 }
13635             }
13636         }
13637         if(*p == ']') *p++;
13638     }
13639
13640     while(*p == ' ') p++;
13641
13642     /* Active color */
13643     switch (*p++) {
13644       case 'w':
13645         *blackPlaysFirst = FALSE;
13646         break;
13647       case 'b': 
13648         *blackPlaysFirst = TRUE;
13649         break;
13650       default:
13651         return FALSE;
13652     }
13653
13654     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13655     /* return the extra info in global variiables             */
13656
13657     /* set defaults in case FEN is incomplete */
13658     FENepStatus = EP_UNKNOWN;
13659     for(i=0; i<nrCastlingRights; i++ ) {
13660         FENcastlingRights[i] =
13661             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13662     }   /* assume possible unless obviously impossible */
13663     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13664     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13665     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13666     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13667     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13668     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13669     FENrulePlies = 0;
13670
13671     while(*p==' ') p++;
13672     if(nrCastlingRights) {
13673       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13674           /* castling indicator present, so default becomes no castlings */
13675           for(i=0; i<nrCastlingRights; i++ ) {
13676                  FENcastlingRights[i] = -1;
13677           }
13678       }
13679       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13680              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13681              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13682              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13683         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13684
13685         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13686             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13687             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13688         }
13689         switch(c) {
13690           case'K':
13691               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13692               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13693               FENcastlingRights[2] = whiteKingFile;
13694               break;
13695           case'Q':
13696               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13697               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13698               FENcastlingRights[2] = whiteKingFile;
13699               break;
13700           case'k':
13701               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13702               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13703               FENcastlingRights[5] = blackKingFile;
13704               break;
13705           case'q':
13706               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13707               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13708               FENcastlingRights[5] = blackKingFile;
13709           case '-':
13710               break;
13711           default: /* FRC castlings */
13712               if(c >= 'a') { /* black rights */
13713                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13714                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13715                   if(i == BOARD_RGHT) break;
13716                   FENcastlingRights[5] = i;
13717                   c -= AAA;
13718                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13719                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13720                   if(c > i)
13721                       FENcastlingRights[3] = c;
13722                   else
13723                       FENcastlingRights[4] = c;
13724               } else { /* white rights */
13725                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13726                     if(board[0][i] == WhiteKing) break;
13727                   if(i == BOARD_RGHT) break;
13728                   FENcastlingRights[2] = i;
13729                   c -= AAA - 'a' + 'A';
13730                   if(board[0][c] >= WhiteKing) break;
13731                   if(c > i)
13732                       FENcastlingRights[0] = c;
13733                   else
13734                       FENcastlingRights[1] = c;
13735               }
13736         }
13737       }
13738     if (appData.debugMode) {
13739         fprintf(debugFP, "FEN castling rights:");
13740         for(i=0; i<nrCastlingRights; i++)
13741         fprintf(debugFP, " %d", FENcastlingRights[i]);
13742         fprintf(debugFP, "\n");
13743     }
13744
13745       while(*p==' ') p++;
13746     }
13747
13748     /* read e.p. field in games that know e.p. capture */
13749     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13750        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13751       if(*p=='-') {
13752         p++; FENepStatus = EP_NONE;
13753       } else {
13754          char c = *p++ - AAA;
13755
13756          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13757          if(*p >= '0' && *p <='9') *p++;
13758          FENepStatus = c;
13759       }
13760     }
13761
13762
13763     if(sscanf(p, "%d", &i) == 1) {
13764         FENrulePlies = i; /* 50-move ply counter */
13765         /* (The move number is still ignored)    */
13766     }
13767
13768     return TRUE;
13769 }
13770       
13771 void
13772 EditPositionPasteFEN(char *fen)
13773 {
13774   if (fen != NULL) {
13775     Board initial_position;
13776
13777     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13778       DisplayError(_("Bad FEN position in clipboard"), 0);
13779       return ;
13780     } else {
13781       int savedBlackPlaysFirst = blackPlaysFirst;
13782       EditPositionEvent();
13783       blackPlaysFirst = savedBlackPlaysFirst;
13784       CopyBoard(boards[0], initial_position);
13785           /* [HGM] copy FEN attributes as well */
13786           {   int i;
13787               initialRulePlies = FENrulePlies;
13788               epStatus[0] = FENepStatus;
13789               for( i=0; i<nrCastlingRights; i++ )
13790                   castlingRights[0][i] = FENcastlingRights[i];
13791           }
13792       EditPositionDone();
13793       DisplayBothClocks();
13794       DrawPosition(FALSE, boards[currentMove]);
13795     }
13796   }
13797 }