fixed warning about radio buttons, grouped radio buttons together
[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   printf ("DEBUG: in init backend 2\n");
1080
1081     if (appData.debugMode) {
1082         fprintf(debugFP, "%s\n", programVersion);
1083     }
1084
1085     if (appData.matchGames > 0) {
1086         appData.matchMode = TRUE;
1087     } else if (appData.matchMode) {
1088         appData.matchGames = 1;
1089     }
1090     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1091         appData.matchGames = appData.sameColorGames;
1092     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1093         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1094         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1095     }
1096     printf ("DEBUG: before reset\n");
1097     Reset(TRUE, FALSE);
1098     printf ("DEBUG: after reset\n");
1099     if (appData.noChessProgram || first.protocolVersion == 1) {
1100       printf ("DEBUG: first if\n");
1101       InitBackEnd3();
1102     } else {
1103       printf ("DEBUG: second if 0\n");
1104       /* kludge: allow timeout for initial "feature" commands */
1105       FreezeUI();
1106       printf ("DEBUG: second if 1\n");
1107       DisplayMessage("", _("Starting chess program"));
1108       printf ("DEBUG: second if 2\n");
1109       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110       printf ("DEBUG: second if 3\n");    }
1111     printf ("DEBUG: end of init2\n");
1112 }
1113
1114 void
1115 InitBackEnd3 P((void))
1116 {
1117     GameMode initialMode;
1118     char buf[MSG_SIZ];
1119     int err;
1120
1121     InitChessProgram(&first, startedFromSetupPosition);
1122
1123
1124     if (appData.icsActive) {
1125 #ifdef WIN32
1126         /* [DM] Make a console window if needed [HGM] merged ifs */
1127         ConsoleCreate(); 
1128 #endif
1129         err = establish();
1130         if (err != 0) {
1131             if (*appData.icsCommPort != NULLCHAR) {
1132                 sprintf(buf, _("Could not open comm port %s"),  
1133                         appData.icsCommPort);
1134             } else {
1135                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1136                         appData.icsHost, appData.icsPort);
1137             }
1138             DisplayFatalError(buf, err, 1);
1139             return;
1140         }
1141         SetICSMode();
1142         telnetISR =
1143           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1144         fromUserISR =
1145           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1146     } else if (appData.noChessProgram) {
1147         SetNCPMode();
1148     } else {
1149         SetGNUMode();
1150     }
1151
1152     if (*appData.cmailGameName != NULLCHAR) {
1153         SetCmailMode();
1154         OpenLoopback(&cmailPR);
1155         cmailISR =
1156           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1157     }
1158     
1159     ThawUI();
1160     DisplayMessage("", "");
1161     if (StrCaseCmp(appData.initialMode, "") == 0) {
1162       initialMode = BeginningOfGame;
1163     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1164       initialMode = TwoMachinesPlay;
1165     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1166       initialMode = AnalyzeFile; 
1167     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1168       initialMode = AnalyzeMode;
1169     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1170       initialMode = MachinePlaysWhite;
1171     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1172       initialMode = MachinePlaysBlack;
1173     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1174       initialMode = EditGame;
1175     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1176       initialMode = EditPosition;
1177     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1178       initialMode = Training;
1179     } else {
1180       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1181       DisplayFatalError(buf, 0, 2);
1182       return;
1183     }
1184
1185     if (appData.matchMode) {
1186         /* Set up machine vs. machine match */
1187         if (appData.noChessProgram) {
1188             DisplayFatalError(_("Can't have a match with no chess programs"),
1189                               0, 2);
1190             return;
1191         }
1192         matchMode = TRUE;
1193         matchGame = 1;
1194         if (*appData.loadGameFile != NULLCHAR) {
1195             int index = appData.loadGameIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadGameFromFile(appData.loadGameFile,
1198                                   index,
1199                                   appData.loadGameFile, FALSE)) {
1200                 DisplayFatalError(_("Bad game file"), 0, 1);
1201                 return;
1202             }
1203         } else if (*appData.loadPositionFile != NULLCHAR) {
1204             int index = appData.loadPositionIndex; // [HGM] autoinc
1205             if(index<0) lastIndex = index = 1;
1206             if (!LoadPositionFromFile(appData.loadPositionFile,
1207                                       index,
1208                                       appData.loadPositionFile)) {
1209                 DisplayFatalError(_("Bad position file"), 0, 1);
1210                 return;
1211             }
1212         }
1213         TwoMachinesEvent();
1214     } else if (*appData.cmailGameName != NULLCHAR) {
1215         /* Set up cmail mode */
1216         ReloadCmailMsgEvent(TRUE);
1217     } else {
1218         /* Set up other modes */
1219         if (initialMode == AnalyzeFile) {
1220           if (*appData.loadGameFile == NULLCHAR) {
1221             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1222             return;
1223           }
1224         }
1225         if (*appData.loadGameFile != NULLCHAR) {
1226             (void) LoadGameFromFile(appData.loadGameFile,
1227                                     appData.loadGameIndex,
1228                                     appData.loadGameFile, TRUE);
1229         } else if (*appData.loadPositionFile != NULLCHAR) {
1230             (void) LoadPositionFromFile(appData.loadPositionFile,
1231                                         appData.loadPositionIndex,
1232                                         appData.loadPositionFile);
1233             /* [HGM] try to make self-starting even after FEN load */
1234             /* to allow automatic setup of fairy variants with wtm */
1235             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1236                 gameMode = BeginningOfGame;
1237                 setboardSpoiledMachineBlack = 1;
1238             }
1239             /* [HGM] loadPos: make that every new game uses the setup */
1240             /* from file as long as we do not switch variant          */
1241             if(!blackPlaysFirst) { int i;
1242                 startedFromPositionFile = TRUE;
1243                 CopyBoard(filePosition, boards[0]);
1244                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1245             }
1246         }
1247         if (initialMode == AnalyzeMode) {
1248           if (appData.noChessProgram) {
1249             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1254             return;
1255           }
1256           AnalyzeModeEvent();
1257         } else if (initialMode == AnalyzeFile) {
1258           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1259           ShowThinkingEvent();
1260           AnalyzeFileEvent();
1261           AnalysisPeriodicEvent(1);
1262         } else if (initialMode == MachinePlaysWhite) {
1263           if (appData.noChessProgram) {
1264             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1265                               0, 2);
1266             return;
1267           }
1268           if (appData.icsActive) {
1269             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1270                               0, 2);
1271             return;
1272           }
1273           MachineWhiteEvent();
1274         } else if (initialMode == MachinePlaysBlack) {
1275           if (appData.noChessProgram) {
1276             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1277                               0, 2);
1278             return;
1279           }
1280           if (appData.icsActive) {
1281             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1282                               0, 2);
1283             return;
1284           }
1285           MachineBlackEvent();
1286         } else if (initialMode == TwoMachinesPlay) {
1287           if (appData.noChessProgram) {
1288             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1289                               0, 2);
1290             return;
1291           }
1292           if (appData.icsActive) {
1293             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1294                               0, 2);
1295             return;
1296           }
1297           TwoMachinesEvent();
1298         } else if (initialMode == EditGame) {
1299           EditGameEvent();
1300         } else if (initialMode == EditPosition) {
1301           EditPositionEvent();
1302         } else if (initialMode == Training) {
1303           if (*appData.loadGameFile == NULLCHAR) {
1304             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1305             return;
1306           }
1307           TrainingEvent();
1308         }
1309     }
1310 }
1311
1312 /*
1313  * Establish will establish a contact to a remote host.port.
1314  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1315  *  used to talk to the host.
1316  * Returns 0 if okay, error code if not.
1317  */
1318 int
1319 establish()
1320 {
1321     char buf[MSG_SIZ];
1322
1323     if (*appData.icsCommPort != NULLCHAR) {
1324         /* Talk to the host through a serial comm port */
1325         return OpenCommPort(appData.icsCommPort, &icsPR);
1326
1327     } else if (*appData.gateway != NULLCHAR) {
1328         if (*appData.remoteShell == NULLCHAR) {
1329             /* Use the rcmd protocol to run telnet program on a gateway host */
1330             snprintf(buf, sizeof(buf), "%s %s %s",
1331                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1332             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1333
1334         } else {
1335             /* Use the rsh program to run telnet program on a gateway host */
1336             if (*appData.remoteUser == NULLCHAR) {
1337                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1338                         appData.gateway, appData.telnetProgram,
1339                         appData.icsHost, appData.icsPort);
1340             } else {
1341                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1342                         appData.remoteShell, appData.gateway, 
1343                         appData.remoteUser, appData.telnetProgram,
1344                         appData.icsHost, appData.icsPort);
1345             }
1346             return StartChildProcess(buf, "", &icsPR);
1347
1348         }
1349     } else if (appData.useTelnet) {
1350         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1351
1352     } else {
1353         /* TCP socket interface differs somewhat between
1354            Unix and NT; handle details in the front end.
1355            */
1356         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1357     }
1358 }
1359
1360 void
1361 show_bytes(fp, buf, count)
1362      FILE *fp;
1363      char *buf;
1364      int count;
1365 {
1366     while (count--) {
1367         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1368             fprintf(fp, "\\%03o", *buf & 0xff);
1369         } else {
1370             putc(*buf, fp);
1371         }
1372         buf++;
1373     }
1374     fflush(fp);
1375 }
1376
1377 /* Returns an errno value */
1378 int
1379 OutputMaybeTelnet(pr, message, count, outError)
1380      ProcRef pr;
1381      char *message;
1382      int count;
1383      int *outError;
1384 {
1385     char buf[8192], *p, *q, *buflim;
1386     int left, newcount, outcount;
1387
1388     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1389         *appData.gateway != NULLCHAR) {
1390         if (appData.debugMode) {
1391             fprintf(debugFP, ">ICS: ");
1392             show_bytes(debugFP, message, count);
1393             fprintf(debugFP, "\n");
1394         }
1395         return OutputToProcess(pr, message, count, outError);
1396     }
1397
1398     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1399     p = message;
1400     q = buf;
1401     left = count;
1402     newcount = 0;
1403     while (left) {
1404         if (q >= buflim) {
1405             if (appData.debugMode) {
1406                 fprintf(debugFP, ">ICS: ");
1407                 show_bytes(debugFP, buf, newcount);
1408                 fprintf(debugFP, "\n");
1409             }
1410             outcount = OutputToProcess(pr, buf, newcount, outError);
1411             if (outcount < newcount) return -1; /* to be sure */
1412             q = buf;
1413             newcount = 0;
1414         }
1415         if (*p == '\n') {
1416             *q++ = '\r';
1417             newcount++;
1418         } else if (((unsigned char) *p) == TN_IAC) {
1419             *q++ = (char) TN_IAC;
1420             newcount ++;
1421         }
1422         *q++ = *p++;
1423         newcount++;
1424         left--;
1425     }
1426     if (appData.debugMode) {
1427         fprintf(debugFP, ">ICS: ");
1428         show_bytes(debugFP, buf, newcount);
1429         fprintf(debugFP, "\n");
1430     }
1431     outcount = OutputToProcess(pr, buf, newcount, outError);
1432     if (outcount < newcount) return -1; /* to be sure */
1433     return count;
1434 }
1435
1436 void
1437 read_from_player(isr, closure, message, count, error)
1438      InputSourceRef isr;
1439      VOIDSTAR closure;
1440      char *message;
1441      int count;
1442      int error;
1443 {
1444     int outError, outCount;
1445     static int gotEof = 0;
1446
1447     /* Pass data read from player on to ICS */
1448     if (count > 0) {
1449         gotEof = 0;
1450         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1451         if (outCount < count) {
1452             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1453         }
1454     } else if (count < 0) {
1455         RemoveInputSource(isr);
1456         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1457     } else if (gotEof++ > 0) {
1458         RemoveInputSource(isr);
1459         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1460     }
1461 }
1462
1463 void
1464 SendToICS(s)
1465      char *s;
1466 {
1467     int count, outCount, outError;
1468
1469     if (icsPR == NULL) return;
1470
1471     count = strlen(s);
1472     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1473     if (outCount < count) {
1474         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1475     }
1476 }
1477
1478 /* This is used for sending logon scripts to the ICS. Sending
1479    without a delay causes problems when using timestamp on ICC
1480    (at least on my machine). */
1481 void
1482 SendToICSDelayed(s,msdelay)
1483      char *s;
1484      long msdelay;
1485 {
1486     int count, outCount, outError;
1487
1488     if (icsPR == NULL) return;
1489
1490     count = strlen(s);
1491     if (appData.debugMode) {
1492         fprintf(debugFP, ">ICS: ");
1493         show_bytes(debugFP, s, count);
1494         fprintf(debugFP, "\n");
1495     }
1496     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1497                                       msdelay);
1498     if (outCount < count) {
1499         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1500     }
1501 }
1502
1503
1504 /* Remove all highlighting escape sequences in s
1505    Also deletes any suffix starting with '(' 
1506    */
1507 char *
1508 StripHighlightAndTitle(s)
1509      char *s;
1510 {
1511     static char retbuf[MSG_SIZ];
1512     char *p = retbuf;
1513
1514     while (*s != NULLCHAR) {
1515         while (*s == '\033') {
1516             while (*s != NULLCHAR && !isalpha(*s)) s++;
1517             if (*s != NULLCHAR) s++;
1518         }
1519         while (*s != NULLCHAR && *s != '\033') {
1520             if (*s == '(' || *s == '[') {
1521                 *p = NULLCHAR;
1522                 return retbuf;
1523             }
1524             *p++ = *s++;
1525         }
1526     }
1527     *p = NULLCHAR;
1528     return retbuf;
1529 }
1530
1531 /* Remove all highlighting escape sequences in s */
1532 char *
1533 StripHighlight(s)
1534      char *s;
1535 {
1536     static char retbuf[MSG_SIZ];
1537     char *p = retbuf;
1538
1539     while (*s != NULLCHAR) {
1540         while (*s == '\033') {
1541             while (*s != NULLCHAR && !isalpha(*s)) s++;
1542             if (*s != NULLCHAR) s++;
1543         }
1544         while (*s != NULLCHAR && *s != '\033') {
1545             *p++ = *s++;
1546         }
1547     }
1548     *p = NULLCHAR;
1549     return retbuf;
1550 }
1551
1552 char *variantNames[] = VARIANT_NAMES;
1553 char *
1554 VariantName(v)
1555      VariantClass v;
1556 {
1557     return variantNames[v];
1558 }
1559
1560
1561 /* Identify a variant from the strings the chess servers use or the
1562    PGN Variant tag names we use. */
1563 VariantClass
1564 StringToVariant(e)
1565      char *e;
1566 {
1567     char *p;
1568     int wnum = -1;
1569     VariantClass v = VariantNormal;
1570     int i, found = FALSE;
1571     char buf[MSG_SIZ];
1572
1573     if (!e) return v;
1574
1575     /* [HGM] skip over optional board-size prefixes */
1576     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1577         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1578         while( *e++ != '_');
1579     }
1580
1581     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1582       if (StrCaseStr(e, variantNames[i])) {
1583         v = (VariantClass) i;
1584         found = TRUE;
1585         break;
1586       }
1587     }
1588
1589     if (!found) {
1590       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1591           || StrCaseStr(e, "wild/fr") 
1592           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1593         v = VariantFischeRandom;
1594       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1595                  (i = 1, p = StrCaseStr(e, "w"))) {
1596         p += i;
1597         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1598         if (isdigit(*p)) {
1599           wnum = atoi(p);
1600         } else {
1601           wnum = -1;
1602         }
1603         switch (wnum) {
1604         case 0: /* FICS only, actually */
1605         case 1:
1606           /* Castling legal even if K starts on d-file */
1607           v = VariantWildCastle;
1608           break;
1609         case 2:
1610         case 3:
1611         case 4:
1612           /* Castling illegal even if K & R happen to start in
1613              normal positions. */
1614           v = VariantNoCastle;
1615           break;
1616         case 5:
1617         case 7:
1618         case 8:
1619         case 10:
1620         case 11:
1621         case 12:
1622         case 13:
1623         case 14:
1624         case 15:
1625         case 18:
1626         case 19:
1627           /* Castling legal iff K & R start in normal positions */
1628           v = VariantNormal;
1629           break;
1630         case 6:
1631         case 20:
1632         case 21:
1633           /* Special wilds for position setup; unclear what to do here */
1634           v = VariantLoadable;
1635           break;
1636         case 9:
1637           /* Bizarre ICC game */
1638           v = VariantTwoKings;
1639           break;
1640         case 16:
1641           v = VariantKriegspiel;
1642           break;
1643         case 17:
1644           v = VariantLosers;
1645           break;
1646         case 22:
1647           v = VariantFischeRandom;
1648           break;
1649         case 23:
1650           v = VariantCrazyhouse;
1651           break;
1652         case 24:
1653           v = VariantBughouse;
1654           break;
1655         case 25:
1656           v = Variant3Check;
1657           break;
1658         case 26:
1659           /* Not quite the same as FICS suicide! */
1660           v = VariantGiveaway;
1661           break;
1662         case 27:
1663           v = VariantAtomic;
1664           break;
1665         case 28:
1666           v = VariantShatranj;
1667           break;
1668
1669         /* Temporary names for future ICC types.  The name *will* change in 
1670            the next xboard/WinBoard release after ICC defines it. */
1671         case 29:
1672           v = Variant29;
1673           break;
1674         case 30:
1675           v = Variant30;
1676           break;
1677         case 31:
1678           v = Variant31;
1679           break;
1680         case 32:
1681           v = Variant32;
1682           break;
1683         case 33:
1684           v = Variant33;
1685           break;
1686         case 34:
1687           v = Variant34;
1688           break;
1689         case 35:
1690           v = Variant35;
1691           break;
1692         case 36:
1693           v = Variant36;
1694           break;
1695         case 37:
1696           v = VariantShogi;
1697           break;
1698         case 38:
1699           v = VariantXiangqi;
1700           break;
1701         case 39:
1702           v = VariantCourier;
1703           break;
1704         case 40:
1705           v = VariantGothic;
1706           break;
1707         case 41:
1708           v = VariantCapablanca;
1709           break;
1710         case 42:
1711           v = VariantKnightmate;
1712           break;
1713         case 43:
1714           v = VariantFairy;
1715           break;
1716         case 44:
1717           v = VariantCylinder;
1718           break;
1719         case 45:
1720           v = VariantFalcon;
1721           break;
1722         case 46:
1723           v = VariantCapaRandom;
1724           break;
1725         case 47:
1726           v = VariantBerolina;
1727           break;
1728         case 48:
1729           v = VariantJanus;
1730           break;
1731         case 49:
1732           v = VariantSuper;
1733           break;
1734         case 50:
1735           v = VariantGreat;
1736           break;
1737         case -1:
1738           /* Found "wild" or "w" in the string but no number;
1739              must assume it's normal chess. */
1740           v = VariantNormal;
1741           break;
1742         default:
1743           sprintf(buf, _("Unknown wild type %d"), wnum);
1744           DisplayError(buf, 0);
1745           v = VariantUnknown;
1746           break;
1747         }
1748       }
1749     }
1750     if (appData.debugMode) {
1751       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1752               e, wnum, VariantName(v));
1753     }
1754     return v;
1755 }
1756
1757 static int leftover_start = 0, leftover_len = 0;
1758 char star_match[STAR_MATCH_N][MSG_SIZ];
1759
1760 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1761    advance *index beyond it, and set leftover_start to the new value of
1762    *index; else return FALSE.  If pattern contains the character '*', it
1763    matches any sequence of characters not containing '\r', '\n', or the
1764    character following the '*' (if any), and the matched sequence(s) are
1765    copied into star_match.
1766    */
1767 int
1768 looking_at(buf, index, pattern)
1769      char *buf;
1770      int *index;
1771      char *pattern;
1772 {
1773     char *bufp = &buf[*index], *patternp = pattern;
1774     int star_count = 0;
1775     char *matchp = star_match[0];
1776     
1777     for (;;) {
1778         if (*patternp == NULLCHAR) {
1779             *index = leftover_start = bufp - buf;
1780             *matchp = NULLCHAR;
1781             return TRUE;
1782         }
1783         if (*bufp == NULLCHAR) return FALSE;
1784         if (*patternp == '*') {
1785             if (*bufp == *(patternp + 1)) {
1786                 *matchp = NULLCHAR;
1787                 matchp = star_match[++star_count];
1788                 patternp += 2;
1789                 bufp++;
1790                 continue;
1791             } else if (*bufp == '\n' || *bufp == '\r') {
1792                 patternp++;
1793                 if (*patternp == NULLCHAR)
1794                   continue;
1795                 else
1796                   return FALSE;
1797             } else {
1798                 *matchp++ = *bufp++;
1799                 continue;
1800             }
1801         }
1802         if (*patternp != *bufp) return FALSE;
1803         patternp++;
1804         bufp++;
1805     }
1806 }
1807
1808 void
1809 SendToPlayer(data, length)
1810      char *data;
1811      int length;
1812 {
1813     int error, outCount;
1814     outCount = OutputToProcess(NoProc, data, length, &error);
1815     if (outCount < length) {
1816         DisplayFatalError(_("Error writing to display"), error, 1);
1817     }
1818 }
1819
1820 void
1821 PackHolding(packed, holding)
1822      char packed[];
1823      char *holding;
1824 {
1825     char *p = holding;
1826     char *q = packed;
1827     int runlength = 0;
1828     int curr = 9999;
1829     do {
1830         if (*p == curr) {
1831             runlength++;
1832         } else {
1833             switch (runlength) {
1834               case 0:
1835                 break;
1836               case 1:
1837                 *q++ = curr;
1838                 break;
1839               case 2:
1840                 *q++ = curr;
1841                 *q++ = curr;
1842                 break;
1843               default:
1844                 sprintf(q, "%d", runlength);
1845                 while (*q) q++;
1846                 *q++ = curr;
1847                 break;
1848             }
1849             runlength = 1;
1850             curr = *p;
1851         }
1852     } while (*p++);
1853     *q = NULLCHAR;
1854 }
1855
1856 /* Telnet protocol requests from the front end */
1857 void
1858 TelnetRequest(ddww, option)
1859      unsigned char ddww, option;
1860 {
1861     unsigned char msg[3];
1862     int outCount, outError;
1863
1864     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1865
1866     if (appData.debugMode) {
1867         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1868         switch (ddww) {
1869           case TN_DO:
1870             ddwwStr = "DO";
1871             break;
1872           case TN_DONT:
1873             ddwwStr = "DONT";
1874             break;
1875           case TN_WILL:
1876             ddwwStr = "WILL";
1877             break;
1878           case TN_WONT:
1879             ddwwStr = "WONT";
1880             break;
1881           default:
1882             ddwwStr = buf1;
1883             sprintf(buf1, "%d", ddww);
1884             break;
1885         }
1886         switch (option) {
1887           case TN_ECHO:
1888             optionStr = "ECHO";
1889             break;
1890           default:
1891             optionStr = buf2;
1892             sprintf(buf2, "%d", option);
1893             break;
1894         }
1895         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1896     }
1897     msg[0] = TN_IAC;
1898     msg[1] = ddww;
1899     msg[2] = option;
1900     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1901     if (outCount < 3) {
1902         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1903     }
1904 }
1905
1906 void
1907 DoEcho()
1908 {
1909     if (!appData.icsActive) return;
1910     TelnetRequest(TN_DO, TN_ECHO);
1911 }
1912
1913 void
1914 DontEcho()
1915 {
1916     if (!appData.icsActive) return;
1917     TelnetRequest(TN_DONT, TN_ECHO);
1918 }
1919
1920 void
1921 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1922 {
1923     /* put the holdings sent to us by the server on the board holdings area */
1924     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1925     char p;
1926     ChessSquare piece;
1927
1928     if(gameInfo.holdingsWidth < 2)  return;
1929
1930     if( (int)lowestPiece >= BlackPawn ) {
1931         holdingsColumn = 0;
1932         countsColumn = 1;
1933         holdingsStartRow = BOARD_HEIGHT-1;
1934         direction = -1;
1935     } else {
1936         holdingsColumn = BOARD_WIDTH-1;
1937         countsColumn = BOARD_WIDTH-2;
1938         holdingsStartRow = 0;
1939         direction = 1;
1940     }
1941
1942     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1943         board[i][holdingsColumn] = EmptySquare;
1944         board[i][countsColumn]   = (ChessSquare) 0;
1945     }
1946     while( (p=*holdings++) != NULLCHAR ) {
1947         piece = CharToPiece( ToUpper(p) );
1948         if(piece == EmptySquare) continue;
1949         /*j = (int) piece - (int) WhitePawn;*/
1950         j = PieceToNumber(piece);
1951         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1952         if(j < 0) continue;               /* should not happen */
1953         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1954         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1955         board[holdingsStartRow+j*direction][countsColumn]++;
1956     }
1957
1958 }
1959
1960
1961 void
1962 VariantSwitch(Board board, VariantClass newVariant)
1963 {
1964    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1965    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1966 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1967
1968    startedFromPositionFile = FALSE;
1969    if(gameInfo.variant == newVariant) return;
1970
1971    /* [HGM] This routine is called each time an assignment is made to
1972     * gameInfo.variant during a game, to make sure the board sizes
1973     * are set to match the new variant. If that means adding or deleting
1974     * holdings, we shift the playing board accordingly
1975     * This kludge is needed because in ICS observe mode, we get boards
1976     * of an ongoing game without knowing the variant, and learn about the
1977     * latter only later. This can be because of the move list we requested,
1978     * in which case the game history is refilled from the beginning anyway,
1979     * but also when receiving holdings of a crazyhouse game. In the latter
1980     * case we want to add those holdings to the already received position.
1981     */
1982
1983
1984   if (appData.debugMode) {
1985     fprintf(debugFP, "Switch board from %s to %s\n",
1986                VariantName(gameInfo.variant), VariantName(newVariant));
1987     setbuf(debugFP, NULL);
1988   }
1989     shuffleOpenings = 0;       /* [HGM] shuffle */
1990     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1991     switch(newVariant) {
1992             case VariantShogi:
1993               newWidth = 9;  newHeight = 9;
1994               gameInfo.holdingsSize = 7;
1995             case VariantBughouse:
1996             case VariantCrazyhouse:
1997               newHoldingsWidth = 2; break;
1998             default:
1999               newHoldingsWidth = gameInfo.holdingsSize = 0;
2000     }
2001
2002     if(newWidth  != gameInfo.boardWidth  ||
2003        newHeight != gameInfo.boardHeight ||
2004        newHoldingsWidth != gameInfo.holdingsWidth ) {
2005
2006         /* shift position to new playing area, if needed */
2007         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++) 
2009                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                      board[i][j];
2012            for(i=0; i<newHeight; i++) {
2013                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015            }
2016         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017            for(i=0; i<BOARD_HEIGHT; i++)
2018                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2020                                                  board[i][j];
2021         }
2022
2023         gameInfo.boardWidth  = newWidth;
2024         gameInfo.boardHeight = newHeight;
2025         gameInfo.holdingsWidth = newHoldingsWidth;
2026         gameInfo.variant = newVariant;
2027         InitDrawingSizes(-2, 0);
2028
2029         /* [HGM] The following should definitely be solved in a better way */
2030 #if 0
2031         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2032         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2033         saveEP = epStatus[0];
2034 #endif
2035         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2036 #if 0
2037         epStatus[0] = saveEP;
2038         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2039         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2040 #endif
2041     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2042
2043     forwardMostMove = oldForwardMostMove;
2044     backwardMostMove = oldBackwardMostMove;
2045     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2046 }
2047
2048 static int loggedOn = FALSE;
2049
2050 /*-- Game start info cache: --*/
2051 int gs_gamenum;
2052 char gs_kind[MSG_SIZ];
2053 static char player1Name[128] = "";
2054 static char player2Name[128] = "";
2055 static int player1Rating = -1;
2056 static int player2Rating = -1;
2057 /*----------------------------*/
2058
2059 ColorClass curColor = ColorNormal;
2060 int suppressKibitz = 0;
2061
2062 void
2063 read_from_ics(isr, closure, data, count, error)
2064      InputSourceRef isr;
2065      VOIDSTAR closure;
2066      char *data;
2067      int count;
2068      int error;
2069 {
2070 #define BUF_SIZE 8192
2071 #define STARTED_NONE 0
2072 #define STARTED_MOVES 1
2073 #define STARTED_BOARD 2
2074 #define STARTED_OBSERVE 3
2075 #define STARTED_HOLDINGS 4
2076 #define STARTED_CHATTER 5
2077 #define STARTED_COMMENT 6
2078 #define STARTED_MOVES_NOHIDE 7
2079     
2080     static int started = STARTED_NONE;
2081     static char parse[20000];
2082     static int parse_pos = 0;
2083     static char buf[BUF_SIZE + 1];
2084     static int firstTime = TRUE, intfSet = FALSE;
2085     static ColorClass prevColor = ColorNormal;
2086     static int savingComment = FALSE;
2087     char str[500];
2088     int i, oldi;
2089     int buf_len;
2090     int next_out;
2091     int tkind;
2092     int backup;    /* [DM] For zippy color lines */
2093     char *p;
2094
2095     if (appData.debugMode) {
2096       if (!error) {
2097         fprintf(debugFP, "<ICS: ");
2098         show_bytes(debugFP, data, count);
2099         fprintf(debugFP, "\n");
2100       }
2101     }
2102
2103     if (appData.debugMode) { int f = forwardMostMove;
2104         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2105                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2106     }
2107     if (count > 0) {
2108         /* If last read ended with a partial line that we couldn't parse,
2109            prepend it to the new read and try again. */
2110         if (leftover_len > 0) {
2111             for (i=0; i<leftover_len; i++)
2112               buf[i] = buf[leftover_start + i];
2113         }
2114
2115         /* Copy in new characters, removing nulls and \r's */
2116         buf_len = leftover_len;
2117         for (i = 0; i < count; i++) {
2118             if (data[i] != NULLCHAR && data[i] != '\r')
2119               buf[buf_len++] = data[i];
2120             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2121                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') 
2122                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2123         }
2124
2125         buf[buf_len] = NULLCHAR;
2126         next_out = leftover_len;
2127         leftover_start = 0;
2128         
2129         i = 0;
2130         while (i < buf_len) {
2131             /* Deal with part of the TELNET option negotiation
2132                protocol.  We refuse to do anything beyond the
2133                defaults, except that we allow the WILL ECHO option,
2134                which ICS uses to turn off password echoing when we are
2135                directly connected to it.  We reject this option
2136                if localLineEditing mode is on (always on in xboard)
2137                and we are talking to port 23, which might be a real
2138                telnet server that will try to keep WILL ECHO on permanently.
2139              */
2140             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2141                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2142                 unsigned char option;
2143                 oldi = i;
2144                 switch ((unsigned char) buf[++i]) {
2145                   case TN_WILL:
2146                     if (appData.debugMode)
2147                       fprintf(debugFP, "\n<WILL ");
2148                     switch (option = (unsigned char) buf[++i]) {
2149                       case TN_ECHO:
2150                         if (appData.debugMode)
2151                           fprintf(debugFP, "ECHO ");
2152                         /* Reply only if this is a change, according
2153                            to the protocol rules. */
2154                         if (remoteEchoOption) break;
2155                         if (appData.localLineEditing &&
2156                             atoi(appData.icsPort) == TN_PORT) {
2157                             TelnetRequest(TN_DONT, TN_ECHO);
2158                         } else {
2159                             EchoOff();
2160                             TelnetRequest(TN_DO, TN_ECHO);
2161                             remoteEchoOption = TRUE;
2162                         }
2163                         break;
2164                       default:
2165                         if (appData.debugMode)
2166                           fprintf(debugFP, "%d ", option);
2167                         /* Whatever this is, we don't want it. */
2168                         TelnetRequest(TN_DONT, option);
2169                         break;
2170                     }
2171                     break;
2172                   case TN_WONT:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<WONT ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       case TN_ECHO:
2177                         if (appData.debugMode)
2178                           fprintf(debugFP, "ECHO ");
2179                         /* Reply only if this is a change, according
2180                            to the protocol rules. */
2181                         if (!remoteEchoOption) break;
2182                         EchoOn();
2183                         TelnetRequest(TN_DONT, TN_ECHO);
2184                         remoteEchoOption = FALSE;
2185                         break;
2186                       default:
2187                         if (appData.debugMode)
2188                           fprintf(debugFP, "%d ", (unsigned char) option);
2189                         /* Whatever this is, it must already be turned
2190                            off, because we never agree to turn on
2191                            anything non-default, so according to the
2192                            protocol rules, we don't reply. */
2193                         break;
2194                     }
2195                     break;
2196                   case TN_DO:
2197                     if (appData.debugMode)
2198                       fprintf(debugFP, "\n<DO ");
2199                     switch (option = (unsigned char) buf[++i]) {
2200                       default:
2201                         /* Whatever this is, we refuse to do it. */
2202                         if (appData.debugMode)
2203                           fprintf(debugFP, "%d ", option);
2204                         TelnetRequest(TN_WONT, option);
2205                         break;
2206                     }
2207                     break;
2208                   case TN_DONT:
2209                     if (appData.debugMode)
2210                       fprintf(debugFP, "\n<DONT ");
2211                     switch (option = (unsigned char) buf[++i]) {
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", option);
2215                         /* Whatever this is, we are already not doing
2216                            it, because we never agree to do anything
2217                            non-default, so according to the protocol
2218                            rules, we don't reply. */
2219                         break;
2220                     }
2221                     break;
2222                   case TN_IAC:
2223                     if (appData.debugMode)
2224                       fprintf(debugFP, "\n<IAC ");
2225                     /* Doubled IAC; pass it through */
2226                     i--;
2227                     break;
2228                   default:
2229                     if (appData.debugMode)
2230                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2231                     /* Drop all other telnet commands on the floor */
2232                     break;
2233                 }
2234                 if (oldi > next_out)
2235                   SendToPlayer(&buf[next_out], oldi - next_out);
2236                 if (++i > next_out)
2237                   next_out = i;
2238                 continue;
2239             }
2240                 
2241             /* OK, this at least will *usually* work */
2242             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2243                 loggedOn = TRUE;
2244             }
2245             
2246             if (loggedOn && !intfSet) {
2247                 if (ics_type == ICS_ICC) {
2248                   sprintf(str,
2249                           "/set-quietly interface %s\n/set-quietly style 12\n",
2250                           programVersion);
2251
2252                 } else if (ics_type == ICS_CHESSNET) {
2253                   sprintf(str, "/style 12\n");
2254                 } else {
2255                   strcpy(str, "alias $ @\n$set interface ");
2256                   strcat(str, programVersion);
2257                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2258 #ifdef WIN32
2259                   strcat(str, "$iset nohighlight 1\n");
2260 #endif
2261                   strcat(str, "$iset lock 1\n$style 12\n");
2262                 }
2263                 SendToICS(str);
2264                 intfSet = TRUE;
2265             }
2266
2267             if (started == STARTED_COMMENT) {
2268                 /* Accumulate characters in comment */
2269                 parse[parse_pos++] = buf[i];
2270                 if (buf[i] == '\n') {
2271                     parse[parse_pos] = NULLCHAR;
2272                     if(!suppressKibitz) // [HGM] kibitz
2273                         AppendComment(forwardMostMove, StripHighlight(parse));
2274                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2275                         int nrDigit = 0, nrAlph = 0, i;
2276                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2277                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2278                         parse[parse_pos] = NULLCHAR;
2279                         // try to be smart: if it does not look like search info, it should go to
2280                         // ICS interaction window after all, not to engine-output window.
2281                         for(i=0; i<parse_pos; i++) { // count letters and digits
2282                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2283                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2284                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2285                         }
2286                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2287                             int depth=0; float score;
2288                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2289                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2290                                 pvInfoList[forwardMostMove-1].depth = depth;
2291                                 pvInfoList[forwardMostMove-1].score = 100*score;
2292                             }
2293                             OutputKibitz(suppressKibitz, parse);
2294                         } else {
2295                             char tmp[MSG_SIZ];
2296                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2297                             SendToPlayer(tmp, strlen(tmp));
2298                         }
2299                     }
2300                     started = STARTED_NONE;
2301                 } else {
2302                     /* Don't match patterns against characters in chatter */
2303                     i++;
2304                     continue;
2305                 }
2306             }
2307             if (started == STARTED_CHATTER) {
2308                 if (buf[i] != '\n') {
2309                     /* Don't match patterns against characters in chatter */
2310                     i++;
2311                     continue;
2312                 }
2313                 started = STARTED_NONE;
2314             }
2315
2316             /* Kludge to deal with rcmd protocol */
2317             if (firstTime && looking_at(buf, &i, "\001*")) {
2318                 DisplayFatalError(&buf[1], 0, 1);
2319                 continue;
2320             } else {
2321                 firstTime = FALSE;
2322             }
2323
2324             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2325                 ics_type = ICS_ICC;
2326                 ics_prefix = "/";
2327                 if (appData.debugMode)
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);
2329                 continue;
2330             }
2331             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2332                 ics_type = ICS_FICS;
2333                 ics_prefix = "$";
2334                 if (appData.debugMode)
2335                   fprintf(debugFP, "ics_type %d\n", ics_type);
2336                 continue;
2337             }
2338             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2339                 ics_type = ICS_CHESSNET;
2340                 ics_prefix = "/";
2341                 if (appData.debugMode)
2342                   fprintf(debugFP, "ics_type %d\n", ics_type);
2343                 continue;
2344             }
2345
2346             if (!loggedOn &&
2347                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2348                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2349                  looking_at(buf, &i, "will be \"*\""))) {
2350               strcpy(ics_handle, star_match[0]);
2351               continue;
2352             }
2353
2354             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2355               char buf[MSG_SIZ];
2356               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2357               DisplayIcsInteractionTitle(buf);
2358               have_set_title = TRUE;
2359             }
2360
2361             /* skip finger notes */
2362             if (started == STARTED_NONE &&
2363                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2364                  (buf[i] == '1' && buf[i+1] == '0')) &&
2365                 buf[i+2] == ':' && buf[i+3] == ' ') {
2366               started = STARTED_CHATTER;
2367               i += 3;
2368               continue;
2369             }
2370
2371             /* skip formula vars */
2372             if (started == STARTED_NONE &&
2373                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2374               started = STARTED_CHATTER;
2375               i += 3;
2376               continue;
2377             }
2378
2379             oldi = i;
2380             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2381             if (appData.autoKibitz && started == STARTED_NONE && 
2382                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2383                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2384                 if(looking_at(buf, &i, "* kibitzes: ") &&
2385                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2386                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2387                         suppressKibitz = TRUE;
2388                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2389                                 && (gameMode == IcsPlayingWhite)) ||
2390                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2391                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2392                             started = STARTED_CHATTER; // own kibitz we simply discard
2393                         else {
2394                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2395                             parse_pos = 0; parse[0] = NULLCHAR;
2396                             savingComment = TRUE;
2397                             suppressKibitz = gameMode != IcsObserving ? 2 :
2398                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2399                         } 
2400                         continue;
2401                 } else
2402                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2403                     started = STARTED_CHATTER;
2404                     suppressKibitz = TRUE;
2405                 }
2406             } // [HGM] kibitz: end of patch
2407
2408             if (appData.zippyTalk || appData.zippyPlay) {
2409                 /* [DM] Backup address for color zippy lines */
2410                 backup = i;
2411 #if ZIPPY
2412        #ifdef WIN32
2413                if (loggedOn == TRUE)
2414                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2415                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2416        #else
2417                 if (ZippyControl(buf, &i) ||
2418                     ZippyConverse(buf, &i) ||
2419                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2420                       loggedOn = TRUE;
2421                       if (!appData.colorize) continue;
2422                 }
2423        #endif
2424 #endif
2425             } // [DM] 'else { ' deleted
2426                 if (/* Don't color "message" or "messages" output */
2427                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2428                     looking_at(buf, &i, "*. * at *:*: ") ||
2429                     looking_at(buf, &i, "--* (*:*): ") ||
2430                     /* Regular tells and says */
2431                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2432                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2433                     looking_at(buf, &i, "* says: ") ||
2434                     /* Message notifications (same color as tells) */
2435                     looking_at(buf, &i, "* has left a message ") ||
2436                     looking_at(buf, &i, "* just sent you a message:\n") ||
2437                     /* Whispers and kibitzes */
2438                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2439                     looking_at(buf, &i, "* kibitzes: ") ||
2440                     /* Channel tells */
2441                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2442
2443                   if (tkind == 1 && strchr(star_match[0], ':')) {
2444                       /* Avoid "tells you:" spoofs in channels */
2445                      tkind = 3;
2446                   }
2447                   if (star_match[0][0] == NULLCHAR ||
2448                       strchr(star_match[0], ' ') ||
2449                       (tkind == 3 && strchr(star_match[1], ' '))) {
2450                     /* Reject bogus matches */
2451                     i = oldi;
2452                   } else {
2453                     if (appData.colorize) {
2454                       if (oldi > next_out) {
2455                         SendToPlayer(&buf[next_out], oldi - next_out);
2456                         next_out = oldi;
2457                       }
2458                       switch (tkind) {
2459                       case 1:
2460                         Colorize(ColorTell, FALSE);
2461                         curColor = ColorTell;
2462                         break;
2463                       case 2:
2464                         Colorize(ColorKibitz, FALSE);
2465                         curColor = ColorKibitz;
2466                         break;
2467                       case 3:
2468                         p = strrchr(star_match[1], '(');
2469                         if (p == NULL) {
2470                           p = star_match[1];
2471                         } else {
2472                           p++;
2473                         }
2474                         if (atoi(p) == 1) {
2475                           Colorize(ColorChannel1, FALSE);
2476                           curColor = ColorChannel1;
2477                         } else {
2478                           Colorize(ColorChannel, FALSE);
2479                           curColor = ColorChannel;
2480                         }
2481                         break;
2482                       case 5:
2483                         curColor = ColorNormal;
2484                         break;
2485                       }
2486                     }
2487                     if (started == STARTED_NONE && appData.autoComment &&
2488                         (gameMode == IcsObserving ||
2489                          gameMode == IcsPlayingWhite ||
2490                          gameMode == IcsPlayingBlack)) {
2491                       parse_pos = i - oldi;
2492                       memcpy(parse, &buf[oldi], parse_pos);
2493                       parse[parse_pos] = NULLCHAR;
2494                       started = STARTED_COMMENT;
2495                       savingComment = TRUE;
2496                     } else {
2497                       started = STARTED_CHATTER;
2498                       savingComment = FALSE;
2499                     }
2500                     loggedOn = TRUE;
2501                     continue;
2502                   }
2503                 }
2504
2505                 if (looking_at(buf, &i, "* s-shouts: ") ||
2506                     looking_at(buf, &i, "* c-shouts: ")) {
2507                     if (appData.colorize) {
2508                         if (oldi > next_out) {
2509                             SendToPlayer(&buf[next_out], oldi - next_out);
2510                             next_out = oldi;
2511                         }
2512                         Colorize(ColorSShout, FALSE);
2513                         curColor = ColorSShout;
2514                     }
2515                     loggedOn = TRUE;
2516                     started = STARTED_CHATTER;
2517                     continue;
2518                 }
2519
2520                 if (looking_at(buf, &i, "--->")) {
2521                     loggedOn = TRUE;
2522                     continue;
2523                 }
2524
2525                 if (looking_at(buf, &i, "* shouts: ") ||
2526                     looking_at(buf, &i, "--> ")) {
2527                     if (appData.colorize) {
2528                         if (oldi > next_out) {
2529                             SendToPlayer(&buf[next_out], oldi - next_out);
2530                             next_out = oldi;
2531                         }
2532                         Colorize(ColorShout, FALSE);
2533                         curColor = ColorShout;
2534                     }
2535                     loggedOn = TRUE;
2536                     started = STARTED_CHATTER;
2537                     continue;
2538                 }
2539
2540                 if (looking_at( buf, &i, "Challenge:")) {
2541                     if (appData.colorize) {
2542                         if (oldi > next_out) {
2543                             SendToPlayer(&buf[next_out], oldi - next_out);
2544                             next_out = oldi;
2545                         }
2546                         Colorize(ColorChallenge, FALSE);
2547                         curColor = ColorChallenge;
2548                     }
2549                     loggedOn = TRUE;
2550                     continue;
2551                 }
2552
2553                 if (looking_at(buf, &i, "* offers you") ||
2554                     looking_at(buf, &i, "* offers to be") ||
2555                     looking_at(buf, &i, "* would like to") ||
2556                     looking_at(buf, &i, "* requests to") ||
2557                     looking_at(buf, &i, "Your opponent offers") ||
2558                     looking_at(buf, &i, "Your opponent requests")) {
2559
2560                     if (appData.colorize) {
2561                         if (oldi > next_out) {
2562                             SendToPlayer(&buf[next_out], oldi - next_out);
2563                             next_out = oldi;
2564                         }
2565                         Colorize(ColorRequest, FALSE);
2566                         curColor = ColorRequest;
2567                     }
2568                     continue;
2569                 }
2570
2571                 if (looking_at(buf, &i, "* (*) seeking")) {
2572                     if (appData.colorize) {
2573                         if (oldi > next_out) {
2574                             SendToPlayer(&buf[next_out], oldi - next_out);
2575                             next_out = oldi;
2576                         }
2577                         Colorize(ColorSeek, FALSE);
2578                         curColor = ColorSeek;
2579                     }
2580                     continue;
2581             }
2582
2583             if (looking_at(buf, &i, "\\   ")) {
2584                 if (prevColor != ColorNormal) {
2585                     if (oldi > next_out) {
2586                         SendToPlayer(&buf[next_out], oldi - next_out);
2587                         next_out = oldi;
2588                     }
2589                     Colorize(prevColor, TRUE);
2590                     curColor = prevColor;
2591                 }
2592                 if (savingComment) {
2593                     parse_pos = i - oldi;
2594                     memcpy(parse, &buf[oldi], parse_pos);
2595                     parse[parse_pos] = NULLCHAR;
2596                     started = STARTED_COMMENT;
2597                 } else {
2598                     started = STARTED_CHATTER;
2599                 }
2600                 continue;
2601             }
2602
2603             if (looking_at(buf, &i, "Black Strength :") ||
2604                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2605                 looking_at(buf, &i, "<10>") ||
2606                 looking_at(buf, &i, "#@#")) {
2607                 /* Wrong board style */
2608                 loggedOn = TRUE;
2609                 SendToICS(ics_prefix);
2610                 SendToICS("set style 12\n");
2611                 SendToICS(ics_prefix);
2612                 SendToICS("refresh\n");
2613                 continue;
2614             }
2615             
2616             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2617                 ICSInitScript();
2618                 have_sent_ICS_logon = 1;
2619                 continue;
2620             }
2621               
2622             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2623                 (looking_at(buf, &i, "\n<12> ") ||
2624                  looking_at(buf, &i, "<12> "))) {
2625                 loggedOn = TRUE;
2626                 if (oldi > next_out) {
2627                     SendToPlayer(&buf[next_out], oldi - next_out);
2628                 }
2629                 next_out = i;
2630                 started = STARTED_BOARD;
2631                 parse_pos = 0;
2632                 continue;
2633             }
2634
2635             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2636                 looking_at(buf, &i, "<b1> ")) {
2637                 if (oldi > next_out) {
2638                     SendToPlayer(&buf[next_out], oldi - next_out);
2639                 }
2640                 next_out = i;
2641                 started = STARTED_HOLDINGS;
2642                 parse_pos = 0;
2643                 continue;
2644             }
2645
2646             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2647                 loggedOn = TRUE;
2648                 /* Header for a move list -- first line */
2649
2650                 switch (ics_getting_history) {
2651                   case H_FALSE:
2652                     switch (gameMode) {
2653                       case IcsIdle:
2654                       case BeginningOfGame:
2655                         /* User typed "moves" or "oldmoves" while we
2656                            were idle.  Pretend we asked for these
2657                            moves and soak them up so user can step
2658                            through them and/or save them.
2659                            */
2660                         Reset(FALSE, TRUE);
2661                         gameMode = IcsObserving;
2662                         ModeHighlight();
2663                         ics_gamenum = -1;
2664                         ics_getting_history = H_GOT_UNREQ_HEADER;
2665                         break;
2666                       case EditGame: /*?*/
2667                       case EditPosition: /*?*/
2668                         /* Should above feature work in these modes too? */
2669                         /* For now it doesn't */
2670                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2671                         break;
2672                       default:
2673                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2674                         break;
2675                     }
2676                     break;
2677                   case H_REQUESTED:
2678                     /* Is this the right one? */
2679                     if (gameInfo.white && gameInfo.black &&
2680                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2681                         strcmp(gameInfo.black, star_match[2]) == 0) {
2682                         /* All is well */
2683                         ics_getting_history = H_GOT_REQ_HEADER;
2684                     }
2685                     break;
2686                   case H_GOT_REQ_HEADER:
2687                   case H_GOT_UNREQ_HEADER:
2688                   case H_GOT_UNWANTED_HEADER:
2689                   case H_GETTING_MOVES:
2690                     /* Should not happen */
2691                     DisplayError(_("Error gathering move list: two headers"), 0);
2692                     ics_getting_history = H_FALSE;
2693                     break;
2694                 }
2695
2696                 /* Save player ratings into gameInfo if needed */
2697                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2698                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2699                     (gameInfo.whiteRating == -1 ||
2700                      gameInfo.blackRating == -1)) {
2701
2702                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2703                     gameInfo.blackRating = string_to_rating(star_match[3]);
2704                     if (appData.debugMode)
2705                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2706                               gameInfo.whiteRating, gameInfo.blackRating);
2707                 }
2708                 continue;
2709             }
2710
2711             if (looking_at(buf, &i,
2712               "* * match, initial time: * minute*, increment: * second")) {
2713                 /* Header for a move list -- second line */
2714                 /* Initial board will follow if this is a wild game */
2715                 if (gameInfo.event != NULL) free(gameInfo.event);
2716                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2717                 gameInfo.event = StrSave(str);
2718                 /* [HGM] we switched variant. Translate boards if needed. */
2719                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2720                 continue;
2721             }
2722
2723             if (looking_at(buf, &i, "Move  ")) {
2724                 /* Beginning of a move list */
2725                 switch (ics_getting_history) {
2726                   case H_FALSE:
2727                     /* Normally should not happen */
2728                     /* Maybe user hit reset while we were parsing */
2729                     break;
2730                   case H_REQUESTED:
2731                     /* Happens if we are ignoring a move list that is not
2732                      * the one we just requested.  Common if the user
2733                      * tries to observe two games without turning off
2734                      * getMoveList */
2735                     break;
2736                   case H_GETTING_MOVES:
2737                     /* Should not happen */
2738                     DisplayError(_("Error gathering move list: nested"), 0);
2739                     ics_getting_history = H_FALSE;
2740                     break;
2741                   case H_GOT_REQ_HEADER:
2742                     ics_getting_history = H_GETTING_MOVES;
2743                     started = STARTED_MOVES;
2744                     parse_pos = 0;
2745                     if (oldi > next_out) {
2746                         SendToPlayer(&buf[next_out], oldi - next_out);
2747                     }
2748                     break;
2749                   case H_GOT_UNREQ_HEADER:
2750                     ics_getting_history = H_GETTING_MOVES;
2751                     started = STARTED_MOVES_NOHIDE;
2752                     parse_pos = 0;
2753                     break;
2754                   case H_GOT_UNWANTED_HEADER:
2755                     ics_getting_history = H_FALSE;
2756                     break;
2757                 }
2758                 continue;
2759             }                           
2760             
2761             if (looking_at(buf, &i, "% ") ||
2762                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2763                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2764                 savingComment = FALSE;
2765                 switch (started) {
2766                   case STARTED_MOVES:
2767                   case STARTED_MOVES_NOHIDE:
2768                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2769                     parse[parse_pos + i - oldi] = NULLCHAR;
2770                     ParseGameHistory(parse);
2771 #if ZIPPY
2772                     if (appData.zippyPlay && first.initDone) {
2773                         FeedMovesToProgram(&first, forwardMostMove);
2774                         if (gameMode == IcsPlayingWhite) {
2775                             if (WhiteOnMove(forwardMostMove)) {
2776                                 if (first.sendTime) {
2777                                   if (first.useColors) {
2778                                     SendToProgram("black\n", &first); 
2779                                   }
2780                                   SendTimeRemaining(&first, TRUE);
2781                                 }
2782 #if 0
2783                                 if (first.useColors) {
2784                                   SendToProgram("white\ngo\n", &first);
2785                                 } else {
2786                                   SendToProgram("go\n", &first);
2787                                 }
2788 #else
2789                                 if (first.useColors) {
2790                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2791                                 }
2792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2793 #endif
2794                                 first.maybeThinking = TRUE;
2795                             } else {
2796                                 if (first.usePlayother) {
2797                                   if (first.sendTime) {
2798                                     SendTimeRemaining(&first, TRUE);
2799                                   }
2800                                   SendToProgram("playother\n", &first);
2801                                   firstMove = FALSE;
2802                                 } else {
2803                                   firstMove = TRUE;
2804                                 }
2805                             }
2806                         } else if (gameMode == IcsPlayingBlack) {
2807                             if (!WhiteOnMove(forwardMostMove)) {
2808                                 if (first.sendTime) {
2809                                   if (first.useColors) {
2810                                     SendToProgram("white\n", &first);
2811                                   }
2812                                   SendTimeRemaining(&first, FALSE);
2813                                 }
2814 #if 0
2815                                 if (first.useColors) {
2816                                   SendToProgram("black\ngo\n", &first);
2817                                 } else {
2818                                   SendToProgram("go\n", &first);
2819                                 }
2820 #else
2821                                 if (first.useColors) {
2822                                   SendToProgram("black\n", &first);
2823                                 }
2824                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2825 #endif
2826                                 first.maybeThinking = TRUE;
2827                             } else {
2828                                 if (first.usePlayother) {
2829                                   if (first.sendTime) {
2830                                     SendTimeRemaining(&first, FALSE);
2831                                   }
2832                                   SendToProgram("playother\n", &first);
2833                                   firstMove = FALSE;
2834                                 } else {
2835                                   firstMove = TRUE;
2836                                 }
2837                             }
2838                         }                       
2839                     }
2840 #endif
2841                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2842                         /* Moves came from oldmoves or moves command
2843                            while we weren't doing anything else.
2844                            */
2845                         currentMove = forwardMostMove;
2846                         ClearHighlights();/*!!could figure this out*/
2847                         flipView = appData.flipView;
2848                         DrawPosition(FALSE, boards[currentMove]);
2849                         DisplayBothClocks();
2850                         sprintf(str, "%s vs. %s",
2851                                 gameInfo.white, gameInfo.black);
2852                         DisplayTitle(str);
2853                         gameMode = IcsIdle;
2854                     } else {
2855                         /* Moves were history of an active game */
2856                         if (gameInfo.resultDetails != NULL) {
2857                             free(gameInfo.resultDetails);
2858                             gameInfo.resultDetails = NULL;
2859                         }
2860                     }
2861                     HistorySet(parseList, backwardMostMove,
2862                                forwardMostMove, currentMove-1);
2863                     DisplayMove(currentMove - 1);
2864                     if (started == STARTED_MOVES) next_out = i;
2865                     started = STARTED_NONE;
2866                     ics_getting_history = H_FALSE;
2867                     break;
2868
2869                   case STARTED_OBSERVE:
2870                     started = STARTED_NONE;
2871                     SendToICS(ics_prefix);
2872                     SendToICS("refresh\n");
2873                     break;
2874
2875                   default:
2876                     break;
2877                 }
2878                 if(bookHit) { // [HGM] book: simulate book reply
2879                     static char bookMove[MSG_SIZ]; // a bit generous?
2880
2881                     programStats.nodes = programStats.depth = programStats.time = 
2882                     programStats.score = programStats.got_only_move = 0;
2883                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2884
2885                     strcpy(bookMove, "move ");
2886                     strcat(bookMove, bookHit);
2887                     HandleMachineMove(bookMove, &first);
2888                 }
2889                 continue;
2890             }
2891             
2892             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2893                  started == STARTED_HOLDINGS ||
2894                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2895                 /* Accumulate characters in move list or board */
2896                 parse[parse_pos++] = buf[i];
2897             }
2898             
2899             /* Start of game messages.  Mostly we detect start of game
2900                when the first board image arrives.  On some versions
2901                of the ICS, though, we need to do a "refresh" after starting
2902                to observe in order to get the current board right away. */
2903             if (looking_at(buf, &i, "Adding game * to observation list")) {
2904                 started = STARTED_OBSERVE;
2905                 continue;
2906             }
2907
2908             /* Handle auto-observe */
2909             if (appData.autoObserve &&
2910                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2911                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2912                 char *player;
2913                 /* Choose the player that was highlighted, if any. */
2914                 if (star_match[0][0] == '\033' ||
2915                     star_match[1][0] != '\033') {
2916                     player = star_match[0];
2917                 } else {
2918                     player = star_match[2];
2919                 }
2920                 sprintf(str, "%sobserve %s\n",
2921                         ics_prefix, StripHighlightAndTitle(player));
2922                 SendToICS(str);
2923
2924                 /* Save ratings from notify string */
2925                 strcpy(player1Name, star_match[0]);
2926                 player1Rating = string_to_rating(star_match[1]);
2927                 strcpy(player2Name, star_match[2]);
2928                 player2Rating = string_to_rating(star_match[3]);
2929
2930                 if (appData.debugMode)
2931                   fprintf(debugFP, 
2932                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2933                           player1Name, player1Rating,
2934                           player2Name, player2Rating);
2935
2936                 continue;
2937             }
2938
2939             /* Deal with automatic examine mode after a game,
2940                and with IcsObserving -> IcsExamining transition */
2941             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2942                 looking_at(buf, &i, "has made you an examiner of game *")) {
2943
2944                 int gamenum = atoi(star_match[0]);
2945                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2946                     gamenum == ics_gamenum) {
2947                     /* We were already playing or observing this game;
2948                        no need to refetch history */
2949                     gameMode = IcsExamining;
2950                     if (pausing) {
2951                         pauseExamForwardMostMove = forwardMostMove;
2952                     } else if (currentMove < forwardMostMove) {
2953                         ForwardInner(forwardMostMove);
2954                     }
2955                 } else {
2956                     /* I don't think this case really can happen */
2957                     SendToICS(ics_prefix);
2958                     SendToICS("refresh\n");
2959                 }
2960                 continue;
2961             }    
2962             
2963             /* Error messages */
2964 //          if (ics_user_moved) {
2965             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2966                 if (looking_at(buf, &i, "Illegal move") ||
2967                     looking_at(buf, &i, "Not a legal move") ||
2968                     looking_at(buf, &i, "Your king is in check") ||
2969                     looking_at(buf, &i, "It isn't your turn") ||
2970                     looking_at(buf, &i, "It is not your move")) {
2971                     /* Illegal move */
2972                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2973                         currentMove = --forwardMostMove;
2974                         DisplayMove(currentMove - 1); /* before DMError */
2975                         DrawPosition(FALSE, boards[currentMove]);
2976                         SwitchClocks();
2977                         DisplayBothClocks();
2978                     }
2979                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2980                     ics_user_moved = 0;
2981                     continue;
2982                 }
2983             }
2984
2985             if (looking_at(buf, &i, "still have time") ||
2986                 looking_at(buf, &i, "not out of time") ||
2987                 looking_at(buf, &i, "either player is out of time") ||
2988                 looking_at(buf, &i, "has timeseal; checking")) {
2989                 /* We must have called his flag a little too soon */
2990                 whiteFlag = blackFlag = FALSE;
2991                 continue;
2992             }
2993
2994             if (looking_at(buf, &i, "added * seconds to") ||
2995                 looking_at(buf, &i, "seconds were added to")) {
2996                 /* Update the clocks */
2997                 SendToICS(ics_prefix);
2998                 SendToICS("refresh\n");
2999                 continue;
3000             }
3001
3002             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3003                 ics_clock_paused = TRUE;
3004                 StopClocks();
3005                 continue;
3006             }
3007
3008             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3009                 ics_clock_paused = FALSE;
3010                 StartClocks();
3011                 continue;
3012             }
3013
3014             /* Grab player ratings from the Creating: message.
3015                Note we have to check for the special case when
3016                the ICS inserts things like [white] or [black]. */
3017             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3018                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3019                 /* star_matches:
3020                    0    player 1 name (not necessarily white)
3021                    1    player 1 rating
3022                    2    empty, white, or black (IGNORED)
3023                    3    player 2 name (not necessarily black)
3024                    4    player 2 rating
3025                    
3026                    The names/ratings are sorted out when the game
3027                    actually starts (below).
3028                 */
3029                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3030                 player1Rating = string_to_rating(star_match[1]);
3031                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3032                 player2Rating = string_to_rating(star_match[4]);
3033
3034                 if (appData.debugMode)
3035                   fprintf(debugFP, 
3036                           "Ratings from 'Creating:' %s %d, %s %d\n",
3037                           player1Name, player1Rating,
3038                           player2Name, player2Rating);
3039
3040                 continue;
3041             }
3042             
3043             /* Improved generic start/end-of-game messages */
3044             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3045                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3046                 /* If tkind == 0: */
3047                 /* star_match[0] is the game number */
3048                 /*           [1] is the white player's name */
3049                 /*           [2] is the black player's name */
3050                 /* For end-of-game: */
3051                 /*           [3] is the reason for the game end */
3052                 /*           [4] is a PGN end game-token, preceded by " " */
3053                 /* For start-of-game: */
3054                 /*           [3] begins with "Creating" or "Continuing" */
3055                 /*           [4] is " *" or empty (don't care). */
3056                 int gamenum = atoi(star_match[0]);
3057                 char *whitename, *blackname, *why, *endtoken;
3058                 ChessMove endtype = (ChessMove) 0;
3059
3060                 if (tkind == 0) {
3061                   whitename = star_match[1];
3062                   blackname = star_match[2];
3063                   why = star_match[3];
3064                   endtoken = star_match[4];
3065                 } else {
3066                   whitename = star_match[1];
3067                   blackname = star_match[3];
3068                   why = star_match[5];
3069                   endtoken = star_match[6];
3070                 }
3071
3072                 /* Game start messages */
3073                 if (strncmp(why, "Creating ", 9) == 0 ||
3074                     strncmp(why, "Continuing ", 11) == 0) {
3075                     gs_gamenum = gamenum;
3076                     strcpy(gs_kind, strchr(why, ' ') + 1);
3077 #if ZIPPY
3078                     if (appData.zippyPlay) {
3079                         ZippyGameStart(whitename, blackname);
3080                     }
3081 #endif /*ZIPPY*/
3082                     continue;
3083                 }
3084
3085                 /* Game end messages */
3086                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3087                     ics_gamenum != gamenum) {
3088                     continue;
3089                 }
3090                 while (endtoken[0] == ' ') endtoken++;
3091                 switch (endtoken[0]) {
3092                   case '*':
3093                   default:
3094                     endtype = GameUnfinished;
3095                     break;
3096                   case '0':
3097                     endtype = BlackWins;
3098                     break;
3099                   case '1':
3100                     if (endtoken[1] == '/')
3101                       endtype = GameIsDrawn;
3102                     else
3103                       endtype = WhiteWins;
3104                     break;
3105                 }
3106                 GameEnds(endtype, why, GE_ICS);
3107 #if ZIPPY
3108                 if (appData.zippyPlay && first.initDone) {
3109                     ZippyGameEnd(endtype, why);
3110                     if (first.pr == NULL) {
3111                       /* Start the next process early so that we'll
3112                          be ready for the next challenge */
3113                       StartChessProgram(&first);
3114                     }
3115                     /* Send "new" early, in case this command takes
3116                        a long time to finish, so that we'll be ready
3117                        for the next challenge. */
3118                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3119                     Reset(TRUE, TRUE);
3120                 }
3121 #endif /*ZIPPY*/
3122                 continue;
3123             }
3124
3125             if (looking_at(buf, &i, "Removing game * from observation") ||
3126                 looking_at(buf, &i, "no longer observing game *") ||
3127                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3128                 if (gameMode == IcsObserving &&
3129                     atoi(star_match[0]) == ics_gamenum)
3130                   {
3131                       /* icsEngineAnalyze */
3132                       if (appData.icsEngineAnalyze) {
3133                             ExitAnalyzeMode();
3134                             ModeHighlight();
3135                       }
3136                       StopClocks();
3137                       gameMode = IcsIdle;
3138                       ics_gamenum = -1;
3139                       ics_user_moved = FALSE;
3140                   }
3141                 continue;
3142             }
3143
3144             if (looking_at(buf, &i, "no longer examining game *")) {
3145                 if (gameMode == IcsExamining &&
3146                     atoi(star_match[0]) == ics_gamenum)
3147                   {
3148                       gameMode = IcsIdle;
3149                       ics_gamenum = -1;
3150                       ics_user_moved = FALSE;
3151                   }
3152                 continue;
3153             }
3154
3155             /* Advance leftover_start past any newlines we find,
3156                so only partial lines can get reparsed */
3157             if (looking_at(buf, &i, "\n")) {
3158                 prevColor = curColor;
3159                 if (curColor != ColorNormal) {
3160                     if (oldi > next_out) {
3161                         SendToPlayer(&buf[next_out], oldi - next_out);
3162                         next_out = oldi;
3163                     }
3164                     Colorize(ColorNormal, FALSE);
3165                     curColor = ColorNormal;
3166                 }
3167                 if (started == STARTED_BOARD) {
3168                     started = STARTED_NONE;
3169                     parse[parse_pos] = NULLCHAR;
3170                     ParseBoard12(parse);
3171                     ics_user_moved = 0;
3172
3173                     /* Send premove here */
3174                     if (appData.premove) {
3175                       char str[MSG_SIZ];
3176                       if (currentMove == 0 &&
3177                           gameMode == IcsPlayingWhite &&
3178                           appData.premoveWhite) {
3179                         sprintf(str, "%s%s\n", ics_prefix,
3180                                 appData.premoveWhiteText);
3181                         if (appData.debugMode)
3182                           fprintf(debugFP, "Sending premove:\n");
3183                         SendToICS(str);
3184                       } else if (currentMove == 1 &&
3185                                  gameMode == IcsPlayingBlack &&
3186                                  appData.premoveBlack) {
3187                         sprintf(str, "%s%s\n", ics_prefix,
3188                                 appData.premoveBlackText);
3189                         if (appData.debugMode)
3190                           fprintf(debugFP, "Sending premove:\n");
3191                         SendToICS(str);
3192                       } else if (gotPremove) {
3193                         gotPremove = 0;
3194                         ClearPremoveHighlights();
3195                         if (appData.debugMode)
3196                           fprintf(debugFP, "Sending premove:\n");
3197                           UserMoveEvent(premoveFromX, premoveFromY, 
3198                                         premoveToX, premoveToY, 
3199                                         premovePromoChar);
3200                       }
3201                     }
3202
3203                     /* Usually suppress following prompt */
3204                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3205                         if (looking_at(buf, &i, "*% ")) {
3206                             savingComment = FALSE;
3207                         }
3208                     }
3209                     next_out = i;
3210                 } else if (started == STARTED_HOLDINGS) {
3211                     int gamenum;
3212                     char new_piece[MSG_SIZ];
3213                     started = STARTED_NONE;
3214                     parse[parse_pos] = NULLCHAR;
3215                     if (appData.debugMode)
3216                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3217                                                         parse, currentMove);
3218                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3219                         gamenum == ics_gamenum) {
3220                         if (gameInfo.variant == VariantNormal) {
3221                           /* [HGM] We seem to switch variant during a game!
3222                            * Presumably no holdings were displayed, so we have
3223                            * to move the position two files to the right to
3224                            * create room for them!
3225                            */
3226                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3227                           /* Get a move list just to see the header, which
3228                              will tell us whether this is really bug or zh */
3229                           if (ics_getting_history == H_FALSE) {
3230                             ics_getting_history = H_REQUESTED;
3231                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3232                             SendToICS(str);
3233                           }
3234                         }
3235                         new_piece[0] = NULLCHAR;
3236                         sscanf(parse, "game %d white [%s black [%s <- %s",
3237                                &gamenum, white_holding, black_holding,
3238                                new_piece);
3239                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3240                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3241                         /* [HGM] copy holdings to board holdings area */
3242                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3243                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3244 #if ZIPPY
3245                         if (appData.zippyPlay && first.initDone) {
3246                             ZippyHoldings(white_holding, black_holding,
3247                                           new_piece);
3248                         }
3249 #endif /*ZIPPY*/
3250                         if (tinyLayout || smallLayout) {
3251                             char wh[16], bh[16];
3252                             PackHolding(wh, white_holding);
3253                             PackHolding(bh, black_holding);
3254                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3255                                     gameInfo.white, gameInfo.black);
3256                         } else {
3257                             sprintf(str, "%s [%s] vs. %s [%s]",
3258                                     gameInfo.white, white_holding,
3259                                     gameInfo.black, black_holding);
3260                         }
3261
3262                         DrawPosition(FALSE, boards[currentMove]);
3263                         DisplayTitle(str);
3264                     }
3265                     /* Suppress following prompt */
3266                     if (looking_at(buf, &i, "*% ")) {
3267                         savingComment = FALSE;
3268                     }
3269                     next_out = i;
3270                 }
3271                 continue;
3272             }
3273
3274             i++;                /* skip unparsed character and loop back */
3275         }
3276         
3277         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3278             started != STARTED_HOLDINGS && i > next_out) {
3279             SendToPlayer(&buf[next_out], i - next_out);
3280             next_out = i;
3281         }
3282         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3283         
3284         leftover_len = buf_len - leftover_start;
3285         /* if buffer ends with something we couldn't parse,
3286            reparse it after appending the next read */
3287         
3288     } else if (count == 0) {
3289         RemoveInputSource(isr);
3290         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3291     } else {
3292         DisplayFatalError(_("Error reading from ICS"), error, 1);
3293     }
3294 }
3295
3296
3297 /* Board style 12 looks like this:
3298    
3299    <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
3300    
3301  * The "<12> " is stripped before it gets to this routine.  The two
3302  * trailing 0's (flip state and clock ticking) are later addition, and
3303  * some chess servers may not have them, or may have only the first.
3304  * Additional trailing fields may be added in the future.  
3305  */
3306
3307 #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"
3308
3309 #define RELATION_OBSERVING_PLAYED    0
3310 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3311 #define RELATION_PLAYING_MYMOVE      1
3312 #define RELATION_PLAYING_NOTMYMOVE  -1
3313 #define RELATION_EXAMINING           2
3314 #define RELATION_ISOLATED_BOARD     -3
3315 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3316
3317 void
3318 ParseBoard12(string)
3319      char *string;
3320
3321     GameMode newGameMode;
3322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3325     char to_play, board_chars[200];
3326     char move_str[500], str[500], elapsed_time[500];
3327     char black[32], white[32];
3328     Board board;
3329     int prevMove = currentMove;
3330     int ticking = 2;
3331     ChessMove moveType;
3332     int fromX, fromY, toX, toY;
3333     char promoChar;
3334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3335     char *bookHit = NULL; // [HGM] book
3336
3337     fromX = fromY = toX = toY = -1;
3338     
3339     newGame = FALSE;
3340
3341     if (appData.debugMode)
3342       fprintf(debugFP, _("Parsing board: %s\n"), string);
3343
3344     move_str[0] = NULLCHAR;
3345     elapsed_time[0] = NULLCHAR;
3346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3347         int  i = 0, j;
3348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3349             if(string[i] == ' ') { ranks++; files = 0; }
3350             else files++;
3351             i++;
3352         }
3353         for(j = 0; j <i; j++) board_chars[j] = string[j];
3354         board_chars[i] = '\0';
3355         string += i + 1;
3356     }
3357     n = sscanf(string, PATTERN, &to_play, &double_push,
3358                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3359                &gamenum, white, black, &relation, &basetime, &increment,
3360                &white_stren, &black_stren, &white_time, &black_time,
3361                &moveNum, str, elapsed_time, move_str, &ics_flip,
3362                &ticking);
3363
3364     if (n < 21) {
3365         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3366         DisplayError(str, 0);
3367         return;
3368     }
3369
3370     /* Convert the move number to internal form */
3371     moveNum = (moveNum - 1) * 2;
3372     if (to_play == 'B') moveNum++;
3373     if (moveNum >= MAX_MOVES) {
3374       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3375                         0, 1);
3376       return;
3377     }
3378     
3379     switch (relation) {
3380       case RELATION_OBSERVING_PLAYED:
3381       case RELATION_OBSERVING_STATIC:
3382         if (gamenum == -1) {
3383             /* Old ICC buglet */
3384             relation = RELATION_OBSERVING_STATIC;
3385         }
3386         newGameMode = IcsObserving;
3387         break;
3388       case RELATION_PLAYING_MYMOVE:
3389       case RELATION_PLAYING_NOTMYMOVE:
3390         newGameMode =
3391           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3392             IcsPlayingWhite : IcsPlayingBlack;
3393         break;
3394       case RELATION_EXAMINING:
3395         newGameMode = IcsExamining;
3396         break;
3397       case RELATION_ISOLATED_BOARD:
3398       default:
3399         /* Just display this board.  If user was doing something else,
3400            we will forget about it until the next board comes. */ 
3401         newGameMode = IcsIdle;
3402         break;
3403       case RELATION_STARTING_POSITION:
3404         newGameMode = gameMode;
3405         break;
3406     }
3407     
3408     /* Modify behavior for initial board display on move listing
3409        of wild games.
3410        */
3411     switch (ics_getting_history) {
3412       case H_FALSE:
3413       case H_REQUESTED:
3414         break;
3415       case H_GOT_REQ_HEADER:
3416       case H_GOT_UNREQ_HEADER:
3417         /* This is the initial position of the current game */
3418         gamenum = ics_gamenum;
3419         moveNum = 0;            /* old ICS bug workaround */
3420         if (to_play == 'B') {
3421           startedFromSetupPosition = TRUE;
3422           blackPlaysFirst = TRUE;
3423           moveNum = 1;
3424           if (forwardMostMove == 0) forwardMostMove = 1;
3425           if (backwardMostMove == 0) backwardMostMove = 1;
3426           if (currentMove == 0) currentMove = 1;
3427         }
3428         newGameMode = gameMode;
3429         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3430         break;
3431       case H_GOT_UNWANTED_HEADER:
3432         /* This is an initial board that we don't want */
3433         return;
3434       case H_GETTING_MOVES:
3435         /* Should not happen */
3436         DisplayError(_("Error gathering move list: extra board"), 0);
3437         ics_getting_history = H_FALSE;
3438         return;
3439     }
3440     
3441     /* Take action if this is the first board of a new game, or of a
3442        different game than is currently being displayed.  */
3443     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3444         relation == RELATION_ISOLATED_BOARD) {
3445         
3446         /* Forget the old game and get the history (if any) of the new one */
3447         if (gameMode != BeginningOfGame) {
3448           Reset(FALSE, TRUE);
3449         }
3450         newGame = TRUE;
3451         if (appData.autoRaiseBoard) BoardToTop();
3452         prevMove = -3;
3453         if (gamenum == -1) {
3454             newGameMode = IcsIdle;
3455         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3456                    appData.getMoveList) {
3457             /* Need to get game history */
3458             ics_getting_history = H_REQUESTED;
3459             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3460             SendToICS(str);
3461         }
3462         
3463         /* Initially flip the board to have black on the bottom if playing
3464            black or if the ICS flip flag is set, but let the user change
3465            it with the Flip View button. */
3466         flipView = appData.autoFlipView ? 
3467           (newGameMode == IcsPlayingBlack) || ics_flip :
3468           appData.flipView;
3469         
3470         /* Done with values from previous mode; copy in new ones */
3471         gameMode = newGameMode;
3472         ModeHighlight();
3473         ics_gamenum = gamenum;
3474         if (gamenum == gs_gamenum) {
3475             int klen = strlen(gs_kind);
3476             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3477             sprintf(str, "ICS %s", gs_kind);
3478             gameInfo.event = StrSave(str);
3479         } else {
3480             gameInfo.event = StrSave("ICS game");
3481         }
3482         gameInfo.site = StrSave(appData.icsHost);
3483         gameInfo.date = PGNDate();
3484         gameInfo.round = StrSave("-");
3485         gameInfo.white = StrSave(white);
3486         gameInfo.black = StrSave(black);
3487         timeControl = basetime * 60 * 1000;
3488         timeControl_2 = 0;
3489         timeIncrement = increment * 1000;
3490         movesPerSession = 0;
3491         gameInfo.timeControl = TimeControlTagValue();
3492         VariantSwitch(board, StringToVariant(gameInfo.event) );
3493   if (appData.debugMode) {
3494     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3495     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3496     setbuf(debugFP, NULL);
3497   }
3498
3499         gameInfo.outOfBook = NULL;
3500         
3501         /* Do we have the ratings? */
3502         if (strcmp(player1Name, white) == 0 &&
3503             strcmp(player2Name, black) == 0) {
3504             if (appData.debugMode)
3505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3506                       player1Rating, player2Rating);
3507             gameInfo.whiteRating = player1Rating;
3508             gameInfo.blackRating = player2Rating;
3509         } else if (strcmp(player2Name, white) == 0 &&
3510                    strcmp(player1Name, black) == 0) {
3511             if (appData.debugMode)
3512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3513                       player2Rating, player1Rating);
3514             gameInfo.whiteRating = player2Rating;
3515             gameInfo.blackRating = player1Rating;
3516         }
3517         player1Name[0] = player2Name[0] = NULLCHAR;
3518
3519         /* Silence shouts if requested */
3520         if (appData.quietPlay &&
3521             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3522             SendToICS(ics_prefix);
3523             SendToICS("set shout 0\n");
3524         }
3525     }
3526     
3527     /* Deal with midgame name changes */
3528     if (!newGame) {
3529         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3530             if (gameInfo.white) free(gameInfo.white);
3531             gameInfo.white = StrSave(white);
3532         }
3533         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3534             if (gameInfo.black) free(gameInfo.black);
3535             gameInfo.black = StrSave(black);
3536         }
3537     }
3538     
3539     /* Throw away game result if anything actually changes in examine mode */
3540     if (gameMode == IcsExamining && !newGame) {
3541         gameInfo.result = GameUnfinished;
3542         if (gameInfo.resultDetails != NULL) {
3543             free(gameInfo.resultDetails);
3544             gameInfo.resultDetails = NULL;
3545         }
3546     }
3547     
3548     /* In pausing && IcsExamining mode, we ignore boards coming
3549        in if they are in a different variation than we are. */
3550     if (pauseExamInvalid) return;
3551     if (pausing && gameMode == IcsExamining) {
3552         if (moveNum <= pauseExamForwardMostMove) {
3553             pauseExamInvalid = TRUE;
3554             forwardMostMove = pauseExamForwardMostMove;
3555             return;
3556         }
3557     }
3558     
3559   if (appData.debugMode) {
3560     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3561   }
3562     /* Parse the board */
3563     for (k = 0; k < ranks; k++) {
3564       for (j = 0; j < files; j++)
3565         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3566       if(gameInfo.holdingsWidth > 1) {
3567            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3568            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3569       }
3570     }
3571     CopyBoard(boards[moveNum], board);
3572     if (moveNum == 0) {
3573         startedFromSetupPosition =
3574           !CompareBoards(board, initialPosition);
3575         if(startedFromSetupPosition)
3576             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3577     }
3578
3579     /* [HGM] Set castling rights. Take the outermost Rooks,
3580        to make it also work for FRC opening positions. Note that board12
3581        is really defective for later FRC positions, as it has no way to
3582        indicate which Rook can castle if they are on the same side of King.
3583        For the initial position we grant rights to the outermost Rooks,
3584        and remember thos rights, and we then copy them on positions
3585        later in an FRC game. This means WB might not recognize castlings with
3586        Rooks that have moved back to their original position as illegal,
3587        but in ICS mode that is not its job anyway.
3588     */
3589     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3590     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3591
3592         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593             if(board[0][i] == WhiteRook) j = i;
3594         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596             if(board[0][i] == WhiteRook) j = i;
3597         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3599             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3600         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3601         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3602             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3603         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3604
3605         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3606         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3607             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3608         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3609             if(board[BOARD_HEIGHT-1][k] == bKing)
3610                 initialRights[5] = castlingRights[moveNum][5] = k;
3611     } else { int r;
3612         r = castlingRights[moveNum][0] = initialRights[0];
3613         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3614         r = castlingRights[moveNum][1] = initialRights[1];
3615         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3616         r = castlingRights[moveNum][3] = initialRights[3];
3617         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3618         r = castlingRights[moveNum][4] = initialRights[4];
3619         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3620         /* wildcastle kludge: always assume King has rights */
3621         r = castlingRights[moveNum][2] = initialRights[2];
3622         r = castlingRights[moveNum][5] = initialRights[5];
3623     }
3624     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3625     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3626
3627     
3628     if (ics_getting_history == H_GOT_REQ_HEADER ||
3629         ics_getting_history == H_GOT_UNREQ_HEADER) {
3630         /* This was an initial position from a move list, not
3631            the current position */
3632         return;
3633     }
3634     
3635     /* Update currentMove and known move number limits */
3636     newMove = newGame || moveNum > forwardMostMove;
3637
3638     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3639     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3640         takeback = forwardMostMove - moveNum;
3641         for (i = 0; i < takeback; i++) {
3642              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3643              SendToProgram("undo\n", &first);
3644         }
3645     }
3646
3647     if (newGame) {
3648         forwardMostMove = backwardMostMove = currentMove = moveNum;
3649         if (gameMode == IcsExamining && moveNum == 0) {
3650           /* Workaround for ICS limitation: we are not told the wild
3651              type when starting to examine a game.  But if we ask for
3652              the move list, the move list header will tell us */
3653             ics_getting_history = H_REQUESTED;
3654             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3655             SendToICS(str);
3656         }
3657     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3658                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3659         forwardMostMove = moveNum;
3660         if (!pausing || currentMove > forwardMostMove)
3661           currentMove = forwardMostMove;
3662     } else {
3663         /* New part of history that is not contiguous with old part */ 
3664         if (pausing && gameMode == IcsExamining) {
3665             pauseExamInvalid = TRUE;
3666             forwardMostMove = pauseExamForwardMostMove;
3667             return;
3668         }
3669         forwardMostMove = backwardMostMove = currentMove = moveNum;
3670         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3671             ics_getting_history = H_REQUESTED;
3672             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3673             SendToICS(str);
3674         }
3675     }
3676     
3677     /* Update the clocks */
3678     if (strchr(elapsed_time, '.')) {
3679       /* Time is in ms */
3680       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3681       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3682     } else {
3683       /* Time is in seconds */
3684       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3685       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3686     }
3687       
3688
3689 #if ZIPPY
3690     if (appData.zippyPlay && newGame &&
3691         gameMode != IcsObserving && gameMode != IcsIdle &&
3692         gameMode != IcsExamining)
3693       ZippyFirstBoard(moveNum, basetime, increment);
3694 #endif
3695     
3696     /* Put the move on the move list, first converting
3697        to canonical algebraic form. */
3698     if (moveNum > 0) {
3699   if (appData.debugMode) {
3700     if (appData.debugMode) { int f = forwardMostMove;
3701         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3702                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3703     }
3704     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3705     fprintf(debugFP, "moveNum = %d\n", moveNum);
3706     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3707     setbuf(debugFP, NULL);
3708   }
3709         if (moveNum <= backwardMostMove) {
3710             /* We don't know what the board looked like before
3711                this move.  Punt. */
3712             strcpy(parseList[moveNum - 1], move_str);
3713             strcat(parseList[moveNum - 1], " ");
3714             strcat(parseList[moveNum - 1], elapsed_time);
3715             moveList[moveNum - 1][0] = NULLCHAR;
3716         } else if (strcmp(move_str, "none") == 0) {
3717             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3718             /* Again, we don't know what the board looked like;
3719                this is really the start of the game. */
3720             parseList[moveNum - 1][0] = NULLCHAR;
3721             moveList[moveNum - 1][0] = NULLCHAR;
3722             backwardMostMove = moveNum;
3723             startedFromSetupPosition = TRUE;
3724             fromX = fromY = toX = toY = -1;
3725         } else {
3726           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3727           //                 So we parse the long-algebraic move string in stead of the SAN move
3728           int valid; char buf[MSG_SIZ], *prom;
3729
3730           // str looks something like "Q/a1-a2"; kill the slash
3731           if(str[1] == '/') 
3732                 sprintf(buf, "%c%s", str[0], str+2);
3733           else  strcpy(buf, str); // might be castling
3734           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3735                 strcat(buf, prom); // long move lacks promo specification!
3736           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3737                 if(appData.debugMode) 
3738                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3739                 strcpy(move_str, buf);
3740           }
3741           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3742                                 &fromX, &fromY, &toX, &toY, &promoChar)
3743                || ParseOneMove(buf, moveNum - 1, &moveType,
3744                                 &fromX, &fromY, &toX, &toY, &promoChar);
3745           // end of long SAN patch
3746           if (valid) {
3747             (void) CoordsToAlgebraic(boards[moveNum - 1],
3748                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3749                                      fromY, fromX, toY, toX, promoChar,
3750                                      parseList[moveNum-1]);
3751             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3752                              castlingRights[moveNum]) ) {
3753               case MT_NONE:
3754               case MT_STALEMATE:
3755               default:
3756                 break;
3757               case MT_CHECK:
3758                 if(gameInfo.variant != VariantShogi)
3759                     strcat(parseList[moveNum - 1], "+");
3760                 break;
3761               case MT_CHECKMATE:
3762               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3763                 strcat(parseList[moveNum - 1], "#");
3764                 break;
3765             }
3766             strcat(parseList[moveNum - 1], " ");
3767             strcat(parseList[moveNum - 1], elapsed_time);
3768             /* currentMoveString is set as a side-effect of ParseOneMove */
3769             strcpy(moveList[moveNum - 1], currentMoveString);
3770             strcat(moveList[moveNum - 1], "\n");
3771           } else {
3772             /* Move from ICS was illegal!?  Punt. */
3773   if (appData.debugMode) {
3774     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3775     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3776   }
3777 #if 0
3778             if (appData.testLegality && appData.debugMode) {
3779                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3780                 DisplayError(str, 0);
3781             }
3782 #endif
3783             strcpy(parseList[moveNum - 1], move_str);
3784             strcat(parseList[moveNum - 1], " ");
3785             strcat(parseList[moveNum - 1], elapsed_time);
3786             moveList[moveNum - 1][0] = NULLCHAR;
3787             fromX = fromY = toX = toY = -1;
3788           }
3789         }
3790   if (appData.debugMode) {
3791     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3792     setbuf(debugFP, NULL);
3793   }
3794
3795 #if ZIPPY
3796         /* Send move to chess program (BEFORE animating it). */
3797         if (appData.zippyPlay && !newGame && newMove && 
3798            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3799
3800             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3801                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3802                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3803                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3804                             move_str);
3805                     DisplayError(str, 0);
3806                 } else {
3807                     if (first.sendTime) {
3808                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3809                     }
3810                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3811                     if (firstMove && !bookHit) {
3812                         firstMove = FALSE;
3813                         if (first.useColors) {
3814                           SendToProgram(gameMode == IcsPlayingWhite ?
3815                                         "white\ngo\n" :
3816                                         "black\ngo\n", &first);
3817                         } else {
3818                           SendToProgram("go\n", &first);
3819                         }
3820                         first.maybeThinking = TRUE;
3821                     }
3822                 }
3823             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3824               if (moveList[moveNum - 1][0] == NULLCHAR) {
3825                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3826                 DisplayError(str, 0);
3827               } else {
3828                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3829                 SendMoveToProgram(moveNum - 1, &first);
3830               }
3831             }
3832         }
3833 #endif
3834     }
3835
3836     if (moveNum > 0 && !gotPremove) {
3837         /* If move comes from a remote source, animate it.  If it
3838            isn't remote, it will have already been animated. */
3839         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3840             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3841         }
3842         if (!pausing && appData.highlightLastMove) {
3843             SetHighlights(fromX, fromY, toX, toY);
3844         }
3845     }
3846     
3847     /* Start the clocks */
3848     whiteFlag = blackFlag = FALSE;
3849     appData.clockMode = !(basetime == 0 && increment == 0);
3850     if (ticking == 0) {
3851       ics_clock_paused = TRUE;
3852       StopClocks();
3853     } else if (ticking == 1) {
3854       ics_clock_paused = FALSE;
3855     }
3856     if (gameMode == IcsIdle ||
3857         relation == RELATION_OBSERVING_STATIC ||
3858         relation == RELATION_EXAMINING ||
3859         ics_clock_paused)
3860       DisplayBothClocks();
3861     else
3862       StartClocks();
3863     
3864     /* Display opponents and material strengths */
3865     if (gameInfo.variant != VariantBughouse &&
3866         gameInfo.variant != VariantCrazyhouse) {
3867         if (tinyLayout || smallLayout) {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, (int) gameInfo.variant);
3876         } else {
3877             if(gameInfo.variant == VariantNormal)
3878                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3879                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3880                     basetime, increment);
3881             else
3882                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3883                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3884                     basetime, increment, VariantName(gameInfo.variant));
3885         }
3886         DisplayTitle(str);
3887   if (appData.debugMode) {
3888     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3889   }
3890     }
3891
3892    
3893     /* Display the board */
3894     if (!pausing) {
3895       
3896       if (appData.premove)
3897           if (!gotPremove || 
3898              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3899              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3900               ClearPremoveHighlights();
3901
3902       DrawPosition(FALSE, boards[currentMove]);
3903       DisplayMove(moveNum - 1);
3904       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3905             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3906               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3907         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3908       }
3909     }
3910
3911     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3912 #if ZIPPY
3913     if(bookHit) { // [HGM] book: simulate book reply
3914         static char bookMove[MSG_SIZ]; // a bit generous?
3915
3916         programStats.nodes = programStats.depth = programStats.time = 
3917         programStats.score = programStats.got_only_move = 0;
3918         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3919
3920         strcpy(bookMove, "move ");
3921         strcat(bookMove, bookHit);
3922         HandleMachineMove(bookMove, &first);
3923     }
3924 #endif
3925 }
3926
3927 void
3928 GetMoveListEvent()
3929 {
3930     char buf[MSG_SIZ];
3931     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3932         ics_getting_history = H_REQUESTED;
3933         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3934         SendToICS(buf);
3935     }
3936 }
3937
3938 void
3939 AnalysisPeriodicEvent(force)
3940      int force;
3941 {
3942     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3943          && !force) || !appData.periodicUpdates)
3944       return;
3945
3946     /* Send . command to Crafty to collect stats */
3947     SendToProgram(".\n", &first);
3948
3949     /* Don't send another until we get a response (this makes
3950        us stop sending to old Crafty's which don't understand
3951        the "." command (sending illegal cmds resets node count & time,
3952        which looks bad)) */
3953     programStats.ok_to_send = 0;
3954 }
3955
3956 void
3957 SendMoveToProgram(moveNum, cps)
3958      int moveNum;
3959      ChessProgramState *cps;
3960 {
3961     char buf[MSG_SIZ];
3962
3963     if (cps->useUsermove) {
3964       SendToProgram("usermove ", cps);
3965     }
3966     if (cps->useSAN) {
3967       char *space;
3968       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3969         int len = space - parseList[moveNum];
3970         memcpy(buf, parseList[moveNum], len);
3971         buf[len++] = '\n';
3972         buf[len] = NULLCHAR;
3973       } else {
3974         sprintf(buf, "%s\n", parseList[moveNum]);
3975       }
3976       SendToProgram(buf, cps);
3977     } else {
3978       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3979         AlphaRank(moveList[moveNum], 4);
3980         SendToProgram(moveList[moveNum], cps);
3981         AlphaRank(moveList[moveNum], 4); // and back
3982       } else
3983       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3984        * the engine. It would be nice to have a better way to identify castle 
3985        * moves here. */
3986       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3987                                                                          && cps->useOOCastle) {
3988         int fromX = moveList[moveNum][0] - AAA; 
3989         int fromY = moveList[moveNum][1] - ONE;
3990         int toX = moveList[moveNum][2] - AAA; 
3991         int toY = moveList[moveNum][3] - ONE;
3992         if((boards[moveNum][fromY][fromX] == WhiteKing 
3993             && boards[moveNum][toY][toX] == WhiteRook)
3994            || (boards[moveNum][fromY][fromX] == BlackKing 
3995                && boards[moveNum][toY][toX] == BlackRook)) {
3996           if(toX > fromX) SendToProgram("O-O\n", cps);
3997           else SendToProgram("O-O-O\n", cps);
3998         }
3999         else SendToProgram(moveList[moveNum], cps);
4000       }
4001       else SendToProgram(moveList[moveNum], cps);
4002       /* End of additions by Tord */
4003     }
4004
4005     /* [HGM] setting up the opening has brought engine in force mode! */
4006     /*       Send 'go' if we are in a mode where machine should play. */
4007     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4008         (gameMode == TwoMachinesPlay   ||
4009 #ifdef ZIPPY
4010          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4011 #endif
4012          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4013         SendToProgram("go\n", cps);
4014   if (appData.debugMode) {
4015     fprintf(debugFP, "(extra)\n");
4016   }
4017     }
4018     setboardSpoiledMachineBlack = 0;
4019 }
4020
4021 void
4022 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4023      ChessMove moveType;
4024      int fromX, fromY, toX, toY;
4025 {
4026     char user_move[MSG_SIZ];
4027
4028     switch (moveType) {
4029       default:
4030         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4031                 (int)moveType, fromX, fromY, toX, toY);
4032         DisplayError(user_move + strlen("say "), 0);
4033         break;
4034       case WhiteKingSideCastle:
4035       case BlackKingSideCastle:
4036       case WhiteQueenSideCastleWild:
4037       case BlackQueenSideCastleWild:
4038       /* PUSH Fabien */
4039       case WhiteHSideCastleFR:
4040       case BlackHSideCastleFR:
4041       /* POP Fabien */
4042         sprintf(user_move, "o-o\n");
4043         break;
4044       case WhiteQueenSideCastle:
4045       case BlackQueenSideCastle:
4046       case WhiteKingSideCastleWild:
4047       case BlackKingSideCastleWild:
4048       /* PUSH Fabien */
4049       case WhiteASideCastleFR:
4050       case BlackASideCastleFR:
4051       /* POP Fabien */
4052         sprintf(user_move, "o-o-o\n");
4053         break;
4054       case WhitePromotionQueen:
4055       case BlackPromotionQueen:
4056       case WhitePromotionRook:
4057       case BlackPromotionRook:
4058       case WhitePromotionBishop:
4059       case BlackPromotionBishop:
4060       case WhitePromotionKnight:
4061       case BlackPromotionKnight:
4062       case WhitePromotionKing:
4063       case BlackPromotionKing:
4064       case WhitePromotionChancellor:
4065       case BlackPromotionChancellor:
4066       case WhitePromotionArchbishop:
4067       case BlackPromotionArchbishop:
4068         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4069             sprintf(user_move, "%c%c%c%c=%c\n",
4070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071                 PieceToChar(WhiteFerz));
4072         else if(gameInfo.variant == VariantGreat)
4073             sprintf(user_move, "%c%c%c%c=%c\n",
4074                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4075                 PieceToChar(WhiteMan));
4076         else
4077             sprintf(user_move, "%c%c%c%c=%c\n",
4078                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4079                 PieceToChar(PromoPiece(moveType)));
4080         break;
4081       case WhiteDrop:
4082       case BlackDrop:
4083         sprintf(user_move, "%c@%c%c\n",
4084                 ToUpper(PieceToChar((ChessSquare) fromX)),
4085                 AAA + toX, ONE + toY);
4086         break;
4087       case NormalMove:
4088       case WhiteCapturesEnPassant:
4089       case BlackCapturesEnPassant:
4090       case IllegalMove:  /* could be a variant we don't quite understand */
4091         sprintf(user_move, "%c%c%c%c\n",
4092                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4093         break;
4094     }
4095     SendToICS(user_move);
4096 }
4097
4098 void
4099 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4100      int rf, ff, rt, ft;
4101      char promoChar;
4102      char move[7];
4103 {
4104     if (rf == DROP_RANK) {
4105         sprintf(move, "%c@%c%c\n",
4106                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4107     } else {
4108         if (promoChar == 'x' || promoChar == NULLCHAR) {
4109             sprintf(move, "%c%c%c%c\n",
4110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4111         } else {
4112             sprintf(move, "%c%c%c%c%c\n",
4113                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4114         }
4115     }
4116 }
4117
4118 void
4119 ProcessICSInitScript(f)
4120      FILE *f;
4121 {
4122     char buf[MSG_SIZ];
4123
4124     while (fgets(buf, MSG_SIZ, f)) {
4125         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4126     }
4127
4128     fclose(f);
4129 }
4130
4131
4132 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4133 void
4134 AlphaRank(char *move, int n)
4135 {
4136 //    char *p = move, c; int x, y;
4137
4138     if (appData.debugMode) {
4139         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4140     }
4141
4142     if(move[1]=='*' && 
4143        move[2]>='0' && move[2]<='9' &&
4144        move[3]>='a' && move[3]<='x'    ) {
4145         move[1] = '@';
4146         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4147         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4148     } else
4149     if(move[0]>='0' && move[0]<='9' &&
4150        move[1]>='a' && move[1]<='x' &&
4151        move[2]>='0' && move[2]<='9' &&
4152        move[3]>='a' && move[3]<='x'    ) {
4153         /* input move, Shogi -> normal */
4154         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4155         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4156         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4157         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4158     } else
4159     if(move[1]=='@' &&
4160        move[3]>='0' && move[3]<='9' &&
4161        move[2]>='a' && move[2]<='x'    ) {
4162         move[1] = '*';
4163         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4164         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4165     } else
4166     if(
4167        move[0]>='a' && move[0]<='x' &&
4168        move[3]>='0' && move[3]<='9' &&
4169        move[2]>='a' && move[2]<='x'    ) {
4170          /* output move, normal -> Shogi */
4171         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4172         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4173         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4174         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4175         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4176     }
4177     if (appData.debugMode) {
4178         fprintf(debugFP, "   out = '%s'\n", move);
4179     }
4180 }
4181
4182 /* Parser for moves from gnuchess, ICS, or user typein box */
4183 Boolean
4184 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4185      char *move;
4186      int moveNum;
4187      ChessMove *moveType;
4188      int *fromX, *fromY, *toX, *toY;
4189      char *promoChar;
4190 {       
4191     if (appData.debugMode) {
4192         fprintf(debugFP, "move to parse: %s\n", move);
4193     }
4194     *moveType = yylexstr(moveNum, move);
4195
4196     switch (*moveType) {
4197       case WhitePromotionChancellor:
4198       case BlackPromotionChancellor:
4199       case WhitePromotionArchbishop:
4200       case BlackPromotionArchbishop:
4201       case WhitePromotionQueen:
4202       case BlackPromotionQueen:
4203       case WhitePromotionRook:
4204       case BlackPromotionRook:
4205       case WhitePromotionBishop:
4206       case BlackPromotionBishop:
4207       case WhitePromotionKnight:
4208       case BlackPromotionKnight:
4209       case WhitePromotionKing:
4210       case BlackPromotionKing:
4211       case NormalMove:
4212       case WhiteCapturesEnPassant:
4213       case BlackCapturesEnPassant:
4214       case WhiteKingSideCastle:
4215       case WhiteQueenSideCastle:
4216       case BlackKingSideCastle:
4217       case BlackQueenSideCastle:
4218       case WhiteKingSideCastleWild:
4219       case WhiteQueenSideCastleWild:
4220       case BlackKingSideCastleWild:
4221       case BlackQueenSideCastleWild:
4222       /* Code added by Tord: */
4223       case WhiteHSideCastleFR:
4224       case WhiteASideCastleFR:
4225       case BlackHSideCastleFR:
4226       case BlackASideCastleFR:
4227       /* End of code added by Tord */
4228       case IllegalMove:         /* bug or odd chess variant */
4229         *fromX = currentMoveString[0] - AAA;
4230         *fromY = currentMoveString[1] - ONE;
4231         *toX = currentMoveString[2] - AAA;
4232         *toY = currentMoveString[3] - ONE;
4233         *promoChar = currentMoveString[4];
4234         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4235             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4236     if (appData.debugMode) {
4237         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4238     }
4239             *fromX = *fromY = *toX = *toY = 0;
4240             return FALSE;
4241         }
4242         if (appData.testLegality) {
4243           return (*moveType != IllegalMove);
4244         } else {
4245           return !(fromX == fromY && toX == toY);
4246         }
4247
4248       case WhiteDrop:
4249       case BlackDrop:
4250         *fromX = *moveType == WhiteDrop ?
4251           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4252           (int) CharToPiece(ToLower(currentMoveString[0]));
4253         *fromY = DROP_RANK;
4254         *toX = currentMoveString[2] - AAA;
4255         *toY = currentMoveString[3] - ONE;
4256         *promoChar = NULLCHAR;
4257         return TRUE;
4258
4259       case AmbiguousMove:
4260       case ImpossibleMove:
4261       case (ChessMove) 0:       /* end of file */
4262       case ElapsedTime:
4263       case Comment:
4264       case PGNTag:
4265       case NAG:
4266       case WhiteWins:
4267       case BlackWins:
4268       case GameIsDrawn:
4269       default:
4270     if (appData.debugMode) {
4271         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4272     }
4273         /* bug? */
4274         *fromX = *fromY = *toX = *toY = 0;
4275         *promoChar = NULLCHAR;
4276         return FALSE;
4277     }
4278 }
4279
4280 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4281 // All positions will have equal probability, but the current method will not provide a unique
4282 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4283 #define DARK 1
4284 #define LITE 2
4285 #define ANY 3
4286
4287 int squaresLeft[4];
4288 int piecesLeft[(int)BlackPawn];
4289 int seed, nrOfShuffles;
4290
4291 void GetPositionNumber()
4292 {       // sets global variable seed
4293         int i;
4294
4295         seed = appData.defaultFrcPosition;
4296         if(seed < 0) { // randomize based on time for negative FRC position numbers
4297                 for(i=0; i<50; i++) seed += random();
4298                 seed = random() ^ random() >> 8 ^ random() << 8;
4299                 if(seed<0) seed = -seed;
4300         }
4301 }
4302
4303 int put(Board board, int pieceType, int rank, int n, int shade)
4304 // put the piece on the (n-1)-th empty squares of the given shade
4305 {
4306         int i;
4307
4308         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4309                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4310                         board[rank][i] = (ChessSquare) pieceType;
4311                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4312                         squaresLeft[ANY]--;
4313                         piecesLeft[pieceType]--; 
4314                         return i;
4315                 }
4316         }
4317         return -1;
4318 }
4319
4320
4321 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4322 // calculate where the next piece goes, (any empty square), and put it there
4323 {
4324         int i;
4325
4326         i = seed % squaresLeft[shade];
4327         nrOfShuffles *= squaresLeft[shade];
4328         seed /= squaresLeft[shade];
4329         put(board, pieceType, rank, i, shade);
4330 }
4331
4332 void AddTwoPieces(Board board, int pieceType, int rank)
4333 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4334 {
4335         int i, n=squaresLeft[ANY], j=n-1, k;
4336
4337         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4338         i = seed % k;  // pick one
4339         nrOfShuffles *= k;
4340         seed /= k;
4341         while(i >= j) i -= j--;
4342         j = n - 1 - j; i += j;
4343         put(board, pieceType, rank, j, ANY);
4344         put(board, pieceType, rank, i, ANY);
4345 }
4346
4347 void SetUpShuffle(Board board, int number)
4348 {
4349         int i, p, first=1;
4350
4351         GetPositionNumber(); nrOfShuffles = 1;
4352
4353         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4354         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4355         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4356
4357         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4358
4359         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4360             p = (int) board[0][i];
4361             if(p < (int) BlackPawn) piecesLeft[p] ++;
4362             board[0][i] = EmptySquare;
4363         }
4364
4365         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4366             // shuffles restricted to allow normal castling put KRR first
4367             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4368                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4369             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4370                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4371             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4372                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4373             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4374                 put(board, WhiteRook, 0, 0, ANY);
4375             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4376         }
4377
4378         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4379             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4380             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4381                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4382                 while(piecesLeft[p] >= 2) {
4383                     AddOnePiece(board, p, 0, LITE);
4384                     AddOnePiece(board, p, 0, DARK);
4385                 }
4386                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4387             }
4388
4389         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4390             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4391             // but we leave King and Rooks for last, to possibly obey FRC restriction
4392             if(p == (int)WhiteRook) continue;
4393             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4394             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4395         }
4396
4397         // now everything is placed, except perhaps King (Unicorn) and Rooks
4398
4399         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4400             // Last King gets castling rights
4401             while(piecesLeft[(int)WhiteUnicorn]) {
4402                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4403                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4404             }
4405
4406             while(piecesLeft[(int)WhiteKing]) {
4407                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4408                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4409             }
4410
4411
4412         } else {
4413             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4414             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4415         }
4416
4417         // Only Rooks can be left; simply place them all
4418         while(piecesLeft[(int)WhiteRook]) {
4419                 i = put(board, WhiteRook, 0, 0, ANY);
4420                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4421                         if(first) {
4422                                 first=0;
4423                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4424                         }
4425                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4426                 }
4427         }
4428         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4429             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4430         }
4431
4432         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4433 }
4434
4435 int SetCharTable( char *table, const char * map )
4436 /* [HGM] moved here from winboard.c because of its general usefulness */
4437 /*       Basically a safe strcpy that uses the last character as King */
4438 {
4439     int result = FALSE; int NrPieces;
4440
4441     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4442                     && NrPieces >= 12 && !(NrPieces&1)) {
4443         int i; /* [HGM] Accept even length from 12 to 34 */
4444
4445         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4446         for( i=0; i<NrPieces/2-1; i++ ) {
4447             table[i] = map[i];
4448             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4449         }
4450         table[(int) WhiteKing]  = map[NrPieces/2-1];
4451         table[(int) BlackKing]  = map[NrPieces-1];
4452
4453         result = TRUE;
4454     }
4455
4456     return result;
4457 }
4458
4459 void Prelude(Board board)
4460 {       // [HGM] superchess: random selection of exo-pieces
4461         int i, j, k; ChessSquare p; 
4462         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4463
4464         GetPositionNumber(); // use FRC position number
4465
4466         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4467             SetCharTable(pieceToChar, appData.pieceToCharTable);
4468             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4469                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4470         }
4471
4472         j = seed%4;                 seed /= 4; 
4473         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3 + (seed%3 >= j); seed /= 3; 
4477         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3; 
4481         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4482         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4483         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4484         j = seed%2 + (seed%2 >= j); seed /= 2; 
4485         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4486         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4487         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4488         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4489         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4490         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4491         put(board, exoPieces[0],    0, 0, ANY);
4492         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4493 }
4494
4495 void
4496 InitPosition(redraw)
4497      int redraw;
4498 {
4499     ChessSquare (* pieces)[BOARD_SIZE];
4500     int i, j, pawnRow, overrule,
4501     oldx = gameInfo.boardWidth,
4502     oldy = gameInfo.boardHeight,
4503     oldh = gameInfo.holdingsWidth,
4504     oldv = gameInfo.variant;
4505
4506     printf ("DEBUG: in init position\n");
4507
4508     currentMove = forwardMostMove = backwardMostMove = 0;
4509     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4510
4511     /* [AS] Initialize pv info list [HGM] and game status */
4512     {
4513         for( i=0; i<MAX_MOVES; i++ ) {
4514             pvInfoList[i].depth = 0;
4515             epStatus[i]=EP_NONE;
4516             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4517         }
4518
4519         initialRulePlies = 0; /* 50-move counter start */
4520
4521         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4522         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4523     }
4524
4525     
4526     /* [HGM] logic here is completely changed. In stead of full positions */
4527     /* the initialized data only consist of the two backranks. The switch */
4528     /* selects which one we will use, which is than copied to the Board   */
4529     /* initialPosition, which for the rest is initialized by Pawns and    */
4530     /* empty squares. This initial position is then copied to boards[0],  */
4531     /* possibly after shuffling, so that it remains available.            */
4532
4533     gameInfo.holdingsWidth = 0; /* default board sizes */
4534     gameInfo.boardWidth    = 8;
4535     gameInfo.boardHeight   = 8;
4536     gameInfo.holdingsSize  = 0;
4537     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4538     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4539     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4540
4541     switch (gameInfo.variant) {
4542     case VariantFischeRandom:
4543       shuffleOpenings = TRUE;
4544     default:
4545       pieces = FIDEArray;
4546       break;
4547     case VariantShatranj:
4548       pieces = ShatranjArray;
4549       nrCastlingRights = 0;
4550       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4551       break;
4552     case VariantTwoKings:
4553       pieces = twoKingsArray;
4554       break;
4555     case VariantCapaRandom:
4556       shuffleOpenings = TRUE;
4557     case VariantCapablanca:
4558       pieces = CapablancaArray;
4559       gameInfo.boardWidth = 10;
4560       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4561       break;
4562     case VariantGothic:
4563       pieces = GothicArray;
4564       gameInfo.boardWidth = 10;
4565       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4566       break;
4567     case VariantJanus:
4568       pieces = JanusArray;
4569       gameInfo.boardWidth = 10;
4570       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4571       nrCastlingRights = 6;
4572         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4573         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4574         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4575         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4576         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4577         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4578       break;
4579     case VariantFalcon:
4580       pieces = FalconArray;
4581       gameInfo.boardWidth = 10;
4582       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4583       break;
4584     case VariantXiangqi:
4585       pieces = XiangqiArray;
4586       gameInfo.boardWidth  = 9;
4587       gameInfo.boardHeight = 10;
4588       nrCastlingRights = 0;
4589       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4590       break;
4591     case VariantShogi:
4592       pieces = ShogiArray;
4593       gameInfo.boardWidth  = 9;
4594       gameInfo.boardHeight = 9;
4595       gameInfo.holdingsSize = 7;
4596       nrCastlingRights = 0;
4597       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4598       break;
4599     case VariantCourier:
4600       pieces = CourierArray;
4601       gameInfo.boardWidth  = 12;
4602       nrCastlingRights = 0;
4603       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4604       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4605       break;
4606     case VariantKnightmate:
4607       pieces = KnightmateArray;
4608       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4609       break;
4610     case VariantFairy:
4611       pieces = fairyArray;
4612       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4613       break;
4614     case VariantGreat:
4615       pieces = GreatArray;
4616       gameInfo.boardWidth = 10;
4617       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4618       gameInfo.holdingsSize = 8;
4619       break;
4620     case VariantSuper:
4621       pieces = FIDEArray;
4622       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4623       gameInfo.holdingsSize = 8;
4624       startedFromSetupPosition = TRUE;
4625       break;
4626     case VariantCrazyhouse:
4627     case VariantBughouse:
4628       pieces = FIDEArray;
4629       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4630       gameInfo.holdingsSize = 5;
4631       break;
4632     case VariantWildCastle:
4633       pieces = FIDEArray;
4634       /* !!?shuffle with kings guaranteed to be on d or e file */
4635       shuffleOpenings = 1;
4636       break;
4637     case VariantNoCastle:
4638       pieces = FIDEArray;
4639       nrCastlingRights = 0;
4640       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4641       /* !!?unconstrained back-rank shuffle */
4642       shuffleOpenings = 1;
4643       break;
4644     }
4645     printf ("DEBUG: in init position 1\n");
4646
4647     overrule = 0;
4648     if(appData.NrFiles >= 0) {
4649         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4650         gameInfo.boardWidth = appData.NrFiles;
4651     }
4652     if(appData.NrRanks >= 0) {
4653         gameInfo.boardHeight = appData.NrRanks;
4654     }
4655     if(appData.holdingsSize >= 0) {
4656         i = appData.holdingsSize;
4657         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4658         gameInfo.holdingsSize = i;
4659     }
4660     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4661     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4662         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4663
4664     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4665     if(pawnRow < 1) pawnRow = 1;
4666
4667     /* User pieceToChar list overrules defaults */
4668     if(appData.pieceToCharTable != NULL)
4669         SetCharTable(pieceToChar, appData.pieceToCharTable);
4670
4671     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4672
4673         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4674             s = (ChessSquare) 0; /* account holding counts in guard band */
4675         for( i=0; i<BOARD_HEIGHT; i++ )
4676             initialPosition[i][j] = s;
4677
4678         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4679         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4680         initialPosition[pawnRow][j] = WhitePawn;
4681         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4682         if(gameInfo.variant == VariantXiangqi) {
4683             if(j&1) {
4684                 initialPosition[pawnRow][j] = 
4685                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4686                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4687                    initialPosition[2][j] = WhiteCannon;
4688                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4689                 }
4690             }
4691         }
4692         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4693     }
4694     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4695
4696             j=BOARD_LEFT+1;
4697             initialPosition[1][j] = WhiteBishop;
4698             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4699             j=BOARD_RGHT-2;
4700             initialPosition[1][j] = WhiteRook;
4701             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4702     }
4703
4704     printf ("DEBUG: in init position 2\n");
4705
4706
4707     if( nrCastlingRights == -1) {
4708         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4709         /*       This sets default castling rights from none to normal corners   */
4710         /* Variants with other castling rights must set them themselves above    */
4711         nrCastlingRights = 6;
4712        
4713         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4714         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4715         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4716         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4717         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4718         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4719      }
4720
4721      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4722      if(gameInfo.variant == VariantGreat) { // promotion commoners
4723         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4724         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4725         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4726         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4727      }
4728 #if 0
4729     if(gameInfo.variant == VariantFischeRandom) {
4730       if( appData.defaultFrcPosition < 0 ) {
4731         ShuffleFRC( initialPosition );
4732       }
4733       else {
4734         SetupFRC( initialPosition, appData.defaultFrcPosition );
4735       }
4736       startedFromSetupPosition = TRUE;
4737     } else 
4738 #else
4739   if (appData.debugMode) {
4740     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4741   }
4742     if(shuffleOpenings) {
4743         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4744         startedFromSetupPosition = TRUE;
4745     }
4746 #endif
4747     if(startedFromPositionFile) {
4748       /* [HGM] loadPos: use PositionFile for every new game */
4749       CopyBoard(initialPosition, filePosition);
4750       for(i=0; i<nrCastlingRights; i++)
4751           castlingRights[0][i] = initialRights[i] = fileRights[i];
4752       startedFromSetupPosition = TRUE;
4753     }
4754
4755     printf ("DEBUG: in init position 3\n");
4756         
4757
4758     CopyBoard(boards[0], initialPosition);
4759     printf ("DEBUG: in init position 3.1\n");
4760     if(oldx != gameInfo.boardWidth ||
4761        oldy != gameInfo.boardHeight ||
4762        oldh != gameInfo.holdingsWidth
4763 #ifdef GOTHIC
4764        || oldv == VariantGothic ||        // For licensing popups
4765        gameInfo.variant == VariantGothic
4766 #endif
4767 #ifdef FALCON
4768        || oldv == VariantFalcon ||
4769        gameInfo.variant == VariantFalcon
4770 #endif
4771                                          )
4772       {
4773             printf ("DEBUG: in init position 3.2\n");
4774             InitDrawingSizes(-2 ,0);
4775       }
4776     printf ("DEBUG: init position 99\n");
4777
4778     if (redraw)
4779       DrawPosition(TRUE, boards[currentMove]);
4780
4781     printf ("DEBUG: end init position\n");
4782 }
4783
4784 void
4785 SendBoard(cps, moveNum)
4786      ChessProgramState *cps;
4787      int moveNum;
4788 {
4789     char message[MSG_SIZ];
4790     
4791     if (cps->useSetboard) {
4792       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4793       sprintf(message, "setboard %s\n", fen);
4794       SendToProgram(message, cps);
4795       free(fen);
4796
4797     } else {
4798       ChessSquare *bp;
4799       int i, j;
4800       /* Kludge to set black to move, avoiding the troublesome and now
4801        * deprecated "black" command.
4802        */
4803       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4804
4805       SendToProgram("edit\n", cps);
4806       SendToProgram("#\n", cps);
4807       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4808         bp = &boards[moveNum][i][BOARD_LEFT];
4809         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4810           if ((int) *bp < (int) BlackPawn) {
4811             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826     
4827       SendToProgram("c\n", cps);
4828       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4829         bp = &boards[moveNum][i][BOARD_LEFT];
4830         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4831           if (((int) *bp != (int) EmptySquare)
4832               && ((int) *bp >= (int) BlackPawn)) {
4833             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4834                     AAA + j, ONE + i);
4835             if(message[0] == '+' || message[0] == '~') {
4836                 sprintf(message, "%c%c%c+\n",
4837                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4838                         AAA + j, ONE + i);
4839             }
4840             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4841                 message[1] = BOARD_RGHT   - 1 - j + '1';
4842                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4843             }
4844             SendToProgram(message, cps);
4845           }
4846         }
4847       }
4848     
4849       SendToProgram(".\n", cps);
4850     }
4851     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4852 }
4853
4854 int
4855 IsPromotion(fromX, fromY, toX, toY)
4856      int fromX, fromY, toX, toY;
4857 {
4858     /* [HGM] add Shogi promotions */
4859     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4860     ChessSquare piece;
4861
4862     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4863       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4864    /* [HGM] Note to self: line above also weeds out drops */
4865     piece = boards[currentMove][fromY][fromX];
4866     if(gameInfo.variant == VariantShogi) {
4867         promotionZoneSize = 3;
4868         highestPromotingPiece = (int)WhiteKing;
4869         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4870            and if in normal chess we then allow promotion to King, why not
4871            allow promotion of other piece in Shogi?                         */
4872     }
4873     if((int)piece >= BlackPawn) {
4874         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4875              return FALSE;
4876         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4877     } else {
4878         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4879            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4880     }
4881     return ( (int)piece <= highestPromotingPiece );
4882 }
4883
4884 int
4885 InPalace(row, column)
4886      int row, column;
4887 {   /* [HGM] for Xiangqi */
4888     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4889          column < (BOARD_WIDTH + 4)/2 &&
4890          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4891     return FALSE;
4892 }
4893
4894 int
4895 PieceForSquare (x, y)
4896      int x;
4897      int y;
4898 {
4899   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4900      return -1;
4901   else
4902      return boards[currentMove][y][x];
4903 }
4904
4905 int
4906 OKToStartUserMove(x, y)
4907      int x, y;
4908 {
4909     ChessSquare from_piece;
4910     int white_piece;
4911
4912     if (matchMode) return FALSE;
4913     if (gameMode == EditPosition) return TRUE;
4914
4915     if (x >= 0 && y >= 0)
4916       from_piece = boards[currentMove][y][x];
4917     else
4918       from_piece = EmptySquare;
4919
4920     if (from_piece == EmptySquare) return FALSE;
4921
4922     white_piece = (int)from_piece >= (int)WhitePawn &&
4923       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4924
4925     switch (gameMode) {
4926       case PlayFromGameFile:
4927       case AnalyzeFile:
4928       case TwoMachinesPlay:
4929       case EndOfGame:
4930         return FALSE;
4931
4932       case IcsObserving:
4933       case IcsIdle:
4934         return FALSE;
4935
4936       case MachinePlaysWhite:
4937       case IcsPlayingBlack:
4938         if (appData.zippyPlay) return FALSE;
4939         if (white_piece) {
4940             DisplayMoveError(_("You are playing Black"));
4941             return FALSE;
4942         }
4943         break;
4944
4945       case MachinePlaysBlack:
4946       case IcsPlayingWhite:
4947         if (appData.zippyPlay) return FALSE;
4948         if (!white_piece) {
4949             DisplayMoveError(_("You are playing White"));
4950             return FALSE;
4951         }
4952         break;
4953
4954       case EditGame:
4955         if (!white_piece && WhiteOnMove(currentMove)) {
4956             DisplayMoveError(_("It is White's turn"));
4957             return FALSE;
4958         }           
4959         if (white_piece && !WhiteOnMove(currentMove)) {
4960             DisplayMoveError(_("It is Black's turn"));
4961             return FALSE;
4962         }           
4963         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4964             /* Editing correspondence game history */
4965             /* Could disallow this or prompt for confirmation */
4966             cmailOldMove = -1;
4967         }
4968         if (currentMove < forwardMostMove) {
4969             /* Discarding moves */
4970             /* Could prompt for confirmation here,
4971                but I don't think that's such a good idea */
4972             forwardMostMove = currentMove;
4973         }
4974         break;
4975
4976       case BeginningOfGame:
4977         if (appData.icsActive) return FALSE;
4978         if (!appData.noChessProgram) {
4979             if (!white_piece) {
4980                 DisplayMoveError(_("You are playing White"));
4981                 return FALSE;
4982             }
4983         }
4984         break;
4985         
4986       case Training:
4987         if (!white_piece && WhiteOnMove(currentMove)) {
4988             DisplayMoveError(_("It is White's turn"));
4989             return FALSE;
4990         }           
4991         if (white_piece && !WhiteOnMove(currentMove)) {
4992             DisplayMoveError(_("It is Black's turn"));
4993             return FALSE;
4994         }           
4995         break;
4996
4997       default:
4998       case IcsExamining:
4999         break;
5000     }
5001     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5002         && gameMode != AnalyzeFile && gameMode != Training) {
5003         DisplayMoveError(_("Displayed position is not current"));
5004         return FALSE;
5005     }
5006     return TRUE;
5007 }
5008
5009 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5010 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5011 int lastLoadGameUseList = FALSE;
5012 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5013 ChessMove lastLoadGameStart = (ChessMove) 0;
5014
5015
5016 ChessMove
5017 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5018      int fromX, fromY, toX, toY;
5019      int promoChar;
5020 {
5021     ChessMove moveType;
5022     ChessSquare pdown, pup;
5023
5024     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5025     if ((fromX == toX) && (fromY == toY)) {
5026         return ImpossibleMove;
5027     }
5028
5029     /* [HGM] suppress all moves into holdings area and guard band */
5030     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5031             return ImpossibleMove;
5032
5033     /* [HGM] <sameColor> moved to here from winboard.c */
5034     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5035     pdown = boards[currentMove][fromY][fromX];
5036     pup = boards[currentMove][toY][toX];
5037     if (    gameMode != EditPosition &&
5038             (WhitePawn <= pdown && pdown < BlackPawn &&
5039              WhitePawn <= pup && pup < BlackPawn  ||
5040              BlackPawn <= pdown && pdown < EmptySquare &&
5041              BlackPawn <= pup && pup < EmptySquare 
5042             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5043                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5044                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5045         )           )
5046          return ImpossibleMove;
5047
5048     /* Check if the user is playing in turn.  This is complicated because we
5049        let the user "pick up" a piece before it is his turn.  So the piece he
5050        tried to pick up may have been captured by the time he puts it down!
5051        Therefore we use the color the user is supposed to be playing in this
5052        test, not the color of the piece that is currently on the starting
5053        square---except in EditGame mode, where the user is playing both
5054        sides; fortunately there the capture race can't happen.  (It can
5055        now happen in IcsExamining mode, but that's just too bad.  The user
5056        will get a somewhat confusing message in that case.)
5057        */
5058
5059     switch (gameMode) {
5060       case PlayFromGameFile:
5061       case AnalyzeFile:
5062       case TwoMachinesPlay:
5063       case EndOfGame:
5064       case IcsObserving:
5065       case IcsIdle:
5066         /* We switched into a game mode where moves are not accepted,
5067            perhaps while the mouse button was down. */
5068         return ImpossibleMove;
5069
5070       case MachinePlaysWhite:
5071         /* User is moving for Black */
5072         if (WhiteOnMove(currentMove)) {
5073             DisplayMoveError(_("It is White's turn"));
5074             return ImpossibleMove;
5075         }
5076         break;
5077
5078       case MachinePlaysBlack:
5079         /* User is moving for White */
5080         if (!WhiteOnMove(currentMove)) {
5081             DisplayMoveError(_("It is Black's turn"));
5082             return ImpossibleMove;
5083         }
5084         break;
5085
5086       case EditGame:
5087       case IcsExamining:
5088       case BeginningOfGame:
5089       case AnalyzeMode:
5090       case Training:
5091         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5092             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5093             /* User is moving for Black */
5094             if (WhiteOnMove(currentMove)) {
5095                 DisplayMoveError(_("It is White's turn"));
5096                 return ImpossibleMove;
5097             }
5098         } else {
5099             /* User is moving for White */
5100             if (!WhiteOnMove(currentMove)) {
5101                 DisplayMoveError(_("It is Black's turn"));
5102                 return ImpossibleMove;
5103             }
5104         }
5105         break;
5106
5107       case IcsPlayingBlack:
5108         /* User is moving for Black */
5109         if (WhiteOnMove(currentMove)) {
5110             if (!appData.premove) {
5111                 DisplayMoveError(_("It is White's turn"));
5112             } else if (toX >= 0 && toY >= 0) {
5113                 premoveToX = toX;
5114                 premoveToY = toY;
5115                 premoveFromX = fromX;
5116                 premoveFromY = fromY;
5117                 premovePromoChar = promoChar;
5118                 gotPremove = 1;
5119                 if (appData.debugMode) 
5120                     fprintf(debugFP, "Got premove: fromX %d,"
5121                             "fromY %d, toX %d, toY %d\n",
5122                             fromX, fromY, toX, toY);
5123             }
5124             return ImpossibleMove;
5125         }
5126         break;
5127
5128       case IcsPlayingWhite:
5129         /* User is moving for White */
5130         if (!WhiteOnMove(currentMove)) {
5131             if (!appData.premove) {
5132                 DisplayMoveError(_("It is Black's turn"));
5133             } else if (toX >= 0 && toY >= 0) {
5134                 premoveToX = toX;
5135                 premoveToY = toY;
5136                 premoveFromX = fromX;
5137                 premoveFromY = fromY;
5138                 premovePromoChar = promoChar;
5139                 gotPremove = 1;
5140                 if (appData.debugMode) 
5141                     fprintf(debugFP, "Got premove: fromX %d,"
5142                             "fromY %d, toX %d, toY %d\n",
5143                             fromX, fromY, toX, toY);
5144             }
5145             return ImpossibleMove;
5146         }
5147         break;
5148
5149       default:
5150         break;
5151
5152       case EditPosition:
5153         /* EditPosition, empty square, or different color piece;
5154            click-click move is possible */
5155         if (toX == -2 || toY == -2) {
5156             boards[0][fromY][fromX] = EmptySquare;
5157             return AmbiguousMove;
5158         } else if (toX >= 0 && toY >= 0) {
5159             boards[0][toY][toX] = boards[0][fromY][fromX];
5160             boards[0][fromY][fromX] = EmptySquare;
5161             return AmbiguousMove;
5162         }
5163         return ImpossibleMove;
5164     }
5165
5166     /* [HGM] If move started in holdings, it means a drop */
5167     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5168          if( pup != EmptySquare ) return ImpossibleMove;
5169          if(appData.testLegality) {
5170              /* it would be more logical if LegalityTest() also figured out
5171               * which drops are legal. For now we forbid pawns on back rank.
5172               * Shogi is on its own here...
5173               */
5174              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5175                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5176                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5177          }
5178          return WhiteDrop; /* Not needed to specify white or black yet */
5179     }
5180
5181     userOfferedDraw = FALSE;
5182         
5183     /* [HGM] always test for legality, to get promotion info */
5184     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5185                           epStatus[currentMove], castlingRights[currentMove],
5186                                          fromY, fromX, toY, toX, promoChar);
5187
5188     /* [HGM] but possibly ignore an IllegalMove result */
5189     if (appData.testLegality) {
5190         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5191             DisplayMoveError(_("Illegal move"));
5192             return ImpossibleMove;
5193         }
5194     }
5195 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5196     return moveType;
5197     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5198        function is made into one that returns an OK move type if FinishMove
5199        should be called. This to give the calling driver routine the
5200        opportunity to finish the userMove input with a promotion popup,
5201        without bothering the user with this for invalid or illegal moves */
5202
5203 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5204 }
5205
5206 /* Common tail of UserMoveEvent and DropMenuEvent */
5207 int
5208 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5209      ChessMove moveType;
5210      int fromX, fromY, toX, toY;
5211      /*char*/int promoChar;
5212 {
5213     char *bookHit = 0;
5214 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5215     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5216         // [HGM] superchess: suppress promotions to non-available piece
5217         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5218         if(WhiteOnMove(currentMove)) {
5219             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5220         } else {
5221             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5222         }
5223     }
5224
5225     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5226        move type in caller when we know the move is a legal promotion */
5227     if(moveType == NormalMove && promoChar)
5228         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5229 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5230     /* [HGM] convert drag-and-drop piece drops to standard form */
5231     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5232          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5233          fromX = boards[currentMove][fromY][fromX];
5234          fromY = DROP_RANK;
5235     }
5236
5237     /* [HGM] <popupFix> The following if has been moved here from
5238        UserMoveEvent(). Because it seemed to belon here (why not allow
5239        piece drops in training games?), and because it can only be
5240        performed after it is known to what we promote. */
5241     if (gameMode == Training) {
5242       /* compare the move played on the board to the next move in the
5243        * game. If they match, display the move and the opponent's response. 
5244        * If they don't match, display an error message.
5245        */
5246       int saveAnimate;
5247       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5248       CopyBoard(testBoard, boards[currentMove]);
5249       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5250
5251       if (CompareBoards(testBoard, boards[currentMove+1])) {
5252         ForwardInner(currentMove+1);
5253
5254         /* Autoplay the opponent's response.
5255          * if appData.animate was TRUE when Training mode was entered,
5256          * the response will be animated.
5257          */
5258         saveAnimate = appData.animate;
5259         appData.animate = animateTraining;
5260         ForwardInner(currentMove+1);
5261         appData.animate = saveAnimate;
5262
5263         /* check for the end of the game */
5264         if (currentMove >= forwardMostMove) {
5265           gameMode = PlayFromGameFile;
5266           ModeHighlight();
5267           SetTrainingModeOff();
5268           DisplayInformation(_("End of game"));
5269         }
5270       } else {
5271         DisplayError(_("Incorrect move"), 0);
5272       }
5273       return 1;
5274     }
5275
5276   /* Ok, now we know that the move is good, so we can kill
5277      the previous line in Analysis Mode */
5278   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5279     forwardMostMove = currentMove;
5280   }
5281
5282   /* If we need the chess program but it's dead, restart it */
5283   ResurrectChessProgram();
5284
5285   /* A user move restarts a paused game*/
5286   if (pausing)
5287     PauseEvent();
5288
5289   thinkOutput[0] = NULLCHAR;
5290
5291   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5292
5293   if (gameMode == BeginningOfGame) {
5294     if (appData.noChessProgram) {
5295       gameMode = EditGame;
5296       SetGameInfo();
5297     } else {
5298       char buf[MSG_SIZ];
5299       gameMode = MachinePlaysBlack;
5300       StartClocks();
5301       SetGameInfo();
5302       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5303       DisplayTitle(buf);
5304       if (first.sendName) {
5305         sprintf(buf, "name %s\n", gameInfo.white);
5306         SendToProgram(buf, &first);
5307       }
5308       StartClocks();
5309     }
5310     ModeHighlight();
5311   }
5312 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5313   /* Relay move to ICS or chess engine */
5314   if (appData.icsActive) {
5315     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5316         gameMode == IcsExamining) {
5317       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5318       ics_user_moved = 1;
5319     }
5320   } else {
5321     if (first.sendTime && (gameMode == BeginningOfGame ||
5322                            gameMode == MachinePlaysWhite ||
5323                            gameMode == MachinePlaysBlack)) {
5324       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5325     }
5326     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5327          // [HGM] book: if program might be playing, let it use book
5328         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5329         first.maybeThinking = TRUE;
5330     } else SendMoveToProgram(forwardMostMove-1, &first);
5331     if (currentMove == cmailOldMove + 1) {
5332       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5333     }
5334   }
5335
5336   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5337
5338   switch (gameMode) {
5339   case EditGame:
5340     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5341                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5342     case MT_NONE:
5343     case MT_CHECK:
5344       break;
5345     case MT_CHECKMATE:
5346     case MT_STAINMATE:
5347       if (WhiteOnMove(currentMove)) {
5348         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5349       } else {
5350         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5351       }
5352       break;
5353     case MT_STALEMATE:
5354       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5355       break;
5356     }
5357     break;
5358     
5359   case MachinePlaysBlack:
5360   case MachinePlaysWhite:
5361     /* disable certain menu options while machine is thinking */
5362     SetMachineThinkingEnables();
5363     break;
5364
5365   default:
5366     break;
5367   }
5368
5369   if(bookHit) { // [HGM] book: simulate book reply
5370         static char bookMove[MSG_SIZ]; // a bit generous?
5371
5372         programStats.nodes = programStats.depth = programStats.time = 
5373         programStats.score = programStats.got_only_move = 0;
5374         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5375
5376         strcpy(bookMove, "move ");
5377         strcat(bookMove, bookHit);
5378         HandleMachineMove(bookMove, &first);
5379   }
5380   return 1;
5381 }
5382
5383 void
5384 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5385      int fromX, fromY, toX, toY;
5386      int promoChar;
5387 {
5388     /* [HGM] This routine was added to allow calling of its two logical
5389        parts from other modules in the old way. Before, UserMoveEvent()
5390        automatically called FinishMove() if the move was OK, and returned
5391        otherwise. I separated the two, in order to make it possible to
5392        slip a promotion popup in between. But that it always needs two
5393        calls, to the first part, (now called UserMoveTest() ), and to
5394        FinishMove if the first part succeeded. Calls that do not need
5395        to do anything in between, can call this routine the old way. 
5396     */
5397     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5398 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5399     if(moveType != ImpossibleMove)
5400         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5401 }
5402
5403 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5404 {
5405 //    char * hint = lastHint;
5406     FrontEndProgramStats stats;
5407
5408     stats.which = cps == &first ? 0 : 1;
5409     stats.depth = cpstats->depth;
5410     stats.nodes = cpstats->nodes;
5411     stats.score = cpstats->score;
5412     stats.time = cpstats->time;
5413     stats.pv = cpstats->movelist;
5414     stats.hint = lastHint;
5415     stats.an_move_index = 0;
5416     stats.an_move_count = 0;
5417
5418     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5419         stats.hint = cpstats->move_name;
5420         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5421         stats.an_move_count = cpstats->nr_moves;
5422     }
5423
5424     SetProgramStats( &stats );
5425 }
5426
5427 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5428 {   // [HGM] book: this routine intercepts moves to simulate book replies
5429     char *bookHit = NULL;
5430
5431     //first determine if the incoming move brings opponent into his book
5432     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5433         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5434     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5435     if(bookHit != NULL && !cps->bookSuspend) {
5436         // make sure opponent is not going to reply after receiving move to book position
5437         SendToProgram("force\n", cps);
5438         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5439     }
5440     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5441     // now arrange restart after book miss
5442     if(bookHit) {
5443         // after a book hit we never send 'go', and the code after the call to this routine
5444         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5445         char buf[MSG_SIZ];
5446         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5447         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5448         SendToProgram(buf, cps);
5449         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5450     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5451         SendToProgram("go\n", cps);
5452         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5453     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5454         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5455             SendToProgram("go\n", cps); 
5456         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5457     }
5458     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5459 }
5460
5461 char *savedMessage;
5462 ChessProgramState *savedState;
5463 void DeferredBookMove(void)
5464 {
5465         if(savedState->lastPing != savedState->lastPong)
5466                     ScheduleDelayedEvent(DeferredBookMove, 10);
5467         else
5468         HandleMachineMove(savedMessage, savedState);
5469 }
5470
5471 void
5472 HandleMachineMove(message, cps)
5473      char *message;
5474      ChessProgramState *cps;
5475 {
5476     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5477     char realname[MSG_SIZ];
5478     int fromX, fromY, toX, toY;
5479     ChessMove moveType;
5480     char promoChar;
5481     char *p;
5482     int machineWhite;
5483     char *bookHit;
5484
5485 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5486     /*
5487      * Kludge to ignore BEL characters
5488      */
5489     while (*message == '\007') message++;
5490
5491     /*
5492      * [HGM] engine debug message: ignore lines starting with '#' character
5493      */
5494     if(cps->debug && *message == '#') return;
5495
5496     /*
5497      * Look for book output
5498      */
5499     if (cps == &first && bookRequested) {
5500         if (message[0] == '\t' || message[0] == ' ') {
5501             /* Part of the book output is here; append it */
5502             strcat(bookOutput, message);
5503             strcat(bookOutput, "  \n");
5504             return;
5505         } else if (bookOutput[0] != NULLCHAR) {
5506             /* All of book output has arrived; display it */
5507             char *p = bookOutput;
5508             while (*p != NULLCHAR) {
5509                 if (*p == '\t') *p = ' ';
5510                 p++;
5511             }
5512             DisplayInformation(bookOutput);
5513             bookRequested = FALSE;
5514             /* Fall through to parse the current output */
5515         }
5516     }
5517
5518     /*
5519      * Look for machine move.
5520      */
5521     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5522         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5523     {
5524         /* This method is only useful on engines that support ping */
5525         if (cps->lastPing != cps->lastPong) {
5526           if (gameMode == BeginningOfGame) {
5527             /* Extra move from before last new; ignore */
5528             if (appData.debugMode) {
5529                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5530             }
5531           } else {
5532             if (appData.debugMode) {
5533                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5534                         cps->which, gameMode);
5535             }
5536
5537             SendToProgram("undo\n", cps);
5538           }
5539           return;
5540         }
5541
5542         switch (gameMode) {
5543           case BeginningOfGame:
5544             /* Extra move from before last reset; ignore */
5545             if (appData.debugMode) {
5546                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5547             }
5548             return;
5549
5550           case EndOfGame:
5551           case IcsIdle:
5552           default:
5553             /* Extra move after we tried to stop.  The mode test is
5554                not a reliable way of detecting this problem, but it's
5555                the best we can do on engines that don't support ping.
5556             */
5557             if (appData.debugMode) {
5558                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5559                         cps->which, gameMode);
5560             }
5561             SendToProgram("undo\n", cps);
5562             return;
5563
5564           case MachinePlaysWhite:
5565           case IcsPlayingWhite:
5566             machineWhite = TRUE;
5567             break;
5568
5569           case MachinePlaysBlack:
5570           case IcsPlayingBlack:
5571             machineWhite = FALSE;
5572             break;
5573
5574           case TwoMachinesPlay:
5575             machineWhite = (cps->twoMachinesColor[0] == 'w');
5576             break;
5577         }
5578         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5579             if (appData.debugMode) {
5580                 fprintf(debugFP,
5581                         "Ignoring move out of turn by %s, gameMode %d"
5582                         ", forwardMost %d\n",
5583                         cps->which, gameMode, forwardMostMove);
5584             }
5585             return;
5586         }
5587
5588     if (appData.debugMode) { int f = forwardMostMove;
5589         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5590                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5591     }
5592         if(cps->alphaRank) AlphaRank(machineMove, 4);
5593         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5594                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5595             /* Machine move could not be parsed; ignore it. */
5596             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5597                     machineMove, cps->which);
5598             DisplayError(buf1, 0);
5599             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5600                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5601             if (gameMode == TwoMachinesPlay) {
5602               GameEnds(machineWhite ? BlackWins : WhiteWins,
5603                        buf1, GE_XBOARD);
5604             }
5605             return;
5606         }
5607
5608         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5609         /* So we have to redo legality test with true e.p. status here,  */
5610         /* to make sure an illegal e.p. capture does not slip through,   */
5611         /* to cause a forfeit on a justified illegal-move complaint      */
5612         /* of the opponent.                                              */
5613         if( gameMode==TwoMachinesPlay && appData.testLegality
5614             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5615                                                               ) {
5616            ChessMove moveType;
5617            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5618                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5619                              fromY, fromX, toY, toX, promoChar);
5620             if (appData.debugMode) {
5621                 int i;
5622                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5623                     castlingRights[forwardMostMove][i], castlingRank[i]);
5624                 fprintf(debugFP, "castling rights\n");
5625             }
5626             if(moveType == IllegalMove) {
5627                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5628                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5629                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5630                            buf1, GE_XBOARD);
5631                 return;
5632            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5633            /* [HGM] Kludge to handle engines that send FRC-style castling
5634               when they shouldn't (like TSCP-Gothic) */
5635            switch(moveType) {
5636              case WhiteASideCastleFR:
5637              case BlackASideCastleFR:
5638                toX+=2;
5639                currentMoveString[2]++;
5640                break;
5641              case WhiteHSideCastleFR:
5642              case BlackHSideCastleFR:
5643                toX--;
5644                currentMoveString[2]--;
5645                break;
5646              default: ; // nothing to do, but suppresses warning of pedantic compilers
5647            }
5648         }
5649         hintRequested = FALSE;
5650         lastHint[0] = NULLCHAR;
5651         bookRequested = FALSE;
5652         /* Program may be pondering now */
5653         cps->maybeThinking = TRUE;
5654         if (cps->sendTime == 2) cps->sendTime = 1;
5655         if (cps->offeredDraw) cps->offeredDraw--;
5656
5657 #if ZIPPY
5658         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5659             first.initDone) {
5660           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5661           ics_user_moved = 1;
5662           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5663                 char buf[3*MSG_SIZ];
5664
5665                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5666                         programStats.score / 100.,
5667                         programStats.depth,
5668                         programStats.time / 100.,
5669                         u64ToDouble(programStats.nodes),
5670                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5671                         programStats.movelist);
5672                 SendToICS(buf);
5673           }
5674         }
5675 #endif
5676         /* currentMoveString is set as a side-effect of ParseOneMove */
5677         strcpy(machineMove, currentMoveString);
5678         strcat(machineMove, "\n");
5679         strcpy(moveList[forwardMostMove], machineMove);
5680
5681         /* [AS] Save move info and clear stats for next move */
5682         pvInfoList[ forwardMostMove ].score = programStats.score;
5683         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5684         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5685         ClearProgramStats();
5686         thinkOutput[0] = NULLCHAR;
5687         hiddenThinkOutputState = 0;
5688
5689         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5690
5691         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5692         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5693             int count = 0;
5694
5695             while( count < adjudicateLossPlies ) {
5696                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5697
5698                 if( count & 1 ) {
5699                     score = -score; /* Flip score for winning side */
5700                 }
5701
5702                 if( score > adjudicateLossThreshold ) {
5703                     break;
5704                 }
5705
5706                 count++;
5707             }
5708
5709             if( count >= adjudicateLossPlies ) {
5710                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5711
5712                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5713                     "Xboard adjudication", 
5714                     GE_XBOARD );
5715
5716                 return;
5717             }
5718         }
5719
5720         if( gameMode == TwoMachinesPlay ) {
5721           // [HGM] some adjudications useful with buggy engines
5722             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5723           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5724
5725
5726             if( appData.testLegality )
5727             {   /* [HGM] Some more adjudications for obstinate engines */
5728                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5729                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5730                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5731                 static int moveCount = 6;
5732                 ChessMove result;
5733                 char *reason = NULL;
5734
5735                 /* Count what is on board. */
5736                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5737                 {   ChessSquare p = boards[forwardMostMove][i][j];
5738                     int m=i;
5739
5740                     switch((int) p)
5741                     {   /* count B,N,R and other of each side */
5742                         case WhiteKing:
5743                         case BlackKing:
5744                              NrK++; break; // [HGM] atomic: count Kings
5745                         case WhiteKnight:
5746                              NrWN++; break;
5747                         case WhiteBishop:
5748                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5749                              bishopsColor |= 1 << ((i^j)&1);
5750                              NrWB++; break;
5751                         case BlackKnight:
5752                              NrBN++; break;
5753                         case BlackBishop:
5754                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5755                              bishopsColor |= 1 << ((i^j)&1);
5756                              NrBB++; break;
5757                         case WhiteRook:
5758                              NrWR++; break;
5759                         case BlackRook:
5760                              NrBR++; break;
5761                         case WhiteQueen:
5762                              NrWQ++; break;
5763                         case BlackQueen:
5764                              NrBQ++; break;
5765                         case EmptySquare: 
5766                              break;
5767                         case BlackPawn:
5768                              m = 7-i;
5769                         case WhitePawn:
5770                              PawnAdvance += m; NrPawns++;
5771                     }
5772                     NrPieces += (p != EmptySquare);
5773                     NrW += ((int)p < (int)BlackPawn);
5774                     if(gameInfo.variant == VariantXiangqi && 
5775                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5776                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5777                         NrW -= ((int)p < (int)BlackPawn);
5778                     }
5779                 }
5780
5781                 /* Some material-based adjudications that have to be made before stalemate test */
5782                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5783                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5784                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5785                      if(appData.checkMates) {
5786                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5787                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5788                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5789                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5790                          return;
5791                      }
5792                 }
5793
5794                 /* Bare King in Shatranj (loses) or Losers (wins) */
5795                 if( NrW == 1 || NrPieces - NrW == 1) {
5796                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5797                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5798                      if(appData.checkMates) {
5799                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5800                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5801                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5802                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5803                          return;
5804                      }
5805                   } else
5806                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5807                   {    /* bare King */
5808                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5809                         if(appData.checkMates) {
5810                             /* but only adjudicate if adjudication enabled */
5811                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5812                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5813                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5814                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5815                             return;
5816                         }
5817                   }
5818                 } else bare = 1;
5819
5820
5821             // don't wait for engine to announce game end if we can judge ourselves
5822             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5823                                        castlingRights[forwardMostMove]) ) {
5824               case MT_CHECK:
5825                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5826                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5827                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5828                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5829                             checkCnt++;
5830                         if(checkCnt >= 2) {
5831                             reason = "Xboard adjudication: 3rd check";
5832                             epStatus[forwardMostMove] = EP_CHECKMATE;
5833                             break;
5834                         }
5835                     }
5836                 }
5837               case MT_NONE:
5838               default:
5839                 break;
5840               case MT_STALEMATE:
5841               case MT_STAINMATE:
5842                 reason = "Xboard adjudication: Stalemate";
5843                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5844                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5845                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5846                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5847                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5848                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5849                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5850                                                                         EP_CHECKMATE : EP_WINS);
5851                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5852                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5853                 }
5854                 break;
5855               case MT_CHECKMATE:
5856                 reason = "Xboard adjudication: Checkmate";
5857                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5858                 break;
5859             }
5860
5861                 switch(i = epStatus[forwardMostMove]) {
5862                     case EP_STALEMATE:
5863                         result = GameIsDrawn; break;
5864                     case EP_CHECKMATE:
5865                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5866                     case EP_WINS:
5867                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5868                     default:
5869                         result = (ChessMove) 0;
5870                 }
5871                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5872                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5873                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5874                     GameEnds( result, reason, GE_XBOARD );
5875                     return;
5876                 }
5877
5878                 /* Next absolutely insufficient mating material. */
5879                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5880                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5881                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5882                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5883                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5884
5885                      /* always flag draws, for judging claims */
5886                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5887
5888                      if(appData.materialDraws) {
5889                          /* but only adjudicate them if adjudication enabled */
5890                          SendToProgram("force\n", cps->other); // suppress reply
5891                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5892                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5894                          return;
5895                      }
5896                 }
5897
5898                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5899                 if(NrPieces == 4 && 
5900                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5901                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5902                    || NrWN==2 || NrBN==2     /* KNNK */
5903                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5904                   ) ) {
5905                      if(--moveCount < 0 && appData.trivialDraws)
5906                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5907                           SendToProgram("force\n", cps->other); // suppress reply
5908                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5909                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5910                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5911                           return;
5912                      }
5913                 } else moveCount = 6;
5914             }
5915           }
5916 #if 1
5917     if (appData.debugMode) { int i;
5918       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5919               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5920               appData.drawRepeats);
5921       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5922            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5923
5924     }
5925 #endif
5926                 /* Check for rep-draws */
5927                 count = 0;
5928                 for(k = forwardMostMove-2;
5929                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5930                         epStatus[k] < EP_UNKNOWN &&
5931                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5932                     k-=2)
5933                 {   int rights=0;
5934 #if 0
5935     if (appData.debugMode) {
5936       fprintf(debugFP, " loop\n");
5937     }
5938 #endif
5939                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5940 #if 0
5941     if (appData.debugMode) {
5942       fprintf(debugFP, "match\n");
5943     }
5944 #endif
5945                         /* compare castling rights */
5946                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5947                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5948                                 rights++; /* King lost rights, while rook still had them */
5949                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5950                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5951                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5952                                    rights++; /* but at least one rook lost them */
5953                         }
5954                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5955                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5956                                 rights++; 
5957                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5958                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5959                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5960                                    rights++;
5961                         }
5962 #if 0
5963     if (appData.debugMode) {
5964       for(i=0; i<nrCastlingRights; i++)
5965       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5966     }
5967
5968     if (appData.debugMode) {
5969       fprintf(debugFP, " %d %d\n", rights, k);
5970     }
5971 #endif
5972                         if( rights == 0 && ++count > appData.drawRepeats-2
5973                             && appData.drawRepeats > 1) {
5974                              /* adjudicate after user-specified nr of repeats */
5975                              SendToProgram("force\n", cps->other); // suppress reply
5976                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5977                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5978                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5979                                 // [HGM] xiangqi: check for forbidden perpetuals
5980                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5981                                 for(m=forwardMostMove; m>k; m-=2) {
5982                                     if(MateTest(boards[m], PosFlags(m), 
5983                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5984                                         ourPerpetual = 0; // the current mover did not always check
5985                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5986                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5987                                         hisPerpetual = 0; // the opponent did not always check
5988                                 }
5989                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5990                                                                         ourPerpetual, hisPerpetual);
5991                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5992                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5993                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5994                                     return;
5995                                 }
5996                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5997                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5998                                 // Now check for perpetual chases
5999                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6000                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6001                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6002                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6003                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6004                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6005                                         return;
6006                                     }
6007                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6008                                         break; // Abort repetition-checking loop.
6009                                 }
6010                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6011                              }
6012                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6013                              return;
6014                         }
6015                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6016                              epStatus[forwardMostMove] = EP_REP_DRAW;
6017                     }
6018                 }
6019
6020                 /* Now we test for 50-move draws. Determine ply count */
6021                 count = forwardMostMove;
6022                 /* look for last irreversble move */
6023                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6024                     count--;
6025                 /* if we hit starting position, add initial plies */
6026                 if( count == backwardMostMove )
6027                     count -= initialRulePlies;
6028                 count = forwardMostMove - count; 
6029                 if( count >= 100)
6030                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6031                          /* this is used to judge if draw claims are legal */
6032                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6033                          SendToProgram("force\n", cps->other); // suppress reply
6034                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6035                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6037                          return;
6038                 }
6039
6040                 /* if draw offer is pending, treat it as a draw claim
6041                  * when draw condition present, to allow engines a way to
6042                  * claim draws before making their move to avoid a race
6043                  * condition occurring after their move
6044                  */
6045                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6046                          char *p = NULL;
6047                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6048                              p = "Draw claim: 50-move rule";
6049                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6050                              p = "Draw claim: 3-fold repetition";
6051                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6052                              p = "Draw claim: insufficient mating material";
6053                          if( p != NULL ) {
6054                              SendToProgram("force\n", cps->other); // suppress reply
6055                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6056                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6057                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6058                              return;
6059                          }
6060                 }
6061
6062
6063                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6064                     SendToProgram("force\n", cps->other); // suppress reply
6065                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6066                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067
6068                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6069
6070                     return;
6071                 }
6072         }
6073
6074         bookHit = NULL;
6075         if (gameMode == TwoMachinesPlay) {
6076             /* [HGM] relaying draw offers moved to after reception of move */
6077             /* and interpreting offer as claim if it brings draw condition */
6078             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6079                 SendToProgram("draw\n", cps->other);
6080             }
6081             if (cps->other->sendTime) {
6082                 SendTimeRemaining(cps->other,
6083                                   cps->other->twoMachinesColor[0] == 'w');
6084             }
6085             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6086             if (firstMove && !bookHit) {
6087                 firstMove = FALSE;
6088                 if (cps->other->useColors) {
6089                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6090                 }
6091                 SendToProgram("go\n", cps->other);
6092             }
6093             cps->other->maybeThinking = TRUE;
6094         }
6095
6096         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6097         
6098         if (!pausing && appData.ringBellAfterMoves) {
6099             RingBell();
6100         }
6101
6102         /* 
6103          * Reenable menu items that were disabled while
6104          * machine was thinking
6105          */
6106         if (gameMode != TwoMachinesPlay)
6107             SetUserThinkingEnables();
6108
6109         // [HGM] book: after book hit opponent has received move and is now in force mode
6110         // force the book reply into it, and then fake that it outputted this move by jumping
6111         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6112         if(bookHit) {
6113                 static char bookMove[MSG_SIZ]; // a bit generous?
6114
6115                 strcpy(bookMove, "move ");
6116                 strcat(bookMove, bookHit);
6117                 message = bookMove;
6118                 cps = cps->other;
6119                 programStats.nodes = programStats.depth = programStats.time = 
6120                 programStats.score = programStats.got_only_move = 0;
6121                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6122
6123                 if(cps->lastPing != cps->lastPong) {
6124                     savedMessage = message; // args for deferred call
6125                     savedState = cps;
6126                     ScheduleDelayedEvent(DeferredBookMove, 10);
6127                     return;
6128                 }
6129                 goto FakeBookMove;
6130         }
6131
6132         return;
6133     }
6134
6135     /* Set special modes for chess engines.  Later something general
6136      *  could be added here; for now there is just one kludge feature,
6137      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6138      *  when "xboard" is given as an interactive command.
6139      */
6140     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6141         cps->useSigint = FALSE;
6142         cps->useSigterm = FALSE;
6143     }
6144
6145     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6146      * want this, I was asked to put it in, and obliged.
6147      */
6148     if (!strncmp(message, "setboard ", 9)) {
6149         Board initial_position; int i;
6150
6151         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6152
6153         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6154             DisplayError(_("Bad FEN received from engine"), 0);
6155             return ;
6156         } else {
6157            Reset(FALSE, FALSE);
6158            CopyBoard(boards[0], initial_position);
6159            initialRulePlies = FENrulePlies;
6160            epStatus[0] = FENepStatus;
6161            for( i=0; i<nrCastlingRights; i++ )
6162                 castlingRights[0][i] = FENcastlingRights[i];
6163            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6164            else gameMode = MachinePlaysBlack;                 
6165            DrawPosition(FALSE, boards[currentMove]);
6166         }
6167         return;
6168     }
6169
6170     /*
6171      * Look for communication commands
6172      */
6173     if (!strncmp(message, "telluser ", 9)) {
6174         DisplayNote(message + 9);
6175         return;
6176     }
6177     if (!strncmp(message, "tellusererror ", 14)) {
6178         DisplayError(message + 14, 0);
6179         return;
6180     }
6181     if (!strncmp(message, "tellopponent ", 13)) {
6182       if (appData.icsActive) {
6183         if (loggedOn) {
6184           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6185           SendToICS(buf1);
6186         }
6187       } else {
6188         DisplayNote(message + 13);
6189       }
6190       return;
6191     }
6192     if (!strncmp(message, "tellothers ", 11)) {
6193       if (appData.icsActive) {
6194         if (loggedOn) {
6195           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6196           SendToICS(buf1);
6197         }
6198       }
6199       return;
6200     }
6201     if (!strncmp(message, "tellall ", 8)) {
6202       if (appData.icsActive) {
6203         if (loggedOn) {
6204           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6205           SendToICS(buf1);
6206         }
6207       } else {
6208         DisplayNote(message + 8);
6209       }
6210       return;
6211     }
6212     if (strncmp(message, "warning", 7) == 0) {
6213         /* Undocumented feature, use tellusererror in new code */
6214         DisplayError(message, 0);
6215         return;
6216     }
6217     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6218         strcpy(realname, cps->tidy);
6219         strcat(realname, " query");
6220         AskQuestion(realname, buf2, buf1, cps->pr);
6221         return;
6222     }
6223     /* Commands from the engine directly to ICS.  We don't allow these to be 
6224      *  sent until we are logged on. Crafty kibitzes have been known to 
6225      *  interfere with the login process.
6226      */
6227     if (loggedOn) {
6228         if (!strncmp(message, "tellics ", 8)) {
6229             SendToICS(message + 8);
6230             SendToICS("\n");
6231             return;
6232         }
6233         if (!strncmp(message, "tellicsnoalias ", 15)) {
6234             SendToICS(ics_prefix);
6235             SendToICS(message + 15);
6236             SendToICS("\n");
6237             return;
6238         }
6239         /* The following are for backward compatibility only */
6240         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6241             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6242             SendToICS(ics_prefix);
6243             SendToICS(message);
6244             SendToICS("\n");
6245             return;
6246         }
6247     }
6248     if (strncmp(message, "feature ", 8) == 0) {
6249       ParseFeatures(message+8, cps);
6250     }
6251     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6252         return;
6253     }
6254     /*
6255      * If the move is illegal, cancel it and redraw the board.
6256      * Also deal with other error cases.  Matching is rather loose
6257      * here to accommodate engines written before the spec.
6258      */
6259     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6260         strncmp(message, "Error", 5) == 0) {
6261         if (StrStr(message, "name") || 
6262             StrStr(message, "rating") || StrStr(message, "?") ||
6263             StrStr(message, "result") || StrStr(message, "board") ||
6264             StrStr(message, "bk") || StrStr(message, "computer") ||
6265             StrStr(message, "variant") || StrStr(message, "hint") ||
6266             StrStr(message, "random") || StrStr(message, "depth") ||
6267             StrStr(message, "accepted")) {
6268             return;
6269         }
6270         if (StrStr(message, "protover")) {
6271           /* Program is responding to input, so it's apparently done
6272              initializing, and this error message indicates it is
6273              protocol version 1.  So we don't need to wait any longer
6274              for it to initialize and send feature commands. */
6275           FeatureDone(cps, 1);
6276           cps->protocolVersion = 1;
6277           return;
6278         }
6279         cps->maybeThinking = FALSE;
6280
6281         if (StrStr(message, "draw")) {
6282             /* Program doesn't have "draw" command */
6283             cps->sendDrawOffers = 0;
6284             return;
6285         }
6286         if (cps->sendTime != 1 &&
6287             (StrStr(message, "time") || StrStr(message, "otim"))) {
6288           /* Program apparently doesn't have "time" or "otim" command */
6289           cps->sendTime = 0;
6290           return;
6291         }
6292         if (StrStr(message, "analyze")) {
6293             cps->analysisSupport = FALSE;
6294             cps->analyzing = FALSE;
6295             Reset(FALSE, TRUE);
6296             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6297             DisplayError(buf2, 0);
6298             return;
6299         }
6300         if (StrStr(message, "(no matching move)st")) {
6301           /* Special kludge for GNU Chess 4 only */
6302           cps->stKludge = TRUE;
6303           SendTimeControl(cps, movesPerSession, timeControl,
6304                           timeIncrement, appData.searchDepth,
6305                           searchTime);
6306           return;
6307         }
6308         if (StrStr(message, "(no matching move)sd")) {
6309           /* Special kludge for GNU Chess 4 only */
6310           cps->sdKludge = TRUE;
6311           SendTimeControl(cps, movesPerSession, timeControl,
6312                           timeIncrement, appData.searchDepth,
6313                           searchTime);
6314           return;
6315         }
6316         if (!StrStr(message, "llegal")) {
6317             return;
6318         }
6319         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6320             gameMode == IcsIdle) return;
6321         if (forwardMostMove <= backwardMostMove) return;
6322 #if 0
6323         /* Following removed: it caused a bug where a real illegal move
6324            message in analyze mored would be ignored. */
6325         if (cps == &first && programStats.ok_to_send == 0) {
6326             /* Bogus message from Crafty responding to "."  This filtering
6327                can miss some of the bad messages, but fortunately the bug 
6328                is fixed in current Crafty versions, so it doesn't matter. */
6329             return;
6330         }
6331 #endif
6332         if (pausing) PauseEvent();
6333         if (gameMode == PlayFromGameFile) {
6334             /* Stop reading this game file */
6335             gameMode = EditGame;
6336             ModeHighlight();
6337         }
6338         currentMove = --forwardMostMove;
6339         DisplayMove(currentMove-1); /* before DisplayMoveError */
6340         SwitchClocks();
6341         DisplayBothClocks();
6342         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6343                 parseList[currentMove], cps->which);
6344         DisplayMoveError(buf1);
6345         DrawPosition(FALSE, boards[currentMove]);
6346
6347         /* [HGM] illegal-move claim should forfeit game when Xboard */
6348         /* only passes fully legal moves                            */
6349         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6350             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6351                                 "False illegal-move claim", GE_XBOARD );
6352         }
6353         return;
6354     }
6355     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6356         /* Program has a broken "time" command that
6357            outputs a string not ending in newline.
6358            Don't use it. */
6359         cps->sendTime = 0;
6360     }
6361     
6362     /*
6363      * If chess program startup fails, exit with an error message.
6364      * Attempts to recover here are futile.
6365      */
6366     if ((StrStr(message, "unknown host") != NULL)
6367         || (StrStr(message, "No remote directory") != NULL)
6368         || (StrStr(message, "not found") != NULL)
6369         || (StrStr(message, "No such file") != NULL)
6370         || (StrStr(message, "can't alloc") != NULL)
6371         || (StrStr(message, "Permission denied") != NULL)) {
6372
6373         cps->maybeThinking = FALSE;
6374         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6375                 cps->which, cps->program, cps->host, message);
6376         RemoveInputSource(cps->isr);
6377         DisplayFatalError(buf1, 0, 1);
6378         return;
6379     }
6380     
6381     /* 
6382      * Look for hint output
6383      */
6384     if (sscanf(message, "Hint: %s", buf1) == 1) {
6385         if (cps == &first && hintRequested) {
6386             hintRequested = FALSE;
6387             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6388                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6389                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6390                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6391                                     fromY, fromX, toY, toX, promoChar, buf1);
6392                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6393                 DisplayInformation(buf2);
6394             } else {
6395                 /* Hint move could not be parsed!? */
6396               snprintf(buf2, sizeof(buf2),
6397                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6398                         buf1, cps->which);
6399                 DisplayError(buf2, 0);
6400             }
6401         } else {
6402             strcpy(lastHint, buf1);
6403         }
6404         return;
6405     }
6406
6407     /*
6408      * Ignore other messages if game is not in progress
6409      */
6410     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6411         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6412
6413     /*
6414      * look for win, lose, draw, or draw offer
6415      */
6416     if (strncmp(message, "1-0", 3) == 0) {
6417         char *p, *q, *r = "";
6418         p = strchr(message, '{');
6419         if (p) {
6420             q = strchr(p, '}');
6421             if (q) {
6422                 *q = NULLCHAR;
6423                 r = p + 1;
6424             }
6425         }
6426         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6427         return;
6428     } else if (strncmp(message, "0-1", 3) == 0) {
6429         char *p, *q, *r = "";
6430         p = strchr(message, '{');
6431         if (p) {
6432             q = strchr(p, '}');
6433             if (q) {
6434                 *q = NULLCHAR;
6435                 r = p + 1;
6436             }
6437         }
6438         /* Kludge for Arasan 4.1 bug */
6439         if (strcmp(r, "Black resigns") == 0) {
6440             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6441             return;
6442         }
6443         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6444         return;
6445     } else if (strncmp(message, "1/2", 3) == 0) {
6446         char *p, *q, *r = "";
6447         p = strchr(message, '{');
6448         if (p) {
6449             q = strchr(p, '}');
6450             if (q) {
6451                 *q = NULLCHAR;
6452                 r = p + 1;
6453             }
6454         }
6455             
6456         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6457         return;
6458
6459     } else if (strncmp(message, "White resign", 12) == 0) {
6460         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6461         return;
6462     } else if (strncmp(message, "Black resign", 12) == 0) {
6463         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6464         return;
6465     } else if (strncmp(message, "White matches", 13) == 0 ||
6466                strncmp(message, "Black matches", 13) == 0   ) {
6467         /* [HGM] ignore GNUShogi noises */
6468         return;
6469     } else if (strncmp(message, "White", 5) == 0 &&
6470                message[5] != '(' &&
6471                StrStr(message, "Black") == NULL) {
6472         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6473         return;
6474     } else if (strncmp(message, "Black", 5) == 0 &&
6475                message[5] != '(') {
6476         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6477         return;
6478     } else if (strcmp(message, "resign") == 0 ||
6479                strcmp(message, "computer resigns") == 0) {
6480         switch (gameMode) {
6481           case MachinePlaysBlack:
6482           case IcsPlayingBlack:
6483             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6484             break;
6485           case MachinePlaysWhite:
6486           case IcsPlayingWhite:
6487             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6488             break;
6489           case TwoMachinesPlay:
6490             if (cps->twoMachinesColor[0] == 'w')
6491               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6492             else
6493               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6494             break;
6495           default:
6496             /* can't happen */
6497             break;
6498         }
6499         return;
6500     } else if (strncmp(message, "opponent mates", 14) == 0) {
6501         switch (gameMode) {
6502           case MachinePlaysBlack:
6503           case IcsPlayingBlack:
6504             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6505             break;
6506           case MachinePlaysWhite:
6507           case IcsPlayingWhite:
6508             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6509             break;
6510           case TwoMachinesPlay:
6511             if (cps->twoMachinesColor[0] == 'w')
6512               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6513             else
6514               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6515             break;
6516           default:
6517             /* can't happen */
6518             break;
6519         }
6520         return;
6521     } else if (strncmp(message, "computer mates", 14) == 0) {
6522         switch (gameMode) {
6523           case MachinePlaysBlack:
6524           case IcsPlayingBlack:
6525             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6526             break;
6527           case MachinePlaysWhite:
6528           case IcsPlayingWhite:
6529             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6530             break;
6531           case TwoMachinesPlay:
6532             if (cps->twoMachinesColor[0] == 'w')
6533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6534             else
6535               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6536             break;
6537           default:
6538             /* can't happen */
6539             break;
6540         }
6541         return;
6542     } else if (strncmp(message, "checkmate", 9) == 0) {
6543         if (WhiteOnMove(forwardMostMove)) {
6544             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6545         } else {
6546             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6547         }
6548         return;
6549     } else if (strstr(message, "Draw") != NULL ||
6550                strstr(message, "game is a draw") != NULL) {
6551         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6552         return;
6553     } else if (strstr(message, "offer") != NULL &&
6554                strstr(message, "draw") != NULL) {
6555 #if ZIPPY
6556         if (appData.zippyPlay && first.initDone) {
6557             /* Relay offer to ICS */
6558             SendToICS(ics_prefix);
6559             SendToICS("draw\n");
6560         }
6561 #endif
6562         cps->offeredDraw = 2; /* valid until this engine moves twice */
6563         if (gameMode == TwoMachinesPlay) {
6564             if (cps->other->offeredDraw) {
6565                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6566             /* [HGM] in two-machine mode we delay relaying draw offer      */
6567             /* until after we also have move, to see if it is really claim */
6568             }
6569 #if 0
6570               else {
6571                 if (cps->other->sendDrawOffers) {
6572                     SendToProgram("draw\n", cps->other);
6573                 }
6574             }
6575 #endif
6576         } else if (gameMode == MachinePlaysWhite ||
6577                    gameMode == MachinePlaysBlack) {
6578           if (userOfferedDraw) {
6579             DisplayInformation(_("Machine accepts your draw offer"));
6580             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6581           } else {
6582             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6583           }
6584         }
6585     }
6586
6587     
6588     /*
6589      * Look for thinking output
6590      */
6591     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6592           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6593                                 ) {
6594         int plylev, mvleft, mvtot, curscore, time;
6595         char mvname[MOVE_LEN];
6596         u64 nodes; // [DM]
6597         char plyext;
6598         int ignore = FALSE;
6599         int prefixHint = FALSE;
6600         mvname[0] = NULLCHAR;
6601
6602         switch (gameMode) {
6603           case MachinePlaysBlack:
6604           case IcsPlayingBlack:
6605             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6606             break;
6607           case MachinePlaysWhite:
6608           case IcsPlayingWhite:
6609             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6610             break;
6611           case AnalyzeMode:
6612           case AnalyzeFile:
6613             break;
6614           case IcsObserving: /* [DM] icsEngineAnalyze */
6615             if (!appData.icsEngineAnalyze) ignore = TRUE;
6616             break;
6617           case TwoMachinesPlay:
6618             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6619                 ignore = TRUE;
6620             }
6621             break;
6622           default:
6623             ignore = TRUE;
6624             break;
6625         }
6626
6627         if (!ignore) {
6628             buf1[0] = NULLCHAR;
6629             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6630                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6631
6632                 if (plyext != ' ' && plyext != '\t') {
6633                     time *= 100;
6634                 }
6635
6636                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6637                 if( cps->scoreIsAbsolute && 
6638                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6639                 {
6640                     curscore = -curscore;
6641                 }
6642
6643
6644                 programStats.depth = plylev;
6645                 programStats.nodes = nodes;
6646                 programStats.time = time;
6647                 programStats.score = curscore;
6648                 programStats.got_only_move = 0;
6649
6650                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6651                         int ticklen;
6652
6653                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6654                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6655                         if(WhiteOnMove(forwardMostMove)) 
6656                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6657                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6658                 }
6659
6660                 /* Buffer overflow protection */
6661                 if (buf1[0] != NULLCHAR) {
6662                     if (strlen(buf1) >= sizeof(programStats.movelist)
6663                         && appData.debugMode) {
6664                         fprintf(debugFP,
6665                                 "PV is too long; using the first %d bytes.\n",
6666                                 sizeof(programStats.movelist) - 1);
6667                     }
6668
6669                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6670                 } else {
6671                     sprintf(programStats.movelist, " no PV\n");
6672                 }
6673
6674                 if (programStats.seen_stat) {
6675                     programStats.ok_to_send = 1;
6676                 }
6677
6678                 if (strchr(programStats.movelist, '(') != NULL) {
6679                     programStats.line_is_book = 1;
6680                     programStats.nr_moves = 0;
6681                     programStats.moves_left = 0;
6682                 } else {
6683                     programStats.line_is_book = 0;
6684                 }
6685
6686                 SendProgramStatsToFrontend( cps, &programStats );
6687
6688                 /* 
6689                     [AS] Protect the thinkOutput buffer from overflow... this
6690                     is only useful if buf1 hasn't overflowed first!
6691                 */
6692                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6693                         plylev, 
6694                         (gameMode == TwoMachinesPlay ?
6695                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6696                         ((double) curscore) / 100.0,
6697                         prefixHint ? lastHint : "",
6698                         prefixHint ? " " : "" );
6699
6700                 if( buf1[0] != NULLCHAR ) {
6701                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6702
6703                     if( strlen(buf1) > max_len ) {
6704                         if( appData.debugMode) {
6705                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6706                         }
6707                         buf1[max_len+1] = '\0';
6708                     }
6709
6710                     strcat( thinkOutput, buf1 );
6711                 }
6712
6713                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6714                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6715                     DisplayMove(currentMove - 1);
6716                     DisplayAnalysis();
6717                 }
6718                 return;
6719
6720             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6721                 /* crafty (9.25+) says "(only move) <move>"
6722                  * if there is only 1 legal move
6723                  */
6724                 sscanf(p, "(only move) %s", buf1);
6725                 sprintf(thinkOutput, "%s (only move)", buf1);
6726                 sprintf(programStats.movelist, "%s (only move)", buf1);
6727                 programStats.depth = 1;
6728                 programStats.nr_moves = 1;
6729                 programStats.moves_left = 1;
6730                 programStats.nodes = 1;
6731                 programStats.time = 1;
6732                 programStats.got_only_move = 1;
6733
6734                 /* Not really, but we also use this member to
6735                    mean "line isn't going to change" (Crafty
6736                    isn't searching, so stats won't change) */
6737                 programStats.line_is_book = 1;
6738
6739                 SendProgramStatsToFrontend( cps, &programStats );
6740                 
6741                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6742                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6743                     DisplayMove(currentMove - 1);
6744                     DisplayAnalysis();
6745                 }
6746                 return;
6747             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6748                               &time, &nodes, &plylev, &mvleft,
6749                               &mvtot, mvname) >= 5) {
6750                 /* The stat01: line is from Crafty (9.29+) in response
6751                    to the "." command */
6752                 programStats.seen_stat = 1;
6753                 cps->maybeThinking = TRUE;
6754
6755                 if (programStats.got_only_move || !appData.periodicUpdates)
6756                   return;
6757
6758                 programStats.depth = plylev;
6759                 programStats.time = time;
6760                 programStats.nodes = nodes;
6761                 programStats.moves_left = mvleft;
6762                 programStats.nr_moves = mvtot;
6763                 strcpy(programStats.move_name, mvname);
6764                 programStats.ok_to_send = 1;
6765                 programStats.movelist[0] = '\0';
6766
6767                 SendProgramStatsToFrontend( cps, &programStats );
6768
6769                 DisplayAnalysis();
6770                 return;
6771
6772             } else if (strncmp(message,"++",2) == 0) {
6773                 /* Crafty 9.29+ outputs this */
6774                 programStats.got_fail = 2;
6775                 return;
6776
6777             } else if (strncmp(message,"--",2) == 0) {
6778                 /* Crafty 9.29+ outputs this */
6779                 programStats.got_fail = 1;
6780                 return;
6781
6782             } else if (thinkOutput[0] != NULLCHAR &&
6783                        strncmp(message, "    ", 4) == 0) {
6784                 unsigned message_len;
6785
6786                 p = message;
6787                 while (*p && *p == ' ') p++;
6788
6789                 message_len = strlen( p );
6790
6791                 /* [AS] Avoid buffer overflow */
6792                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6793                     strcat(thinkOutput, " ");
6794                     strcat(thinkOutput, p);
6795                 }
6796
6797                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6798                     strcat(programStats.movelist, " ");
6799                     strcat(programStats.movelist, p);
6800                 }
6801
6802                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6803                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6804                     DisplayMove(currentMove - 1);
6805                     DisplayAnalysis();
6806                 }
6807                 return;
6808             }
6809         }
6810         else {
6811             buf1[0] = NULLCHAR;
6812
6813             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6814                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6815             {
6816                 ChessProgramStats cpstats;
6817
6818                 if (plyext != ' ' && plyext != '\t') {
6819                     time *= 100;
6820                 }
6821
6822                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6823                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6824                     curscore = -curscore;
6825                 }
6826
6827                 cpstats.depth = plylev;
6828                 cpstats.nodes = nodes;
6829                 cpstats.time = time;
6830                 cpstats.score = curscore;
6831                 cpstats.got_only_move = 0;
6832                 cpstats.movelist[0] = '\0';
6833
6834                 if (buf1[0] != NULLCHAR) {
6835                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6836                 }
6837
6838                 cpstats.ok_to_send = 0;
6839                 cpstats.line_is_book = 0;
6840                 cpstats.nr_moves = 0;
6841                 cpstats.moves_left = 0;
6842
6843                 SendProgramStatsToFrontend( cps, &cpstats );
6844             }
6845         }
6846     }
6847 }
6848
6849
6850 /* Parse a game score from the character string "game", and
6851    record it as the history of the current game.  The game
6852    score is NOT assumed to start from the standard position. 
6853    The display is not updated in any way.
6854    */
6855 void
6856 ParseGameHistory(game)
6857      char *game;
6858 {
6859     ChessMove moveType;
6860     int fromX, fromY, toX, toY, boardIndex;
6861     char promoChar;
6862     char *p, *q;
6863     char buf[MSG_SIZ];
6864
6865     if (appData.debugMode)
6866       fprintf(debugFP, "Parsing game history: %s\n", game);
6867
6868     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6869     gameInfo.site = StrSave(appData.icsHost);
6870     gameInfo.date = PGNDate();
6871     gameInfo.round = StrSave("-");
6872
6873     /* Parse out names of players */
6874     while (*game == ' ') game++;
6875     p = buf;
6876     while (*game != ' ') *p++ = *game++;
6877     *p = NULLCHAR;
6878     gameInfo.white = StrSave(buf);
6879     while (*game == ' ') game++;
6880     p = buf;
6881     while (*game != ' ' && *game != '\n') *p++ = *game++;
6882     *p = NULLCHAR;
6883     gameInfo.black = StrSave(buf);
6884
6885     /* Parse moves */
6886     boardIndex = blackPlaysFirst ? 1 : 0;
6887     yynewstr(game);
6888     for (;;) {
6889         yyboardindex = boardIndex;
6890         moveType = (ChessMove) yylex();
6891         switch (moveType) {
6892           case IllegalMove:             /* maybe suicide chess, etc. */
6893   if (appData.debugMode) {
6894     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6895     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6896     setbuf(debugFP, NULL);
6897   }
6898           case WhitePromotionChancellor:
6899           case BlackPromotionChancellor:
6900           case WhitePromotionArchbishop:
6901           case BlackPromotionArchbishop:
6902           case WhitePromotionQueen:
6903           case BlackPromotionQueen:
6904           case WhitePromotionRook:
6905           case BlackPromotionRook:
6906           case WhitePromotionBishop:
6907           case BlackPromotionBishop:
6908           case WhitePromotionKnight:
6909           case BlackPromotionKnight:
6910           case WhitePromotionKing:
6911           case BlackPromotionKing:
6912           case NormalMove:
6913           case WhiteCapturesEnPassant:
6914           case BlackCapturesEnPassant:
6915           case WhiteKingSideCastle:
6916           case WhiteQueenSideCastle:
6917           case BlackKingSideCastle:
6918           case BlackQueenSideCastle:
6919           case WhiteKingSideCastleWild:
6920           case WhiteQueenSideCastleWild:
6921           case BlackKingSideCastleWild:
6922           case BlackQueenSideCastleWild:
6923           /* PUSH Fabien */
6924           case WhiteHSideCastleFR:
6925           case WhiteASideCastleFR:
6926           case BlackHSideCastleFR:
6927           case BlackASideCastleFR:
6928           /* POP Fabien */
6929             fromX = currentMoveString[0] - AAA;
6930             fromY = currentMoveString[1] - ONE;
6931             toX = currentMoveString[2] - AAA;
6932             toY = currentMoveString[3] - ONE;
6933             promoChar = currentMoveString[4];
6934             break;
6935           case WhiteDrop:
6936           case BlackDrop:
6937             fromX = moveType == WhiteDrop ?
6938               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6939             (int) CharToPiece(ToLower(currentMoveString[0]));
6940             fromY = DROP_RANK;
6941             toX = currentMoveString[2] - AAA;
6942             toY = currentMoveString[3] - ONE;
6943             promoChar = NULLCHAR;
6944             break;
6945           case AmbiguousMove:
6946             /* bug? */
6947             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6948   if (appData.debugMode) {
6949     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6950     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6951     setbuf(debugFP, NULL);
6952   }
6953             DisplayError(buf, 0);
6954             return;
6955           case ImpossibleMove:
6956             /* bug? */
6957             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6958   if (appData.debugMode) {
6959     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6960     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6961     setbuf(debugFP, NULL);
6962   }
6963             DisplayError(buf, 0);
6964             return;
6965           case (ChessMove) 0:   /* end of file */
6966             if (boardIndex < backwardMostMove) {
6967                 /* Oops, gap.  How did that happen? */
6968                 DisplayError(_("Gap in move list"), 0);
6969                 return;
6970             }
6971             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6972             if (boardIndex > forwardMostMove) {
6973                 forwardMostMove = boardIndex;
6974             }
6975             return;
6976           case ElapsedTime:
6977             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6978                 strcat(parseList[boardIndex-1], " ");
6979                 strcat(parseList[boardIndex-1], yy_text);
6980             }
6981             continue;
6982           case Comment:
6983           case PGNTag:
6984           case NAG:
6985           default:
6986             /* ignore */
6987             continue;
6988           case WhiteWins:
6989           case BlackWins:
6990           case GameIsDrawn:
6991           case GameUnfinished:
6992             if (gameMode == IcsExamining) {
6993                 if (boardIndex < backwardMostMove) {
6994                     /* Oops, gap.  How did that happen? */
6995                     return;
6996                 }
6997                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6998                 return;
6999             }
7000             gameInfo.result = moveType;
7001             p = strchr(yy_text, '{');
7002             if (p == NULL) p = strchr(yy_text, '(');
7003             if (p == NULL) {
7004                 p = yy_text;
7005                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7006             } else {
7007                 q = strchr(p, *p == '{' ? '}' : ')');
7008                 if (q != NULL) *q = NULLCHAR;
7009                 p++;
7010             }
7011             gameInfo.resultDetails = StrSave(p);
7012             continue;
7013         }
7014         if (boardIndex >= forwardMostMove &&
7015             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7016             backwardMostMove = blackPlaysFirst ? 1 : 0;
7017             return;
7018         }
7019         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7020                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7021                                  parseList[boardIndex]);
7022         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7023         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7024         /* currentMoveString is set as a side-effect of yylex */
7025         strcpy(moveList[boardIndex], currentMoveString);
7026         strcat(moveList[boardIndex], "\n");
7027         boardIndex++;
7028         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7029                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7030         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7031                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7032           case MT_NONE:
7033           case MT_STALEMATE:
7034           default:
7035             break;
7036           case MT_CHECK:
7037             if(gameInfo.variant != VariantShogi)
7038                 strcat(parseList[boardIndex - 1], "+");
7039             break;
7040           case MT_CHECKMATE:
7041           case MT_STAINMATE:
7042             strcat(parseList[boardIndex - 1], "#");
7043             break;
7044         }
7045     }
7046 }
7047
7048
7049 /* Apply a move to the given board  */
7050 void
7051 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7052      int fromX, fromY, toX, toY;
7053      int promoChar;
7054      Board board;
7055      char *castling;
7056      char *ep;
7057 {
7058   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7059
7060     /* [HGM] compute & store e.p. status and castling rights for new position */
7061     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7062     { int i;
7063
7064       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7065       oldEP = *ep;
7066       *ep = EP_NONE;
7067
7068       if( board[toY][toX] != EmptySquare ) 
7069            *ep = EP_CAPTURE;  
7070
7071       if( board[fromY][fromX] == WhitePawn ) {
7072            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7073                *ep = EP_PAWN_MOVE;
7074            if( toY-fromY==2) {
7075                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7076                         gameInfo.variant != VariantBerolina || toX < fromX)
7077                       *ep = toX | berolina;
7078                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7079                         gameInfo.variant != VariantBerolina || toX > fromX) 
7080                       *ep = toX;
7081            }
7082       } else 
7083       if( board[fromY][fromX] == BlackPawn ) {
7084            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7085                *ep = EP_PAWN_MOVE; 
7086            if( toY-fromY== -2) {
7087                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7088                         gameInfo.variant != VariantBerolina || toX < fromX)
7089                       *ep = toX | berolina;
7090                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7091                         gameInfo.variant != VariantBerolina || toX > fromX) 
7092                       *ep = toX;
7093            }
7094        }
7095
7096        for(i=0; i<nrCastlingRights; i++) {
7097            if(castling[i] == fromX && castlingRank[i] == fromY ||
7098               castling[i] == toX   && castlingRank[i] == toY   
7099              ) castling[i] = -1; // revoke for moved or captured piece
7100        }
7101
7102     }
7103
7104   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7105   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7106        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7107          
7108   if (fromX == toX && fromY == toY) return;
7109
7110   if (fromY == DROP_RANK) {
7111         /* must be first */
7112         piece = board[toY][toX] = (ChessSquare) fromX;
7113   } else {
7114      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7115      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7116      if(gameInfo.variant == VariantKnightmate)
7117          king += (int) WhiteUnicorn - (int) WhiteKing;
7118
7119     /* Code added by Tord: */
7120     /* FRC castling assumed when king captures friendly rook. */
7121     if (board[fromY][fromX] == WhiteKing &&
7122              board[toY][toX] == WhiteRook) {
7123       board[fromY][fromX] = EmptySquare;
7124       board[toY][toX] = EmptySquare;
7125       if(toX > fromX) {
7126         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7127       } else {
7128         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7129       }
7130     } else if (board[fromY][fromX] == BlackKing &&
7131                board[toY][toX] == BlackRook) {
7132       board[fromY][fromX] = EmptySquare;
7133       board[toY][toX] = EmptySquare;
7134       if(toX > fromX) {
7135         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7136       } else {
7137         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7138       }
7139     /* End of code added by Tord */
7140
7141     } else if (board[fromY][fromX] == king
7142         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7143         && toY == fromY && toX > fromX+1) {
7144         board[fromY][fromX] = EmptySquare;
7145         board[toY][toX] = king;
7146         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7147         board[fromY][BOARD_RGHT-1] = EmptySquare;
7148     } else if (board[fromY][fromX] == king
7149         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7150                && toY == fromY && toX < fromX-1) {
7151         board[fromY][fromX] = EmptySquare;
7152         board[toY][toX] = king;
7153         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7154         board[fromY][BOARD_LEFT] = EmptySquare;
7155     } else if (board[fromY][fromX] == WhitePawn
7156                && toY == BOARD_HEIGHT-1
7157                && gameInfo.variant != VariantXiangqi
7158                ) {
7159         /* white pawn promotion */
7160         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7161         if (board[toY][toX] == EmptySquare) {
7162             board[toY][toX] = WhiteQueen;
7163         }
7164         if(gameInfo.variant==VariantBughouse ||
7165            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7166             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7167         board[fromY][fromX] = EmptySquare;
7168     } else if ((fromY == BOARD_HEIGHT-4)
7169                && (toX != fromX)
7170                && gameInfo.variant != VariantXiangqi
7171                && gameInfo.variant != VariantBerolina
7172                && (board[fromY][fromX] == WhitePawn)
7173                && (board[toY][toX] == EmptySquare)) {
7174         board[fromY][fromX] = EmptySquare;
7175         board[toY][toX] = WhitePawn;
7176         captured = board[toY - 1][toX];
7177         board[toY - 1][toX] = EmptySquare;
7178     } else if ((fromY == BOARD_HEIGHT-4)
7179                && (toX == fromX)
7180                && gameInfo.variant == VariantBerolina
7181                && (board[fromY][fromX] == WhitePawn)
7182                && (board[toY][toX] == EmptySquare)) {
7183         board[fromY][fromX] = EmptySquare;
7184         board[toY][toX] = WhitePawn;
7185         if(oldEP & EP_BEROLIN_A) {
7186                 captured = board[fromY][fromX-1];
7187                 board[fromY][fromX-1] = EmptySquare;
7188         }else{  captured = board[fromY][fromX+1];
7189                 board[fromY][fromX+1] = EmptySquare;
7190         }
7191     } else if (board[fromY][fromX] == king
7192         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7193                && toY == fromY && toX > fromX+1) {
7194         board[fromY][fromX] = EmptySquare;
7195         board[toY][toX] = king;
7196         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7197         board[fromY][BOARD_RGHT-1] = EmptySquare;
7198     } else if (board[fromY][fromX] == king
7199         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7200                && toY == fromY && toX < fromX-1) {
7201         board[fromY][fromX] = EmptySquare;
7202         board[toY][toX] = king;
7203         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7204         board[fromY][BOARD_LEFT] = EmptySquare;
7205     } else if (fromY == 7 && fromX == 3
7206                && board[fromY][fromX] == BlackKing
7207                && toY == 7 && toX == 5) {
7208         board[fromY][fromX] = EmptySquare;
7209         board[toY][toX] = BlackKing;
7210         board[fromY][7] = EmptySquare;
7211         board[toY][4] = BlackRook;
7212     } else if (fromY == 7 && fromX == 3
7213                && board[fromY][fromX] == BlackKing
7214                && toY == 7 && toX == 1) {
7215         board[fromY][fromX] = EmptySquare;
7216         board[toY][toX] = BlackKing;
7217         board[fromY][0] = EmptySquare;
7218         board[toY][2] = BlackRook;
7219     } else if (board[fromY][fromX] == BlackPawn
7220                && toY == 0
7221                && gameInfo.variant != VariantXiangqi
7222                ) {
7223         /* black pawn promotion */
7224         board[0][toX] = CharToPiece(ToLower(promoChar));
7225         if (board[0][toX] == EmptySquare) {
7226             board[0][toX] = BlackQueen;
7227         }
7228         if(gameInfo.variant==VariantBughouse ||
7229            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7230             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7231         board[fromY][fromX] = EmptySquare;
7232     } else if ((fromY == 3)
7233                && (toX != fromX)
7234                && gameInfo.variant != VariantXiangqi
7235                && gameInfo.variant != VariantBerolina
7236                && (board[fromY][fromX] == BlackPawn)
7237                && (board[toY][toX] == EmptySquare)) {
7238         board[fromY][fromX] = EmptySquare;
7239         board[toY][toX] = BlackPawn;
7240         captured = board[toY + 1][toX];
7241         board[toY + 1][toX] = EmptySquare;
7242     } else if ((fromY == 3)
7243                && (toX == fromX)
7244                && gameInfo.variant == VariantBerolina
7245                && (board[fromY][fromX] == BlackPawn)
7246                && (board[toY][toX] == EmptySquare)) {
7247         board[fromY][fromX] = EmptySquare;
7248         board[toY][toX] = BlackPawn;
7249         if(oldEP & EP_BEROLIN_A) {
7250                 captured = board[fromY][fromX-1];
7251                 board[fromY][fromX-1] = EmptySquare;
7252         }else{  captured = board[fromY][fromX+1];
7253                 board[fromY][fromX+1] = EmptySquare;
7254         }
7255     } else {
7256         board[toY][toX] = board[fromY][fromX];
7257         board[fromY][fromX] = EmptySquare;
7258     }
7259
7260     /* [HGM] now we promote for Shogi, if needed */
7261     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7262         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7263   }
7264
7265     if (gameInfo.holdingsWidth != 0) {
7266
7267       /* !!A lot more code needs to be written to support holdings  */
7268       /* [HGM] OK, so I have written it. Holdings are stored in the */
7269       /* penultimate board files, so they are automaticlly stored   */
7270       /* in the game history.                                       */
7271       if (fromY == DROP_RANK) {
7272         /* Delete from holdings, by decreasing count */
7273         /* and erasing image if necessary            */
7274         p = (int) fromX;
7275         if(p < (int) BlackPawn) { /* white drop */
7276              p -= (int)WhitePawn;
7277              if(p >= gameInfo.holdingsSize) p = 0;
7278              if(--board[p][BOARD_WIDTH-2] == 0)
7279                   board[p][BOARD_WIDTH-1] = EmptySquare;
7280         } else {                  /* black drop */
7281              p -= (int)BlackPawn;
7282              if(p >= gameInfo.holdingsSize) p = 0;
7283              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7284                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7285         }
7286       }
7287       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7288           && gameInfo.variant != VariantBughouse        ) {
7289         /* [HGM] holdings: Add to holdings, if holdings exist */
7290         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7291                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7292                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7293         }
7294         p = (int) captured;
7295         if (p >= (int) BlackPawn) {
7296           p -= (int)BlackPawn;
7297           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7298                   /* in Shogi restore piece to its original  first */
7299                   captured = (ChessSquare) (DEMOTED captured);
7300                   p = DEMOTED p;
7301           }
7302           p = PieceToNumber((ChessSquare)p);
7303           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7304           board[p][BOARD_WIDTH-2]++;
7305           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7306         } else {
7307           p -= (int)WhitePawn;
7308           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7309                   captured = (ChessSquare) (DEMOTED captured);
7310                   p = DEMOTED p;
7311           }
7312           p = PieceToNumber((ChessSquare)p);
7313           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7314           board[BOARD_HEIGHT-1-p][1]++;
7315           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7316         }
7317       }
7318
7319     } else if (gameInfo.variant == VariantAtomic) {
7320       if (captured != EmptySquare) {
7321         int y, x;
7322         for (y = toY-1; y <= toY+1; y++) {
7323           for (x = toX-1; x <= toX+1; x++) {
7324             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7325                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7326               board[y][x] = EmptySquare;
7327             }
7328           }
7329         }
7330         board[toY][toX] = EmptySquare;
7331       }
7332     }
7333     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7334         /* [HGM] Shogi promotions */
7335         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7336     }
7337
7338     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7339                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7340         // [HGM] superchess: take promotion piece out of holdings
7341         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7342         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7343             if(!--board[k][BOARD_WIDTH-2])
7344                 board[k][BOARD_WIDTH-1] = EmptySquare;
7345         } else {
7346             if(!--board[BOARD_HEIGHT-1-k][1])
7347                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7348         }
7349     }
7350
7351 }
7352
7353 /* Updates forwardMostMove */
7354 void
7355 MakeMove(fromX, fromY, toX, toY, promoChar)
7356      int fromX, fromY, toX, toY;
7357      int promoChar;
7358 {
7359 //    forwardMostMove++; // [HGM] bare: moved downstream
7360
7361     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7362         int timeLeft; static int lastLoadFlag=0; int king, piece;
7363         piece = boards[forwardMostMove][fromY][fromX];
7364         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7365         if(gameInfo.variant == VariantKnightmate)
7366             king += (int) WhiteUnicorn - (int) WhiteKing;
7367         if(forwardMostMove == 0) {
7368             if(blackPlaysFirst) 
7369                 fprintf(serverMoves, "%s;", second.tidy);
7370             fprintf(serverMoves, "%s;", first.tidy);
7371             if(!blackPlaysFirst) 
7372                 fprintf(serverMoves, "%s;", second.tidy);
7373         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7374         lastLoadFlag = loadFlag;
7375         // print base move
7376         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7377         // print castling suffix
7378         if( toY == fromY && piece == king ) {
7379             if(toX-fromX > 1)
7380                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7381             if(fromX-toX >1)
7382                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7383         }
7384         // e.p. suffix
7385         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7386              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7387              boards[forwardMostMove][toY][toX] == EmptySquare
7388              && fromX != toX )
7389                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7390         // promotion suffix
7391         if(promoChar != NULLCHAR)
7392                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7393         if(!loadFlag) {
7394             fprintf(serverMoves, "/%d/%d",
7395                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7396             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7397             else                      timeLeft = blackTimeRemaining/1000;
7398             fprintf(serverMoves, "/%d", timeLeft);
7399         }
7400         fflush(serverMoves);
7401     }
7402
7403     if (forwardMostMove+1 >= MAX_MOVES) {
7404       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7405                         0, 1);
7406       return;
7407     }
7408     SwitchClocks();
7409     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7410     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7411     if (commentList[forwardMostMove+1] != NULL) {
7412         free(commentList[forwardMostMove+1]);
7413         commentList[forwardMostMove+1] = NULL;
7414     }
7415     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7416     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7417     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7418                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7419     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7420     gameInfo.result = GameUnfinished;
7421     if (gameInfo.resultDetails != NULL) {
7422         free(gameInfo.resultDetails);
7423         gameInfo.resultDetails = NULL;
7424     }
7425     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7426                               moveList[forwardMostMove - 1]);
7427     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7428                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7429                              fromY, fromX, toY, toX, promoChar,
7430                              parseList[forwardMostMove - 1]);
7431     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7432                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7433                             castlingRights[forwardMostMove]) ) {
7434       case MT_NONE:
7435       case MT_STALEMATE:
7436       default:
7437         break;
7438       case MT_CHECK:
7439         if(gameInfo.variant != VariantShogi)
7440             strcat(parseList[forwardMostMove - 1], "+");
7441         break;
7442       case MT_CHECKMATE:
7443       case MT_STAINMATE:
7444         strcat(parseList[forwardMostMove - 1], "#");
7445         break;
7446     }
7447     if (appData.debugMode) {
7448         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7449     }
7450
7451 }
7452
7453 /* Updates currentMove if not pausing */
7454 void
7455 ShowMove(fromX, fromY, toX, toY)
7456 {
7457     int instant = (gameMode == PlayFromGameFile) ?
7458         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7459     if(appData.noGUI) return;
7460     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7461         if (!instant) {
7462             if (forwardMostMove == currentMove + 1) {
7463                 AnimateMove(boards[forwardMostMove - 1],
7464                             fromX, fromY, toX, toY);
7465             }
7466             if (appData.highlightLastMove) {
7467                 SetHighlights(fromX, fromY, toX, toY);
7468             }
7469         }
7470         currentMove = forwardMostMove;
7471     }
7472
7473     if (instant) return;
7474
7475     DisplayMove(currentMove - 1);
7476     DrawPosition(FALSE, boards[currentMove]);
7477     DisplayBothClocks();
7478     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7479 }
7480
7481 void SendEgtPath(ChessProgramState *cps)
7482 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7483         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7484
7485         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7486
7487         while(*p) {
7488             char c, *q = name+1, *r, *s;
7489
7490             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7491             while(*p && *p != ',') *q++ = *p++;
7492             *q++ = ':'; *q = 0;
7493             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7494                 strcmp(name, ",nalimov:") == 0 ) {
7495                 // take nalimov path from the menu-changeable option first, if it is defined
7496                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7497                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7498             } else
7499             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7500                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7501                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7502                 s = r = StrStr(s, ":") + 1; // beginning of path info
7503                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7504                 c = *r; *r = 0;             // temporarily null-terminate path info
7505                     *--q = 0;               // strip of trailig ':' from name
7506                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7507                 *r = c;
7508                 SendToProgram(buf,cps);     // send egtbpath command for this format
7509             }
7510             if(*p == ',') p++; // read away comma to position for next format name
7511         }
7512 }
7513
7514 void
7515 InitChessProgram(cps, setup)
7516      ChessProgramState *cps;
7517      int setup; /* [HGM] needed to setup FRC opening position */
7518 {
7519     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7520     if (appData.noChessProgram) return;
7521     hintRequested = FALSE;
7522     bookRequested = FALSE;
7523
7524     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7525     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7526     if(cps->memSize) { /* [HGM] memory */
7527         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7528         SendToProgram(buf, cps);
7529     }
7530     SendEgtPath(cps); /* [HGM] EGT */
7531     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7532         sprintf(buf, "cores %d\n", appData.smpCores);
7533         SendToProgram(buf, cps);
7534     }
7535
7536     SendToProgram(cps->initString, cps);
7537     if (gameInfo.variant != VariantNormal &&
7538         gameInfo.variant != VariantLoadable
7539         /* [HGM] also send variant if board size non-standard */
7540         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7541                                             ) {
7542       char *v = VariantName(gameInfo.variant);
7543       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7544         /* [HGM] in protocol 1 we have to assume all variants valid */
7545         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7546         DisplayFatalError(buf, 0, 1);
7547         return;
7548       }
7549
7550       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7551       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7552       if( gameInfo.variant == VariantXiangqi )
7553            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7554       if( gameInfo.variant == VariantShogi )
7555            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7556       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7558       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7559                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7561       if( gameInfo.variant == VariantCourier )
7562            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7563       if( gameInfo.variant == VariantSuper )
7564            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7565       if( gameInfo.variant == VariantGreat )
7566            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7567
7568       if(overruled) {
7569            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7570                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7571            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7572            if(StrStr(cps->variants, b) == NULL) { 
7573                // specific sized variant not known, check if general sizing allowed
7574                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7575                    if(StrStr(cps->variants, "boardsize") == NULL) {
7576                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7577                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7578                        DisplayFatalError(buf, 0, 1);
7579                        return;
7580                    }
7581                    /* [HGM] here we really should compare with the maximum supported board size */
7582                }
7583            }
7584       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7585       sprintf(buf, "variant %s\n", b);
7586       SendToProgram(buf, cps);
7587     }
7588     currentlyInitializedVariant = gameInfo.variant;
7589
7590     /* [HGM] send opening position in FRC to first engine */
7591     if(setup) {
7592           SendToProgram("force\n", cps);
7593           SendBoard(cps, 0);
7594           /* engine is now in force mode! Set flag to wake it up after first move. */
7595           setboardSpoiledMachineBlack = 1;
7596     }
7597
7598     if (cps->sendICS) {
7599       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7600       SendToProgram(buf, cps);
7601     }
7602     cps->maybeThinking = FALSE;
7603     cps->offeredDraw = 0;
7604     if (!appData.icsActive) {
7605         SendTimeControl(cps, movesPerSession, timeControl,
7606                         timeIncrement, appData.searchDepth,
7607                         searchTime);
7608     }
7609     if (appData.showThinking 
7610         // [HGM] thinking: four options require thinking output to be sent
7611         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7612                                 ) {
7613         SendToProgram("post\n", cps);
7614     }
7615     SendToProgram("hard\n", cps);
7616     if (!appData.ponderNextMove) {
7617         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7618            it without being sure what state we are in first.  "hard"
7619            is not a toggle, so that one is OK.
7620          */
7621         SendToProgram("easy\n", cps);
7622     }
7623     if (cps->usePing) {
7624       sprintf(buf, "ping %d\n", ++cps->lastPing);
7625       SendToProgram(buf, cps);
7626     }
7627     cps->initDone = TRUE;
7628 }   
7629
7630
7631 void
7632 StartChessProgram(cps)
7633      ChessProgramState *cps;
7634 {
7635     char buf[MSG_SIZ];
7636     int err;
7637
7638     if (appData.noChessProgram) return;
7639     cps->initDone = FALSE;
7640
7641     if (strcmp(cps->host, "localhost") == 0) {
7642         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7643     } else if (*appData.remoteShell == NULLCHAR) {
7644         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7645     } else {
7646         if (*appData.remoteUser == NULLCHAR) {
7647           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7648                     cps->program);
7649         } else {
7650           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7651                     cps->host, appData.remoteUser, cps->program);
7652         }
7653         err = StartChildProcess(buf, "", &cps->pr);
7654     }
7655     
7656     if (err != 0) {
7657         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7658         DisplayFatalError(buf, err, 1);
7659         cps->pr = NoProc;
7660         cps->isr = NULL;
7661         return;
7662     }
7663     
7664     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7665     if (cps->protocolVersion > 1) {
7666       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7667       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7668       cps->comboCnt = 0;  //                and values of combo boxes
7669       SendToProgram(buf, cps);
7670     } else {
7671       SendToProgram("xboard\n", cps);
7672     }
7673 }
7674
7675
7676 void
7677 TwoMachinesEventIfReady P((void))
7678 {
7679   if (first.lastPing != first.lastPong) {
7680     DisplayMessage("", _("Waiting for first chess program"));
7681     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7682     return;
7683   }
7684   if (second.lastPing != second.lastPong) {
7685     DisplayMessage("", _("Waiting for second chess program"));
7686     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7687     return;
7688   }
7689   ThawUI();
7690   TwoMachinesEvent();
7691 }
7692
7693 void
7694 NextMatchGame P((void))
7695 {
7696     int index; /* [HGM] autoinc: step lod index during match */
7697     Reset(FALSE, TRUE);
7698     if (*appData.loadGameFile != NULLCHAR) {
7699         index = appData.loadGameIndex;
7700         if(index < 0) { // [HGM] autoinc
7701             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7702             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7703         } 
7704         LoadGameFromFile(appData.loadGameFile,
7705                          index,
7706                          appData.loadGameFile, FALSE);
7707     } else if (*appData.loadPositionFile != NULLCHAR) {
7708         index = appData.loadPositionIndex;
7709         if(index < 0) { // [HGM] autoinc
7710             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7711             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7712         } 
7713         LoadPositionFromFile(appData.loadPositionFile,
7714                              index,
7715                              appData.loadPositionFile);
7716     }
7717     TwoMachinesEventIfReady();
7718 }
7719
7720 void UserAdjudicationEvent( int result )
7721 {
7722     ChessMove gameResult = GameIsDrawn;
7723
7724     if( result > 0 ) {
7725         gameResult = WhiteWins;
7726     }
7727     else if( result < 0 ) {
7728         gameResult = BlackWins;
7729     }
7730
7731     if( gameMode == TwoMachinesPlay ) {
7732         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7733     }
7734 }
7735
7736
7737 void
7738 GameEnds(result, resultDetails, whosays)
7739      ChessMove result;
7740      char *resultDetails;
7741      int whosays;
7742 {
7743     GameMode nextGameMode;
7744     int isIcsGame;
7745     char buf[MSG_SIZ];
7746
7747     if(endingGame) return; /* [HGM] crash: forbid recursion */
7748     endingGame = 1;
7749
7750     if (appData.debugMode) {
7751       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7752               result, resultDetails ? resultDetails : "(null)", whosays);
7753     }
7754
7755     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7756         /* If we are playing on ICS, the server decides when the
7757            game is over, but the engine can offer to draw, claim 
7758            a draw, or resign. 
7759          */
7760 #if ZIPPY
7761         if (appData.zippyPlay && first.initDone) {
7762             if (result == GameIsDrawn) {
7763                 /* In case draw still needs to be claimed */
7764                 SendToICS(ics_prefix);
7765                 SendToICS("draw\n");
7766             } else if (StrCaseStr(resultDetails, "resign")) {
7767                 SendToICS(ics_prefix);
7768                 SendToICS("resign\n");
7769             }
7770         }
7771 #endif
7772         endingGame = 0; /* [HGM] crash */
7773         return;
7774     }
7775
7776     /* If we're loading the game from a file, stop */
7777     if (whosays == GE_FILE) {
7778       (void) StopLoadGameTimer();
7779       gameFileFP = NULL;
7780     }
7781
7782     /* Cancel draw offers */
7783     first.offeredDraw = second.offeredDraw = 0;
7784
7785     /* If this is an ICS game, only ICS can really say it's done;
7786        if not, anyone can. */
7787     isIcsGame = (gameMode == IcsPlayingWhite || 
7788                  gameMode == IcsPlayingBlack || 
7789                  gameMode == IcsObserving    || 
7790                  gameMode == IcsExamining);
7791
7792     if (!isIcsGame || whosays == GE_ICS) {
7793         /* OK -- not an ICS game, or ICS said it was done */
7794         StopClocks();
7795         if (!isIcsGame && !appData.noChessProgram) 
7796           SetUserThinkingEnables();
7797
7798         /* [HGM] if a machine claims the game end we verify this claim */
7799         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7800             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7801                 char claimer;
7802                 ChessMove trueResult = (ChessMove) -1;
7803
7804                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7805                                             first.twoMachinesColor[0] :
7806                                             second.twoMachinesColor[0] ;
7807
7808                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7809                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7810                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7811                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7812                 } else
7813                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7814                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7815                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7816                 } else
7817                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7818                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7819                 }
7820
7821                 // now verify win claims, but not in drop games, as we don't understand those yet
7822                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7823                                                  || gameInfo.variant == VariantGreat) &&
7824                     (result == WhiteWins && claimer == 'w' ||
7825                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7826                       if (appData.debugMode) {
7827                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7828                                 result, epStatus[forwardMostMove], forwardMostMove);
7829                       }
7830                       if(result != trueResult) {
7831                               sprintf(buf, "False win claim: '%s'", resultDetails);
7832                               result = claimer == 'w' ? BlackWins : WhiteWins;
7833                               resultDetails = buf;
7834                       }
7835                 } else
7836                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7837                     && (forwardMostMove <= backwardMostMove ||
7838                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7839                         (claimer=='b')==(forwardMostMove&1))
7840                                                                                   ) {
7841                       /* [HGM] verify: draws that were not flagged are false claims */
7842                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7843                       result = claimer == 'w' ? BlackWins : WhiteWins;
7844                       resultDetails = buf;
7845                 }
7846                 /* (Claiming a loss is accepted no questions asked!) */
7847             }
7848
7849             /* [HGM] bare: don't allow bare King to win */
7850             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7851                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7852                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7853                && result != GameIsDrawn)
7854             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7855                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7856                         int p = (int)boards[forwardMostMove][i][j] - color;
7857                         if(p >= 0 && p <= (int)WhiteKing) k++;
7858                 }
7859                 if (appData.debugMode) {
7860                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7861                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7862                 }
7863                 if(k <= 1) {
7864                         result = GameIsDrawn;
7865                         sprintf(buf, "%s but bare king", resultDetails);
7866                         resultDetails = buf;
7867                 }
7868             }
7869         }
7870
7871         if(serverMoves != NULL && !loadFlag) { char c = '=';
7872             if(result==WhiteWins) c = '+';
7873             if(result==BlackWins) c = '-';
7874             if(resultDetails != NULL)
7875                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7876         }
7877         if (resultDetails != NULL) {
7878             gameInfo.result = result;
7879             gameInfo.resultDetails = StrSave(resultDetails);
7880
7881             /* display last move only if game was not loaded from file */
7882             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7883                 DisplayMove(currentMove - 1);
7884     
7885             if (forwardMostMove != 0) {
7886                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7887                     if (*appData.saveGameFile != NULLCHAR) {
7888                         SaveGameToFile(appData.saveGameFile, TRUE);
7889                     } else if (appData.autoSaveGames) {
7890                         AutoSaveGame();
7891                     }
7892                     if (*appData.savePositionFile != NULLCHAR) {
7893                         SavePositionToFile(appData.savePositionFile);
7894                     }
7895                 }
7896             }
7897
7898             /* Tell program how game ended in case it is learning */
7899             /* [HGM] Moved this to after saving the PGN, just in case */
7900             /* engine died and we got here through time loss. In that */
7901             /* case we will get a fatal error writing the pipe, which */
7902             /* would otherwise lose us the PGN.                       */
7903             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7904             /* output during GameEnds should never be fatal anymore   */
7905             if (gameMode == MachinePlaysWhite ||
7906                 gameMode == MachinePlaysBlack ||
7907                 gameMode == TwoMachinesPlay ||
7908                 gameMode == IcsPlayingWhite ||
7909                 gameMode == IcsPlayingBlack ||
7910                 gameMode == BeginningOfGame) {
7911                 char buf[MSG_SIZ];
7912                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7913                         resultDetails);
7914                 if (first.pr != NoProc) {
7915                     SendToProgram(buf, &first);
7916                 }
7917                 if (second.pr != NoProc &&
7918                     gameMode == TwoMachinesPlay) {
7919                     SendToProgram(buf, &second);
7920                 }
7921             }
7922         }
7923
7924         if (appData.icsActive) {
7925             if (appData.quietPlay &&
7926                 (gameMode == IcsPlayingWhite ||
7927                  gameMode == IcsPlayingBlack)) {
7928                 SendToICS(ics_prefix);
7929                 SendToICS("set shout 1\n");
7930             }
7931             nextGameMode = IcsIdle;
7932             ics_user_moved = FALSE;
7933             /* clean up premove.  It's ugly when the game has ended and the
7934              * premove highlights are still on the board.
7935              */
7936             if (gotPremove) {
7937               gotPremove = FALSE;
7938               ClearPremoveHighlights();
7939               DrawPosition(FALSE, boards[currentMove]);
7940             }
7941             if (whosays == GE_ICS) {
7942                 switch (result) {
7943                 case WhiteWins:
7944                     if (gameMode == IcsPlayingWhite)
7945                         PlayIcsWinSound();
7946                     else if(gameMode == IcsPlayingBlack)
7947                         PlayIcsLossSound();
7948                     break;
7949                 case BlackWins:
7950                     if (gameMode == IcsPlayingBlack)
7951                         PlayIcsWinSound();
7952                     else if(gameMode == IcsPlayingWhite)
7953                         PlayIcsLossSound();
7954                     break;
7955                 case GameIsDrawn:
7956                     PlayIcsDrawSound();
7957                     break;
7958                 default:
7959                     PlayIcsUnfinishedSound();
7960                 }
7961             }
7962         } else if (gameMode == EditGame ||
7963                    gameMode == PlayFromGameFile || 
7964                    gameMode == AnalyzeMode || 
7965                    gameMode == AnalyzeFile) {
7966             nextGameMode = gameMode;
7967         } else {
7968             nextGameMode = EndOfGame;
7969         }
7970         pausing = FALSE;
7971         ModeHighlight();
7972     } else {
7973         nextGameMode = gameMode;
7974     }
7975
7976     if (appData.noChessProgram) {
7977         gameMode = nextGameMode;
7978         ModeHighlight();
7979         endingGame = 0; /* [HGM] crash */
7980         return;
7981     }
7982
7983     if (first.reuse) {
7984         /* Put first chess program into idle state */
7985         if (first.pr != NoProc &&
7986             (gameMode == MachinePlaysWhite ||
7987              gameMode == MachinePlaysBlack ||
7988              gameMode == TwoMachinesPlay ||
7989              gameMode == IcsPlayingWhite ||
7990              gameMode == IcsPlayingBlack ||
7991              gameMode == BeginningOfGame)) {
7992             SendToProgram("force\n", &first);
7993             if (first.usePing) {
7994               char buf[MSG_SIZ];
7995               sprintf(buf, "ping %d\n", ++first.lastPing);
7996               SendToProgram(buf, &first);
7997             }
7998         }
7999     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8000         /* Kill off first chess program */
8001         if (first.isr != NULL)
8002           RemoveInputSource(first.isr);
8003         first.isr = NULL;
8004     
8005         if (first.pr != NoProc) {
8006             ExitAnalyzeMode();
8007             DoSleep( appData.delayBeforeQuit );
8008             SendToProgram("quit\n", &first);
8009             DoSleep( appData.delayAfterQuit );
8010             DestroyChildProcess(first.pr, first.useSigterm);
8011         }
8012         first.pr = NoProc;
8013     }
8014     if (second.reuse) {
8015         /* Put second chess program into idle state */
8016         if (second.pr != NoProc &&
8017             gameMode == TwoMachinesPlay) {
8018             SendToProgram("force\n", &second);
8019             if (second.usePing) {
8020               char buf[MSG_SIZ];
8021               sprintf(buf, "ping %d\n", ++second.lastPing);
8022               SendToProgram(buf, &second);
8023             }
8024         }
8025     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8026         /* Kill off second chess program */
8027         if (second.isr != NULL)
8028           RemoveInputSource(second.isr);
8029         second.isr = NULL;
8030     
8031         if (second.pr != NoProc) {
8032             DoSleep( appData.delayBeforeQuit );
8033             SendToProgram("quit\n", &second);
8034             DoSleep( appData.delayAfterQuit );
8035             DestroyChildProcess(second.pr, second.useSigterm);
8036         }
8037         second.pr = NoProc;
8038     }
8039
8040     if (matchMode && gameMode == TwoMachinesPlay) {
8041         switch (result) {
8042         case WhiteWins:
8043           if (first.twoMachinesColor[0] == 'w') {
8044             first.matchWins++;
8045           } else {
8046             second.matchWins++;
8047           }
8048           break;
8049         case BlackWins:
8050           if (first.twoMachinesColor[0] == 'b') {
8051             first.matchWins++;
8052           } else {
8053             second.matchWins++;
8054           }
8055           break;
8056         default:
8057           break;
8058         }
8059         if (matchGame < appData.matchGames) {
8060             char *tmp;
8061             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8062                 tmp = first.twoMachinesColor;
8063                 first.twoMachinesColor = second.twoMachinesColor;
8064                 second.twoMachinesColor = tmp;
8065             }
8066             gameMode = nextGameMode;
8067             matchGame++;
8068             if(appData.matchPause>10000 || appData.matchPause<10)
8069                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8070             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8071             endingGame = 0; /* [HGM] crash */
8072             return;
8073         } else {
8074             char buf[MSG_SIZ];
8075             gameMode = nextGameMode;
8076             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8077                     first.tidy, second.tidy,
8078                     first.matchWins, second.matchWins,
8079                     appData.matchGames - (first.matchWins + second.matchWins));
8080             DisplayFatalError(buf, 0, 0);
8081         }
8082     }
8083     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8084         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8085       ExitAnalyzeMode();
8086     gameMode = nextGameMode;
8087     ModeHighlight();
8088     endingGame = 0;  /* [HGM] crash */
8089 }
8090
8091 /* Assumes program was just initialized (initString sent).
8092    Leaves program in force mode. */
8093 void
8094 FeedMovesToProgram(cps, upto) 
8095      ChessProgramState *cps;
8096      int upto;
8097 {
8098     int i;
8099     
8100     if (appData.debugMode)
8101       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8102               startedFromSetupPosition ? "position and " : "",
8103               backwardMostMove, upto, cps->which);
8104     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8105         // [HGM] variantswitch: make engine aware of new variant
8106         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8107                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8108         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8109         SendToProgram(buf, cps);
8110         currentlyInitializedVariant = gameInfo.variant;
8111     }
8112     SendToProgram("force\n", cps);
8113     if (startedFromSetupPosition) {
8114         SendBoard(cps, backwardMostMove);
8115     if (appData.debugMode) {
8116         fprintf(debugFP, "feedMoves\n");
8117     }
8118     }
8119     for (i = backwardMostMove; i < upto; i++) {
8120         SendMoveToProgram(i, cps);
8121     }
8122 }
8123
8124
8125 void
8126 ResurrectChessProgram()
8127 {
8128      /* The chess program may have exited.
8129         If so, restart it and feed it all the moves made so far. */
8130
8131     if (appData.noChessProgram || first.pr != NoProc) return;
8132     
8133     StartChessProgram(&first);
8134     InitChessProgram(&first, FALSE);
8135     FeedMovesToProgram(&first, currentMove);
8136
8137     if (!first.sendTime) {
8138         /* can't tell gnuchess what its clock should read,
8139            so we bow to its notion. */
8140         ResetClocks();
8141         timeRemaining[0][currentMove] = whiteTimeRemaining;
8142         timeRemaining[1][currentMove] = blackTimeRemaining;
8143     }
8144
8145     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8146                 appData.icsEngineAnalyze) && first.analysisSupport) {
8147       SendToProgram("analyze\n", &first);
8148       first.analyzing = TRUE;
8149     }
8150 }
8151
8152 /*
8153  * Button procedures
8154  */
8155 void
8156 Reset(redraw, init)
8157      int redraw, init;
8158 {
8159     int i;
8160     printf ("DEBUG: in reset\n");
8161
8162     if (appData.debugMode) {
8163         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8164                 redraw, init, gameMode);
8165     }
8166     pausing = pauseExamInvalid = FALSE;
8167     startedFromSetupPosition = blackPlaysFirst = FALSE;
8168     firstMove = TRUE;
8169     whiteFlag = blackFlag = FALSE;
8170     userOfferedDraw = FALSE;
8171     hintRequested = bookRequested = FALSE;
8172     first.maybeThinking = FALSE;
8173     second.maybeThinking = FALSE;
8174     first.bookSuspend = FALSE; // [HGM] book
8175     second.bookSuspend = FALSE;
8176     thinkOutput[0] = NULLCHAR;
8177     lastHint[0] = NULLCHAR;
8178     ClearGameInfo(&gameInfo);
8179     gameInfo.variant = StringToVariant(appData.variant);
8180     ics_user_moved = ics_clock_paused = FALSE;
8181     ics_getting_history = H_FALSE;
8182     ics_gamenum = -1;
8183     white_holding[0] = black_holding[0] = NULLCHAR;
8184     ClearProgramStats();
8185     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8186
8187     printf ("DEBUG: in reset 1\n");
8188
8189     
8190     ResetFrontEnd();
8191     ClearHighlights();
8192     flipView = appData.flipView;
8193     ClearPremoveHighlights();
8194     gotPremove = FALSE;
8195     alarmSounded = FALSE;
8196
8197     printf ("DEBUG: in reset 2\n");
8198
8199
8200     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8201     printf ("DEBUG: in reset2.1\n");
8202     if(appData.serverMovesName != NULL) {
8203         /* [HGM] prepare to make moves file for broadcasting */
8204         clock_t t = clock();
8205         if(serverMoves != NULL) fclose(serverMoves);
8206         serverMoves = fopen(appData.serverMovesName, "r");
8207         if(serverMoves != NULL) {
8208             fclose(serverMoves);
8209             /* delay 15 sec before overwriting, so all clients can see end */
8210             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8211         }
8212         serverMoves = fopen(appData.serverMovesName, "w");
8213     }
8214
8215     printf ("DEBUG: in reset 3\n");
8216
8217     ExitAnalyzeMode();
8218     gameMode = BeginningOfGame;
8219     ModeHighlight();
8220     printf ("DEBUG: in reset 3.0.1\n");
8221
8222     if(appData.icsActive) gameInfo.variant = VariantNormal;
8223     InitPosition(redraw);
8224     printf ("DEBUG: in reset 3.0.1.1\n");
8225     for (i = 0; i < MAX_MOVES; i++) {
8226         if (commentList[i] != NULL) {
8227             free(commentList[i]);
8228             commentList[i] = NULL;
8229         }
8230     }
8231     printf ("DEBUG: in reset 3.1\n");
8232
8233     ResetClocks();
8234     timeRemaining[0][0] = whiteTimeRemaining;
8235     timeRemaining[1][0] = blackTimeRemaining;
8236     if (first.pr == NULL) {
8237         StartChessProgram(&first);
8238     }
8239     if (init) {
8240             InitChessProgram(&first, startedFromSetupPosition);
8241     }
8242     
8243     printf ("DEBUG: in reset 4\n");
8244         
8245
8246     GUI_DisplayTitle("");
8247     printf ("DEBUG: in reset 5\n");
8248     DisplayMessage("", "");
8249     printf ("DEBUG: in reset 6\n");
8250     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8251
8252     printf ("DEBUG: end reset \n");
8253 }
8254
8255 void
8256 AutoPlayGameLoop()
8257 {
8258     for (;;) {
8259         if (!AutoPlayOneMove())
8260           return;
8261         if (matchMode || appData.timeDelay == 0)
8262           continue;
8263         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8264           return;
8265         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8266         break;
8267     }
8268 }
8269
8270
8271 int
8272 AutoPlayOneMove()
8273 {
8274     int fromX, fromY, toX, toY;
8275
8276     if (appData.debugMode) {
8277       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8278     }
8279
8280     if (gameMode != PlayFromGameFile)
8281       return FALSE;
8282
8283     if (currentMove >= forwardMostMove) {
8284       gameMode = EditGame;
8285       ModeHighlight();
8286
8287       /* [AS] Clear current move marker at the end of a game */
8288       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8289
8290       return FALSE;
8291     }
8292     
8293     toX = moveList[currentMove][2] - AAA;
8294     toY = moveList[currentMove][3] - ONE;
8295
8296     if (moveList[currentMove][1] == '@') {
8297         if (appData.highlightLastMove) {
8298             SetHighlights(-1, -1, toX, toY);
8299         }
8300     } else {
8301         fromX = moveList[currentMove][0] - AAA;
8302         fromY = moveList[currentMove][1] - ONE;
8303
8304         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8305
8306         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8307
8308         if (appData.highlightLastMove) {
8309             SetHighlights(fromX, fromY, toX, toY);
8310         }
8311     }
8312     DisplayMove(currentMove);
8313     SendMoveToProgram(currentMove++, &first);
8314     DisplayBothClocks();
8315     DrawPosition(FALSE, boards[currentMove]);
8316     // [HGM] PV info: always display, routine tests if empty
8317     DisplayComment(currentMove - 1, commentList[currentMove]);
8318     return TRUE;
8319 }
8320
8321
8322 int
8323 LoadGameOneMove(readAhead)
8324      ChessMove readAhead;
8325 {
8326     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8327     char promoChar = NULLCHAR;
8328     ChessMove moveType;
8329     char move[MSG_SIZ];
8330     char *p, *q;
8331     
8332     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8333         gameMode != AnalyzeMode && gameMode != Training) {
8334         gameFileFP = NULL;
8335         return FALSE;
8336     }
8337     
8338     yyboardindex = forwardMostMove;
8339     if (readAhead != (ChessMove)0) {
8340       moveType = readAhead;
8341     } else {
8342       if (gameFileFP == NULL)
8343           return FALSE;
8344       moveType = (ChessMove) yylex();
8345     }
8346     
8347     done = FALSE;
8348     switch (moveType) {
8349       case Comment:
8350         if (appData.debugMode) 
8351           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8352         p = yy_text;
8353         if (*p == '{' || *p == '[' || *p == '(') {
8354             p[strlen(p) - 1] = NULLCHAR;
8355             p++;
8356         }
8357
8358         /* append the comment but don't display it */
8359         while (*p == '\n') p++;
8360         AppendComment(currentMove, p);
8361         return TRUE;
8362
8363       case WhiteCapturesEnPassant:
8364       case BlackCapturesEnPassant:
8365       case WhitePromotionChancellor:
8366       case BlackPromotionChancellor:
8367       case WhitePromotionArchbishop:
8368       case BlackPromotionArchbishop:
8369       case WhitePromotionCentaur:
8370       case BlackPromotionCentaur:
8371       case WhitePromotionQueen:
8372       case BlackPromotionQueen:
8373       case WhitePromotionRook:
8374       case BlackPromotionRook:
8375       case WhitePromotionBishop:
8376       case BlackPromotionBishop:
8377       case WhitePromotionKnight:
8378       case BlackPromotionKnight:
8379       case WhitePromotionKing:
8380       case BlackPromotionKing:
8381       case NormalMove:
8382       case WhiteKingSideCastle:
8383       case WhiteQueenSideCastle:
8384       case BlackKingSideCastle:
8385       case BlackQueenSideCastle:
8386       case WhiteKingSideCastleWild:
8387       case WhiteQueenSideCastleWild:
8388       case BlackKingSideCastleWild:
8389       case BlackQueenSideCastleWild:
8390       /* PUSH Fabien */
8391       case WhiteHSideCastleFR:
8392       case WhiteASideCastleFR:
8393       case BlackHSideCastleFR:
8394       case BlackASideCastleFR:
8395       /* POP Fabien */
8396         if (appData.debugMode)
8397           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8398         fromX = currentMoveString[0] - AAA;
8399         fromY = currentMoveString[1] - ONE;
8400         toX = currentMoveString[2] - AAA;
8401         toY = currentMoveString[3] - ONE;
8402         promoChar = currentMoveString[4];
8403         break;
8404
8405       case WhiteDrop:
8406       case BlackDrop:
8407         if (appData.debugMode)
8408           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8409         fromX = moveType == WhiteDrop ?
8410           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8411         (int) CharToPiece(ToLower(currentMoveString[0]));
8412         fromY = DROP_RANK;
8413         toX = currentMoveString[2] - AAA;
8414         toY = currentMoveString[3] - ONE;
8415         break;
8416
8417       case WhiteWins:
8418       case BlackWins:
8419       case GameIsDrawn:
8420       case GameUnfinished:
8421         if (appData.debugMode)
8422           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8423         p = strchr(yy_text, '{');
8424         if (p == NULL) p = strchr(yy_text, '(');
8425         if (p == NULL) {
8426             p = yy_text;
8427             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8428         } else {
8429             q = strchr(p, *p == '{' ? '}' : ')');
8430             if (q != NULL) *q = NULLCHAR;
8431             p++;
8432         }
8433         GameEnds(moveType, p, GE_FILE);
8434         done = TRUE;
8435         if (cmailMsgLoaded) {
8436             ClearHighlights();
8437             flipView = WhiteOnMove(currentMove);
8438             if (moveType == GameUnfinished) flipView = !flipView;
8439             if (appData.debugMode)
8440               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8441         }
8442         break;
8443
8444       case (ChessMove) 0:       /* end of file */
8445         if (appData.debugMode)
8446           fprintf(debugFP, "Parser hit end of file\n");
8447         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8448                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8449           case MT_NONE:
8450           case MT_CHECK:
8451             break;
8452           case MT_CHECKMATE:
8453           case MT_STAINMATE:
8454             if (WhiteOnMove(currentMove)) {
8455                 GameEnds(BlackWins, "Black mates", GE_FILE);
8456             } else {
8457                 GameEnds(WhiteWins, "White mates", GE_FILE);
8458             }
8459             break;
8460           case MT_STALEMATE:
8461             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8462             break;
8463         }
8464         done = TRUE;
8465         break;
8466
8467       case MoveNumberOne:
8468         if (lastLoadGameStart == GNUChessGame) {
8469             /* GNUChessGames have numbers, but they aren't move numbers */
8470             if (appData.debugMode)
8471               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8472                       yy_text, (int) moveType);
8473             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8474         }
8475         /* else fall thru */
8476
8477       case XBoardGame:
8478       case GNUChessGame:
8479       case PGNTag:
8480         /* Reached start of next game in file */
8481         if (appData.debugMode)
8482           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8483         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8484                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8485           case MT_NONE:
8486           case MT_CHECK:
8487             break;
8488           case MT_CHECKMATE:
8489           case MT_STAINMATE:
8490             if (WhiteOnMove(currentMove)) {
8491                 GameEnds(BlackWins, "Black mates", GE_FILE);
8492             } else {
8493                 GameEnds(WhiteWins, "White mates", GE_FILE);
8494             }
8495             break;
8496           case MT_STALEMATE:
8497             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8498             break;
8499         }
8500         done = TRUE;
8501         break;
8502
8503       case PositionDiagram:     /* should not happen; ignore */
8504       case ElapsedTime:         /* ignore */
8505       case NAG:                 /* ignore */
8506         if (appData.debugMode)
8507           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8508                   yy_text, (int) moveType);
8509         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8510
8511       case IllegalMove:
8512         if (appData.testLegality) {
8513             if (appData.debugMode)
8514               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8515             sprintf(move, _("Illegal move: %d.%s%s"),
8516                     (forwardMostMove / 2) + 1,
8517                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8518             DisplayError(move, 0);
8519             done = TRUE;
8520         } else {
8521             if (appData.debugMode)
8522               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8523                       yy_text, currentMoveString);
8524             fromX = currentMoveString[0] - AAA;
8525             fromY = currentMoveString[1] - ONE;
8526             toX = currentMoveString[2] - AAA;
8527             toY = currentMoveString[3] - ONE;
8528             promoChar = currentMoveString[4];
8529         }
8530         break;
8531
8532       case AmbiguousMove:
8533         if (appData.debugMode)
8534           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8535         sprintf(move, _("Ambiguous move: %d.%s%s"),
8536                 (forwardMostMove / 2) + 1,
8537                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8538         DisplayError(move, 0);
8539         done = TRUE;
8540         break;
8541
8542       default:
8543       case ImpossibleMove:
8544         if (appData.debugMode)
8545           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8546         sprintf(move, _("Illegal move: %d.%s%s"),
8547                 (forwardMostMove / 2) + 1,
8548                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8549         DisplayError(move, 0);
8550         done = TRUE;
8551         break;
8552     }
8553
8554     if (done) {
8555         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8556             DrawPosition(FALSE, boards[currentMove]);
8557             DisplayBothClocks();
8558             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8559               DisplayComment(currentMove - 1, commentList[currentMove]);
8560         }
8561         (void) StopLoadGameTimer();
8562         gameFileFP = NULL;
8563         cmailOldMove = forwardMostMove;
8564         return FALSE;
8565     } else {
8566         /* currentMoveString is set as a side-effect of yylex */
8567         strcat(currentMoveString, "\n");
8568         strcpy(moveList[forwardMostMove], currentMoveString);
8569         
8570         thinkOutput[0] = NULLCHAR;
8571         MakeMove(fromX, fromY, toX, toY, promoChar);
8572         currentMove = forwardMostMove;
8573         return TRUE;
8574     }
8575 }
8576
8577 /* Load the nth game from the given file */
8578 int
8579 LoadGameFromFile(filename, n, title, useList)
8580      char *filename;
8581      int n;
8582      char *title;
8583      /*Boolean*/ int useList;
8584 {
8585     FILE *f;
8586     char buf[MSG_SIZ];
8587
8588     if (strcmp(filename, "-") == 0) {
8589         f = stdin;
8590         title = "stdin";
8591     } else {
8592         f = fopen(filename, "rb");
8593         if (f == NULL) {
8594           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8595             DisplayError(buf, errno);
8596             return FALSE;
8597         }
8598     }
8599     if (fseek(f, 0, 0) == -1) {
8600         /* f is not seekable; probably a pipe */
8601         useList = FALSE;
8602     }
8603     if (useList && n == 0) {
8604         int error = GameListBuild(f);
8605         if (error) {
8606             DisplayError(_("Cannot build game list"), error);
8607         } else if (!ListEmpty(&gameList) &&
8608                    ((ListGame *) gameList.tailPred)->number > 1) {
8609             GameListPopUp(f, title);
8610             return TRUE;
8611         }
8612         GameListDestroy();
8613         n = 1;
8614     }
8615     if (n == 0) n = 1;
8616     return LoadGame(f, n, title, FALSE);
8617 }
8618
8619
8620 void
8621 MakeRegisteredMove()
8622 {
8623     int fromX, fromY, toX, toY;
8624     char promoChar;
8625     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8626         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8627           case CMAIL_MOVE:
8628           case CMAIL_DRAW:
8629             if (appData.debugMode)
8630               fprintf(debugFP, "Restoring %s for game %d\n",
8631                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8632     
8633             thinkOutput[0] = NULLCHAR;
8634             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8635             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8636             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8637             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8638             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8639             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8640             MakeMove(fromX, fromY, toX, toY, promoChar);
8641             ShowMove(fromX, fromY, toX, toY);
8642               
8643             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8644                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8645               case MT_NONE:
8646               case MT_CHECK:
8647                 break;
8648                 
8649               case MT_CHECKMATE:
8650               case MT_STAINMATE:
8651                 if (WhiteOnMove(currentMove)) {
8652                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8653                 } else {
8654                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8655                 }
8656                 break;
8657                 
8658               case MT_STALEMATE:
8659                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8660                 break;
8661             }
8662
8663             break;
8664             
8665           case CMAIL_RESIGN:
8666             if (WhiteOnMove(currentMove)) {
8667                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8668             } else {
8669                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8670             }
8671             break;
8672             
8673           case CMAIL_ACCEPT:
8674             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8675             break;
8676               
8677           default:
8678             break;
8679         }
8680     }
8681
8682     return;
8683 }
8684
8685 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8686 int
8687 CmailLoadGame(f, gameNumber, title, useList)
8688      FILE *f;
8689      int gameNumber;
8690      char *title;
8691      int useList;
8692 {
8693     int retVal;
8694
8695     if (gameNumber > nCmailGames) {
8696         DisplayError(_("No more games in this message"), 0);
8697         return FALSE;
8698     }
8699     if (f == lastLoadGameFP) {
8700         int offset = gameNumber - lastLoadGameNumber;
8701         if (offset == 0) {
8702             cmailMsg[0] = NULLCHAR;
8703             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8704                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8705                 nCmailMovesRegistered--;
8706             }
8707             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8708             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8709                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8710             }
8711         } else {
8712             if (! RegisterMove()) return FALSE;
8713         }
8714     }
8715
8716     retVal = LoadGame(f, gameNumber, title, useList);
8717
8718     /* Make move registered during previous look at this game, if any */
8719     MakeRegisteredMove();
8720
8721     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8722         commentList[currentMove]
8723           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8724         DisplayComment(currentMove - 1, commentList[currentMove]);
8725     }
8726
8727     return retVal;
8728 }
8729
8730 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8731 int
8732 ReloadGame(offset)
8733      int offset;
8734 {
8735     int gameNumber = lastLoadGameNumber + offset;
8736     if (lastLoadGameFP == NULL) {
8737         DisplayError(_("No game has been loaded yet"), 0);
8738         return FALSE;
8739     }
8740     if (gameNumber <= 0) {
8741         DisplayError(_("Can't back up any further"), 0);
8742         return FALSE;
8743     }
8744     if (cmailMsgLoaded) {
8745         return CmailLoadGame(lastLoadGameFP, gameNumber,
8746                              lastLoadGameTitle, lastLoadGameUseList);
8747     } else {
8748         return LoadGame(lastLoadGameFP, gameNumber,
8749                         lastLoadGameTitle, lastLoadGameUseList);
8750     }
8751 }
8752
8753
8754
8755 /* Load the nth game from open file f */
8756 int
8757 LoadGame(f, gameNumber, title, useList)
8758      FILE *f;
8759      int gameNumber;
8760      char *title;
8761      int useList;
8762 {
8763     ChessMove cm;
8764     char buf[MSG_SIZ];
8765     int gn = gameNumber;
8766     ListGame *lg = NULL;
8767     int numPGNTags = 0;
8768     int err;
8769     GameMode oldGameMode;
8770     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8771
8772     if (appData.debugMode) 
8773         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8774
8775     if (gameMode == Training )
8776         SetTrainingModeOff();
8777
8778     oldGameMode = gameMode;
8779     if (gameMode != BeginningOfGame) {
8780       Reset(FALSE, TRUE);
8781     }
8782
8783     gameFileFP = f;
8784     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8785         fclose(lastLoadGameFP);
8786     }
8787
8788     if (useList) {
8789         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8790         
8791         if (lg) {
8792             fseek(f, lg->offset, 0);
8793             GameListHighlight(gameNumber);
8794             gn = 1;
8795         }
8796         else {
8797             DisplayError(_("Game number out of range"), 0);
8798             return FALSE;
8799         }
8800     } else {
8801         GameListDestroy();
8802         if (fseek(f, 0, 0) == -1) {
8803             if (f == lastLoadGameFP ?
8804                 gameNumber == lastLoadGameNumber + 1 :
8805                 gameNumber == 1) {
8806                 gn = 1;
8807             } else {
8808                 DisplayError(_("Can't seek on game file"), 0);
8809                 return FALSE;
8810             }
8811         }
8812     }
8813     lastLoadGameFP = f;
8814     lastLoadGameNumber = gameNumber;
8815     strcpy(lastLoadGameTitle, title);
8816     lastLoadGameUseList = useList;
8817
8818     yynewfile(f);
8819
8820     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8821       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8822                 lg->gameInfo.black);
8823             DisplayTitle(buf);
8824     } else if (*title != NULLCHAR) {
8825         if (gameNumber > 1) {
8826             sprintf(buf, "%s %d", title, gameNumber);
8827             DisplayTitle(buf);
8828         } else {
8829             DisplayTitle(title);
8830         }
8831     }
8832
8833     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8834         gameMode = PlayFromGameFile;
8835         ModeHighlight();
8836     }
8837
8838     currentMove = forwardMostMove = backwardMostMove = 0;
8839     CopyBoard(boards[0], initialPosition);
8840     StopClocks();
8841
8842     /*
8843      * Skip the first gn-1 games in the file.
8844      * Also skip over anything that precedes an identifiable 
8845      * start of game marker, to avoid being confused by 
8846      * garbage at the start of the file.  Currently 
8847      * recognized start of game markers are the move number "1",
8848      * the pattern "gnuchess .* game", the pattern
8849      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8850      * A game that starts with one of the latter two patterns
8851      * will also have a move number 1, possibly
8852      * following a position diagram.
8853      * 5-4-02: Let's try being more lenient and allowing a game to
8854      * start with an unnumbered move.  Does that break anything?
8855      */
8856     cm = lastLoadGameStart = (ChessMove) 0;
8857     while (gn > 0) {
8858         yyboardindex = forwardMostMove;
8859         cm = (ChessMove) yylex();
8860         switch (cm) {
8861           case (ChessMove) 0:
8862             if (cmailMsgLoaded) {
8863                 nCmailGames = CMAIL_MAX_GAMES - gn;
8864             } else {
8865                 Reset(TRUE, TRUE);
8866                 DisplayError(_("Game not found in file"), 0);
8867             }
8868             return FALSE;
8869
8870           case GNUChessGame:
8871           case XBoardGame:
8872             gn--;
8873             lastLoadGameStart = cm;
8874             break;
8875             
8876           case MoveNumberOne:
8877             switch (lastLoadGameStart) {
8878               case GNUChessGame:
8879               case XBoardGame:
8880               case PGNTag:
8881                 break;
8882               case MoveNumberOne:
8883               case (ChessMove) 0:
8884                 gn--;           /* count this game */
8885                 lastLoadGameStart = cm;
8886                 break;
8887               default:
8888                 /* impossible */
8889                 break;
8890             }
8891             break;
8892
8893           case PGNTag:
8894             switch (lastLoadGameStart) {
8895               case GNUChessGame:
8896               case PGNTag:
8897               case MoveNumberOne:
8898               case (ChessMove) 0:
8899                 gn--;           /* count this game */
8900                 lastLoadGameStart = cm;
8901                 break;
8902               case XBoardGame:
8903                 lastLoadGameStart = cm; /* game counted already */
8904                 break;
8905               default:
8906                 /* impossible */
8907                 break;
8908             }
8909             if (gn > 0) {
8910                 do {
8911                     yyboardindex = forwardMostMove;
8912                     cm = (ChessMove) yylex();
8913                 } while (cm == PGNTag || cm == Comment);
8914             }
8915             break;
8916
8917           case WhiteWins:
8918           case BlackWins:
8919           case GameIsDrawn:
8920             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8921                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8922                     != CMAIL_OLD_RESULT) {
8923                     nCmailResults ++ ;
8924                     cmailResult[  CMAIL_MAX_GAMES
8925                                 - gn - 1] = CMAIL_OLD_RESULT;
8926                 }
8927             }
8928             break;
8929
8930           case NormalMove:
8931             /* Only a NormalMove can be at the start of a game
8932              * without a position diagram. */
8933             if (lastLoadGameStart == (ChessMove) 0) {
8934               gn--;
8935               lastLoadGameStart = MoveNumberOne;
8936             }
8937             break;
8938
8939           default:
8940             break;
8941         }
8942     }
8943     
8944     if (appData.debugMode)
8945       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8946
8947     if (cm == XBoardGame) {
8948         /* Skip any header junk before position diagram and/or move 1 */
8949         for (;;) {
8950             yyboardindex = forwardMostMove;
8951             cm = (ChessMove) yylex();
8952
8953             if (cm == (ChessMove) 0 ||
8954                 cm == GNUChessGame || cm == XBoardGame) {
8955                 /* Empty game; pretend end-of-file and handle later */
8956                 cm = (ChessMove) 0;
8957                 break;
8958             }
8959
8960             if (cm == MoveNumberOne || cm == PositionDiagram ||
8961                 cm == PGNTag || cm == Comment)
8962               break;
8963         }
8964     } else if (cm == GNUChessGame) {
8965         if (gameInfo.event != NULL) {
8966             free(gameInfo.event);
8967         }
8968         gameInfo.event = StrSave(yy_text);
8969     }   
8970
8971     startedFromSetupPosition = FALSE;
8972     while (cm == PGNTag) {
8973         if (appData.debugMode) 
8974           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8975         err = ParsePGNTag(yy_text, &gameInfo);
8976         if (!err) numPGNTags++;
8977
8978         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8979         if(gameInfo.variant != oldVariant) {
8980             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8981             InitPosition(TRUE);
8982             oldVariant = gameInfo.variant;
8983             if (appData.debugMode) 
8984               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8985         }
8986
8987
8988         if (gameInfo.fen != NULL) {
8989           Board initial_position;
8990           startedFromSetupPosition = TRUE;
8991           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8992             Reset(TRUE, TRUE);
8993             DisplayError(_("Bad FEN position in file"), 0);
8994             return FALSE;
8995           }
8996           CopyBoard(boards[0], initial_position);
8997           if (blackPlaysFirst) {
8998             currentMove = forwardMostMove = backwardMostMove = 1;
8999             CopyBoard(boards[1], initial_position);
9000             strcpy(moveList[0], "");
9001             strcpy(parseList[0], "");
9002             timeRemaining[0][1] = whiteTimeRemaining;
9003             timeRemaining[1][1] = blackTimeRemaining;
9004             if (commentList[0] != NULL) {
9005               commentList[1] = commentList[0];
9006               commentList[0] = NULL;
9007             }
9008           } else {
9009             currentMove = forwardMostMove = backwardMostMove = 0;
9010           }
9011           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9012           {   int i;
9013               initialRulePlies = FENrulePlies;
9014               epStatus[forwardMostMove] = FENepStatus;
9015               for( i=0; i< nrCastlingRights; i++ )
9016                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9017           }
9018           yyboardindex = forwardMostMove;
9019           free(gameInfo.fen);
9020           gameInfo.fen = NULL;
9021         }
9022
9023         yyboardindex = forwardMostMove;
9024         cm = (ChessMove) yylex();
9025
9026         /* Handle comments interspersed among the tags */
9027         while (cm == Comment) {
9028             char *p;
9029             if (appData.debugMode) 
9030               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9031             p = yy_text;
9032             if (*p == '{' || *p == '[' || *p == '(') {
9033                 p[strlen(p) - 1] = NULLCHAR;
9034                 p++;
9035             }
9036             while (*p == '\n') p++;
9037             AppendComment(currentMove, p);
9038             yyboardindex = forwardMostMove;
9039             cm = (ChessMove) yylex();
9040         }
9041     }
9042
9043     /* don't rely on existence of Event tag since if game was
9044      * pasted from clipboard the Event tag may not exist
9045      */
9046     if (numPGNTags > 0){
9047         char *tags;
9048         if (gameInfo.variant == VariantNormal) {
9049           gameInfo.variant = StringToVariant(gameInfo.event);
9050         }
9051         if (!matchMode) {
9052           if( appData.autoDisplayTags ) {
9053             tags = PGNTags(&gameInfo);
9054             TagsPopUp(tags, CmailMsg());
9055             free(tags);
9056           }
9057         }
9058     } else {
9059         /* Make something up, but don't display it now */
9060         SetGameInfo();
9061         TagsPopDown();
9062     }
9063
9064     if (cm == PositionDiagram) {
9065         int i, j;
9066         char *p;
9067         Board initial_position;
9068
9069         if (appData.debugMode)
9070           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9071
9072         if (!startedFromSetupPosition) {
9073             p = yy_text;
9074             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9075               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9076                 switch (*p) {
9077                   case '[':
9078                   case '-':
9079                   case ' ':
9080                   case '\t':
9081                   case '\n':
9082                   case '\r':
9083                     break;
9084                   default:
9085                     initial_position[i][j++] = CharToPiece(*p);
9086                     break;
9087                 }
9088             while (*p == ' ' || *p == '\t' ||
9089                    *p == '\n' || *p == '\r') p++;
9090         
9091             if (strncmp(p, "black", strlen("black"))==0)
9092               blackPlaysFirst = TRUE;
9093             else
9094               blackPlaysFirst = FALSE;
9095             startedFromSetupPosition = TRUE;
9096         
9097             CopyBoard(boards[0], initial_position);
9098             if (blackPlaysFirst) {
9099                 currentMove = forwardMostMove = backwardMostMove = 1;
9100                 CopyBoard(boards[1], initial_position);
9101                 strcpy(moveList[0], "");
9102                 strcpy(parseList[0], "");
9103                 timeRemaining[0][1] = whiteTimeRemaining;
9104                 timeRemaining[1][1] = blackTimeRemaining;
9105                 if (commentList[0] != NULL) {
9106                     commentList[1] = commentList[0];
9107                     commentList[0] = NULL;
9108                 }
9109             } else {
9110                 currentMove = forwardMostMove = backwardMostMove = 0;
9111             }
9112         }
9113         yyboardindex = forwardMostMove;
9114         cm = (ChessMove) yylex();
9115     }
9116
9117     if (first.pr == NoProc) {
9118         StartChessProgram(&first);
9119     }
9120     InitChessProgram(&first, FALSE);
9121     SendToProgram("force\n", &first);
9122     if (startedFromSetupPosition) {
9123         SendBoard(&first, forwardMostMove);
9124     if (appData.debugMode) {
9125         fprintf(debugFP, "Load Game\n");
9126     }
9127         DisplayBothClocks();
9128     }      
9129
9130     /* [HGM] server: flag to write setup moves in broadcast file as one */
9131     loadFlag = appData.suppressLoadMoves;
9132
9133     while (cm == Comment) {
9134         char *p;
9135         if (appData.debugMode) 
9136           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9137         p = yy_text;
9138         if (*p == '{' || *p == '[' || *p == '(') {
9139             p[strlen(p) - 1] = NULLCHAR;
9140             p++;
9141         }
9142         while (*p == '\n') p++;
9143         AppendComment(currentMove, p);
9144         yyboardindex = forwardMostMove;
9145         cm = (ChessMove) yylex();
9146     }
9147
9148     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9149         cm == WhiteWins || cm == BlackWins ||
9150         cm == GameIsDrawn || cm == GameUnfinished) {
9151         DisplayMessage("", _("No moves in game"));
9152         if (cmailMsgLoaded) {
9153             if (appData.debugMode)
9154               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9155             ClearHighlights();
9156             flipView = FALSE;
9157         }
9158         DrawPosition(FALSE, boards[currentMove]);
9159         DisplayBothClocks();
9160         gameMode = EditGame;
9161         ModeHighlight();
9162         gameFileFP = NULL;
9163         cmailOldMove = 0;
9164         return TRUE;
9165     }
9166
9167     // [HGM] PV info: routine tests if comment empty
9168     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9169         DisplayComment(currentMove - 1, commentList[currentMove]);
9170     }
9171     if (!matchMode && appData.timeDelay != 0) 
9172       DrawPosition(FALSE, boards[currentMove]);
9173
9174     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9175       programStats.ok_to_send = 1;
9176     }
9177
9178     /* if the first token after the PGN tags is a move
9179      * and not move number 1, retrieve it from the parser 
9180      */
9181     if (cm != MoveNumberOne)
9182         LoadGameOneMove(cm);
9183
9184     /* load the remaining moves from the file */
9185     while (LoadGameOneMove((ChessMove)0)) {
9186       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9187       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9188     }
9189
9190     /* rewind to the start of the game */
9191     currentMove = backwardMostMove;
9192
9193     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9194
9195     if (oldGameMode == AnalyzeFile ||
9196         oldGameMode == AnalyzeMode) {
9197       AnalyzeFileEvent();
9198     }
9199
9200     if (matchMode || appData.timeDelay == 0) {
9201       ToEndEvent();
9202       gameMode = EditGame;
9203       ModeHighlight();
9204     } else if (appData.timeDelay > 0) {
9205       AutoPlayGameLoop();
9206     }
9207
9208     if (appData.debugMode) 
9209         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9210
9211     loadFlag = 0; /* [HGM] true game starts */
9212     return TRUE;
9213 }
9214
9215 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9216 int
9217 ReloadPosition(offset)
9218      int offset;
9219 {
9220     int positionNumber = lastLoadPositionNumber + offset;
9221     if (lastLoadPositionFP == NULL) {
9222         DisplayError(_("No position has been loaded yet"), 0);
9223         return FALSE;
9224     }
9225     if (positionNumber <= 0) {
9226         DisplayError(_("Can't back up any further"), 0);
9227         return FALSE;
9228     }
9229     return LoadPosition(lastLoadPositionFP, positionNumber,
9230                         lastLoadPositionTitle);
9231 }
9232
9233 /* Load the nth position from the given file */
9234 int
9235 LoadPositionFromFile(filename, n, title)
9236      char *filename;
9237      int n;
9238      char *title;
9239 {
9240     FILE *f;
9241     char buf[MSG_SIZ];
9242
9243     if (strcmp(filename, "-") == 0) {
9244         return LoadPosition(stdin, n, "stdin");
9245     } else {
9246         f = fopen(filename, "rb");
9247         if (f == NULL) {
9248             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9249             DisplayError(buf, errno);
9250             return FALSE;
9251         } else {
9252             return LoadPosition(f, n, title);
9253         }
9254     }
9255 }
9256
9257 /* Load the nth position from the given open file, and close it */
9258 int
9259 LoadPosition(f, positionNumber, title)
9260      FILE *f;
9261      int positionNumber;
9262      char *title;
9263 {
9264     char *p, line[MSG_SIZ];
9265     Board initial_position;
9266     int i, j, fenMode, pn;
9267     
9268     if (gameMode == Training )
9269         SetTrainingModeOff();
9270
9271     if (gameMode != BeginningOfGame) {
9272         Reset(FALSE, TRUE);
9273     }
9274     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9275         fclose(lastLoadPositionFP);
9276     }
9277     if (positionNumber == 0) positionNumber = 1;
9278     lastLoadPositionFP = f;
9279     lastLoadPositionNumber = positionNumber;
9280     strcpy(lastLoadPositionTitle, title);
9281     if (first.pr == NoProc) {
9282       StartChessProgram(&first);
9283       InitChessProgram(&first, FALSE);
9284     }    
9285     pn = positionNumber;
9286     if (positionNumber < 0) {
9287         /* Negative position number means to seek to that byte offset */
9288         if (fseek(f, -positionNumber, 0) == -1) {
9289             DisplayError(_("Can't seek on position file"), 0);
9290             return FALSE;
9291         };
9292         pn = 1;
9293     } else {
9294         if (fseek(f, 0, 0) == -1) {
9295             if (f == lastLoadPositionFP ?
9296                 positionNumber == lastLoadPositionNumber + 1 :
9297                 positionNumber == 1) {
9298                 pn = 1;
9299             } else {
9300                 DisplayError(_("Can't seek on position file"), 0);
9301                 return FALSE;
9302             }
9303         }
9304     }
9305     /* See if this file is FEN or old-style xboard */
9306     if (fgets(line, MSG_SIZ, f) == NULL) {
9307         DisplayError(_("Position not found in file"), 0);
9308         return FALSE;
9309     }
9310 #if 0
9311     switch (line[0]) {
9312       case '#':  case 'x':
9313       default:
9314         fenMode = FALSE;
9315         break;
9316       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9317       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9318       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9319       case '7':  case '8':  case '9':
9320       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9321       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9322       case 'C':  case 'W':             case 'c':  case 'w': 
9323         fenMode = TRUE;
9324         break;
9325     }
9326 #else
9327     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9328     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9329 #endif
9330
9331     if (pn >= 2) {
9332         if (fenMode || line[0] == '#') pn--;
9333         while (pn > 0) {
9334             /* skip positions before number pn */
9335             if (fgets(line, MSG_SIZ, f) == NULL) {
9336                 Reset(TRUE, TRUE);
9337                 DisplayError(_("Position not found in file"), 0);
9338                 return FALSE;
9339             }
9340             if (fenMode || line[0] == '#') pn--;
9341         }
9342     }
9343
9344     if (fenMode) {
9345         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9346             DisplayError(_("Bad FEN position in file"), 0);
9347             return FALSE;
9348         }
9349     } else {
9350         (void) fgets(line, MSG_SIZ, f);
9351         (void) fgets(line, MSG_SIZ, f);
9352     
9353         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9354             (void) fgets(line, MSG_SIZ, f);
9355             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9356                 if (*p == ' ')
9357                   continue;
9358                 initial_position[i][j++] = CharToPiece(*p);
9359             }
9360         }
9361     
9362         blackPlaysFirst = FALSE;
9363         if (!feof(f)) {
9364             (void) fgets(line, MSG_SIZ, f);
9365             if (strncmp(line, "black", strlen("black"))==0)
9366               blackPlaysFirst = TRUE;
9367         }
9368     }
9369     startedFromSetupPosition = TRUE;
9370     
9371     SendToProgram("force\n", &first);
9372     CopyBoard(boards[0], initial_position);
9373     if (blackPlaysFirst) {
9374         currentMove = forwardMostMove = backwardMostMove = 1;
9375         strcpy(moveList[0], "");
9376         strcpy(parseList[0], "");
9377         CopyBoard(boards[1], initial_position);
9378         DisplayMessage("", _("Black to play"));
9379     } else {
9380         currentMove = forwardMostMove = backwardMostMove = 0;
9381         DisplayMessage("", _("White to play"));
9382     }
9383           /* [HGM] copy FEN attributes as well */
9384           {   int i;
9385               initialRulePlies = FENrulePlies;
9386               epStatus[forwardMostMove] = FENepStatus;
9387               for( i=0; i< nrCastlingRights; i++ )
9388                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9389           }
9390     SendBoard(&first, forwardMostMove);
9391     if (appData.debugMode) {
9392 int i, j;
9393   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9394   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9395         fprintf(debugFP, "Load Position\n");
9396     }
9397
9398     if (positionNumber > 1) {
9399         sprintf(line, "%s %d", title, positionNumber);
9400         DisplayTitle(line);
9401     } else {
9402         DisplayTitle(title);
9403     }
9404     gameMode = EditGame;
9405     ModeHighlight();
9406     ResetClocks();
9407     timeRemaining[0][1] = whiteTimeRemaining;
9408     timeRemaining[1][1] = blackTimeRemaining;
9409     DrawPosition(FALSE, boards[currentMove]);
9410    
9411     return TRUE;
9412 }
9413
9414
9415 void
9416 CopyPlayerNameIntoFileName(dest, src)
9417      char **dest, *src;
9418 {
9419     while (*src != NULLCHAR && *src != ',') {
9420         if (*src == ' ') {
9421             *(*dest)++ = '_';
9422             src++;
9423         } else {
9424             *(*dest)++ = *src++;
9425         }
9426     }
9427 }
9428
9429 char *DefaultFileName(ext)
9430      char *ext;
9431 {
9432     static char def[MSG_SIZ];
9433     char *p;
9434
9435     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9436         p = def;
9437         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9438         *p++ = '-';
9439         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9440         *p++ = '.';
9441         strcpy(p, ext);
9442     } else {
9443         def[0] = NULLCHAR;
9444     }
9445     return def;
9446 }
9447
9448 /* Save the current game to the given file */
9449 int
9450 SaveGameToFile(filename, append)
9451      char *filename;
9452      int append;
9453 {
9454     FILE *f;
9455     char buf[MSG_SIZ];
9456
9457     if (strcmp(filename, "-") == 0) {
9458         return SaveGame(stdout, 0, NULL);
9459     } else {
9460         f = fopen(filename, append ? "a" : "w");
9461         if (f == NULL) {
9462             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9463             DisplayError(buf, errno);
9464             return FALSE;
9465         } else {
9466             return SaveGame(f, 0, NULL);
9467         }
9468     }
9469 }
9470
9471 char *
9472 SavePart(str)
9473      char *str;
9474 {
9475     static char buf[MSG_SIZ];
9476     char *p;
9477     
9478     p = strchr(str, ' ');
9479     if (p == NULL) return str;
9480     strncpy(buf, str, p - str);
9481     buf[p - str] = NULLCHAR;
9482     return buf;
9483 }
9484
9485 #define PGN_MAX_LINE 75
9486
9487 #define PGN_SIDE_WHITE  0
9488 #define PGN_SIDE_BLACK  1
9489
9490 /* [AS] */
9491 static int FindFirstMoveOutOfBook( int side )
9492 {
9493     int result = -1;
9494
9495     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9496         int index = backwardMostMove;
9497         int has_book_hit = 0;
9498
9499         if( (index % 2) != side ) {
9500             index++;
9501         }
9502
9503         while( index < forwardMostMove ) {
9504             /* Check to see if engine is in book */
9505             int depth = pvInfoList[index].depth;
9506             int score = pvInfoList[index].score;
9507             int in_book = 0;
9508
9509             if( depth <= 2 ) {
9510                 in_book = 1;
9511             }
9512             else if( score == 0 && depth == 63 ) {
9513                 in_book = 1; /* Zappa */
9514             }
9515             else if( score == 2 && depth == 99 ) {
9516                 in_book = 1; /* Abrok */
9517             }
9518
9519             has_book_hit += in_book;
9520
9521             if( ! in_book ) {
9522                 result = index;
9523
9524                 break;
9525             }
9526
9527             index += 2;
9528         }
9529     }
9530
9531     return result;
9532 }
9533
9534 /* [AS] */
9535 void GetOutOfBookInfo( char * buf )
9536 {
9537     int oob[2];
9538     int i;
9539     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9540
9541     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9542     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9543
9544     *buf = '\0';
9545
9546     if( oob[0] >= 0 || oob[1] >= 0 ) {
9547         for( i=0; i<2; i++ ) {
9548             int idx = oob[i];
9549
9550             if( idx >= 0 ) {
9551                 if( i > 0 && oob[0] >= 0 ) {
9552                     strcat( buf, "   " );
9553                 }
9554
9555                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9556                 sprintf( buf+strlen(buf), "%s%.2f", 
9557                     pvInfoList[idx].score >= 0 ? "+" : "",
9558                     pvInfoList[idx].score / 100.0 );
9559             }
9560         }
9561     }
9562 }
9563
9564 /* Save game in PGN style and close the file */
9565 int
9566 SaveGamePGN(f)
9567      FILE *f;
9568 {
9569     int i, offset, linelen, newblock;
9570     time_t tm;
9571 //    char *movetext;
9572     char numtext[32];
9573     int movelen, numlen, blank;
9574     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9575
9576     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9577     
9578     tm = time((time_t *) NULL);
9579     
9580     PrintPGNTags(f, &gameInfo);
9581     
9582     if (backwardMostMove > 0 || startedFromSetupPosition) {
9583         char *fen = PositionToFEN(backwardMostMove, NULL);
9584         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9585         fprintf(f, "\n{--------------\n");
9586         PrintPosition(f, backwardMostMove);
9587         fprintf(f, "--------------}\n");
9588         free(fen);
9589     }
9590     else {
9591         /* [AS] Out of book annotation */
9592         if( appData.saveOutOfBookInfo ) {
9593             char buf[64];
9594
9595             GetOutOfBookInfo( buf );
9596
9597             if( buf[0] != '\0' ) {
9598                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9599             }
9600         }
9601
9602         fprintf(f, "\n");
9603     }
9604
9605     i = backwardMostMove;
9606     linelen = 0;
9607     newblock = TRUE;
9608
9609     while (i < forwardMostMove) {
9610         /* Print comments preceding this move */
9611         if (commentList[i] != NULL) {
9612             if (linelen > 0) fprintf(f, "\n");
9613             fprintf(f, "{\n%s}\n", commentList[i]);
9614             linelen = 0;
9615             newblock = TRUE;
9616         }
9617
9618         /* Format move number */
9619         if ((i % 2) == 0) {
9620             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9621         } else {
9622             if (newblock) {
9623                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9624             } else {
9625                 numtext[0] = NULLCHAR;
9626             }
9627         }
9628         numlen = strlen(numtext);
9629         newblock = FALSE;
9630
9631         /* Print move number */
9632         blank = linelen > 0 && numlen > 0;
9633         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9634             fprintf(f, "\n");
9635             linelen = 0;
9636             blank = 0;
9637         }
9638         if (blank) {
9639             fprintf(f, " ");
9640             linelen++;
9641         }
9642         fprintf(f, numtext);
9643         linelen += numlen;
9644
9645         /* Get move */
9646         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9647         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9648         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9649                 int p = movelen - 1;
9650                 if(move_buffer[p] == ' ') p--;
9651                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9652                     while(p && move_buffer[--p] != '(');
9653                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9654                 }
9655         }
9656
9657         /* Print move */
9658         blank = linelen > 0 && movelen > 0;
9659         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9660             fprintf(f, "\n");
9661             linelen = 0;
9662             blank = 0;
9663         }
9664         if (blank) {
9665             fprintf(f, " ");
9666             linelen++;
9667         }
9668         fprintf(f, move_buffer);
9669         linelen += movelen;
9670
9671         /* [AS] Add PV info if present */
9672         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9673             /* [HGM] add time */
9674             char buf[MSG_SIZ]; int seconds = 0;
9675
9676 #if 1
9677             if(i >= backwardMostMove) {
9678                 if(WhiteOnMove(i))
9679                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9680                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9681                 else
9682                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9683                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9684             }
9685             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9686 #else
9687             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9688 #endif
9689
9690             if( seconds <= 0) buf[0] = 0; else
9691             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9692                 seconds = (seconds + 4)/10; // round to full seconds
9693                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9694                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9695             }
9696
9697             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9698                 pvInfoList[i].score >= 0 ? "+" : "",
9699                 pvInfoList[i].score / 100.0,
9700                 pvInfoList[i].depth,
9701                 buf );
9702
9703             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9704
9705             /* Print score/depth */
9706             blank = linelen > 0 && movelen > 0;
9707             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9708                 fprintf(f, "\n");
9709                 linelen = 0;
9710                 blank = 0;
9711             }
9712             if (blank) {
9713                 fprintf(f, " ");
9714                 linelen++;
9715             }
9716             fprintf(f, move_buffer);
9717             linelen += movelen;
9718         }
9719
9720         i++;
9721     }
9722     
9723     /* Start a new line */
9724     if (linelen > 0) fprintf(f, "\n");
9725
9726     /* Print comments after last move */
9727     if (commentList[i] != NULL) {
9728         fprintf(f, "{\n%s}\n", commentList[i]);
9729     }
9730
9731     /* Print result */
9732     if (gameInfo.resultDetails != NULL &&
9733         gameInfo.resultDetails[0] != NULLCHAR) {
9734         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9735                 PGNResult(gameInfo.result));
9736     } else {
9737         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9738     }
9739
9740     fclose(f);
9741     return TRUE;
9742 }
9743
9744 /* Save game in old style and close the file */
9745 int
9746 SaveGameOldStyle(f)
9747      FILE *f;
9748 {
9749     int i, offset;
9750     time_t tm;
9751     
9752     tm = time((time_t *) NULL);
9753     
9754     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9755     PrintOpponents(f);
9756     
9757     if (backwardMostMove > 0 || startedFromSetupPosition) {
9758         fprintf(f, "\n[--------------\n");
9759         PrintPosition(f, backwardMostMove);
9760         fprintf(f, "--------------]\n");
9761     } else {
9762         fprintf(f, "\n");
9763     }
9764
9765     i = backwardMostMove;
9766     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9767
9768     while (i < forwardMostMove) {
9769         if (commentList[i] != NULL) {
9770             fprintf(f, "[%s]\n", commentList[i]);
9771         }
9772
9773         if ((i % 2) == 1) {
9774             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9775             i++;
9776         } else {
9777             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9778             i++;
9779             if (commentList[i] != NULL) {
9780                 fprintf(f, "\n");
9781                 continue;
9782             }
9783             if (i >= forwardMostMove) {
9784                 fprintf(f, "\n");
9785                 break;
9786             }
9787             fprintf(f, "%s\n", parseList[i]);
9788             i++;
9789         }
9790     }
9791     
9792     if (commentList[i] != NULL) {
9793         fprintf(f, "[%s]\n", commentList[i]);
9794     }
9795
9796     /* This isn't really the old style, but it's close enough */
9797     if (gameInfo.resultDetails != NULL &&
9798         gameInfo.resultDetails[0] != NULLCHAR) {
9799         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9800                 gameInfo.resultDetails);
9801     } else {
9802         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9803     }
9804
9805     fclose(f);
9806     return TRUE;
9807 }
9808
9809 /* Save the current game to open file f and close the file */
9810 int
9811 SaveGame(f, dummy, dummy2)
9812      FILE *f;
9813      int dummy;
9814      char *dummy2;
9815 {
9816     if (gameMode == EditPosition) EditPositionDone();
9817     if (appData.oldSaveStyle)
9818       return SaveGameOldStyle(f);
9819     else
9820       return SaveGamePGN(f);
9821 }
9822
9823 /* Save the current position to the given file */
9824 int
9825 SavePositionToFile(filename)
9826      char *filename;
9827 {
9828     FILE *f;
9829     char buf[MSG_SIZ];
9830
9831     if (strcmp(filename, "-") == 0) {
9832         return SavePosition(stdout, 0, NULL);
9833     } else {
9834         f = fopen(filename, "a");
9835         if (f == NULL) {
9836             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9837             DisplayError(buf, errno);
9838             return FALSE;
9839         } else {
9840             SavePosition(f, 0, NULL);
9841             return TRUE;
9842         }
9843     }
9844 }
9845
9846 /* Save the current position to the given open file and close the file */
9847 int
9848 SavePosition(f, dummy, dummy2)
9849      FILE *f;
9850      int dummy;
9851      char *dummy2;
9852 {
9853     time_t tm;
9854     char *fen;
9855     
9856     if (appData.oldSaveStyle) {
9857         tm = time((time_t *) NULL);
9858     
9859         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9860         PrintOpponents(f);
9861         fprintf(f, "[--------------\n");
9862         PrintPosition(f, currentMove);
9863         fprintf(f, "--------------]\n");
9864     } else {
9865         fen = PositionToFEN(currentMove, NULL);
9866         fprintf(f, "%s\n", fen);
9867         free(fen);
9868     }
9869     fclose(f);
9870     return TRUE;
9871 }
9872
9873 void
9874 ReloadCmailMsgEvent(unregister)
9875      int unregister;
9876 {
9877 #if !WIN32
9878     static char *inFilename = NULL;
9879     static char *outFilename;
9880     int i;
9881     struct stat inbuf, outbuf;
9882     int status;
9883     
9884     /* Any registered moves are unregistered if unregister is set, */
9885     /* i.e. invoked by the signal handler */
9886     if (unregister) {
9887         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9888             cmailMoveRegistered[i] = FALSE;
9889             if (cmailCommentList[i] != NULL) {
9890                 free(cmailCommentList[i]);
9891                 cmailCommentList[i] = NULL;
9892             }
9893         }
9894         nCmailMovesRegistered = 0;
9895     }
9896
9897     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9898         cmailResult[i] = CMAIL_NOT_RESULT;
9899     }
9900     nCmailResults = 0;
9901
9902     if (inFilename == NULL) {
9903         /* Because the filenames are static they only get malloced once  */
9904         /* and they never get freed                                      */
9905         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9906         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9907
9908         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9909         sprintf(outFilename, "%s.out", appData.cmailGameName);
9910     }
9911     
9912     status = stat(outFilename, &outbuf);
9913     if (status < 0) {
9914         cmailMailedMove = FALSE;
9915     } else {
9916         status = stat(inFilename, &inbuf);
9917         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9918     }
9919     
9920     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9921        counts the games, notes how each one terminated, etc.
9922        
9923        It would be nice to remove this kludge and instead gather all
9924        the information while building the game list.  (And to keep it
9925        in the game list nodes instead of having a bunch of fixed-size
9926        parallel arrays.)  Note this will require getting each game's
9927        termination from the PGN tags, as the game list builder does
9928        not process the game moves.  --mann
9929        */
9930     cmailMsgLoaded = TRUE;
9931     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9932     
9933     /* Load first game in the file or popup game menu */
9934     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9935
9936 #endif /* !WIN32 */
9937     return;
9938 }
9939
9940 int
9941 RegisterMove()
9942 {
9943     FILE *f;
9944     char string[MSG_SIZ];
9945
9946     if (   cmailMailedMove
9947         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9948         return TRUE;            /* Allow free viewing  */
9949     }
9950
9951     /* Unregister move to ensure that we don't leave RegisterMove        */
9952     /* with the move registered when the conditions for registering no   */
9953     /* longer hold                                                       */
9954     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9955         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9956         nCmailMovesRegistered --;
9957
9958         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9959           {
9960               free(cmailCommentList[lastLoadGameNumber - 1]);
9961               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9962           }
9963     }
9964
9965     if (cmailOldMove == -1) {
9966         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9967         return FALSE;
9968     }
9969
9970     if (currentMove > cmailOldMove + 1) {
9971         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9972         return FALSE;
9973     }
9974
9975     if (currentMove < cmailOldMove) {
9976         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9977         return FALSE;
9978     }
9979
9980     if (forwardMostMove > currentMove) {
9981         /* Silently truncate extra moves */
9982         TruncateGame();
9983     }
9984
9985     if (   (currentMove == cmailOldMove + 1)
9986         || (   (currentMove == cmailOldMove)
9987             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9988                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9989         if (gameInfo.result != GameUnfinished) {
9990             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9991         }
9992
9993         if (commentList[currentMove] != NULL) {
9994             cmailCommentList[lastLoadGameNumber - 1]
9995               = StrSave(commentList[currentMove]);
9996         }
9997         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9998
9999         if (appData.debugMode)
10000           fprintf(debugFP, "Saving %s for game %d\n",
10001                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10002
10003         sprintf(string,
10004                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10005         
10006         f = fopen(string, "w");
10007         if (appData.oldSaveStyle) {
10008             SaveGameOldStyle(f); /* also closes the file */
10009             
10010             sprintf(string, "%s.pos.out", appData.cmailGameName);
10011             f = fopen(string, "w");
10012             SavePosition(f, 0, NULL); /* also closes the file */
10013         } else {
10014             fprintf(f, "{--------------\n");
10015             PrintPosition(f, currentMove);
10016             fprintf(f, "--------------}\n\n");
10017             
10018             SaveGame(f, 0, NULL); /* also closes the file*/
10019         }
10020         
10021         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10022         nCmailMovesRegistered ++;
10023     } else if (nCmailGames == 1) {
10024         DisplayError(_("You have not made a move yet"), 0);
10025         return FALSE;
10026     }
10027
10028     return TRUE;
10029 }
10030
10031 void
10032 MailMoveEvent()
10033 {
10034 #if !WIN32
10035     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10036     FILE *commandOutput;
10037     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10038     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10039     int nBuffers;
10040     int i;
10041     int archived;
10042     char *arcDir;
10043
10044     if (! cmailMsgLoaded) {
10045         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10046         return;
10047     }
10048
10049     if (nCmailGames == nCmailResults) {
10050         DisplayError(_("No unfinished games"), 0);
10051         return;
10052     }
10053
10054 #if CMAIL_PROHIBIT_REMAIL
10055     if (cmailMailedMove) {
10056         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);
10057         DisplayError(msg, 0);
10058         return;
10059     }
10060 #endif
10061
10062     if (! (cmailMailedMove || RegisterMove())) return;
10063     
10064     if (   cmailMailedMove
10065         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10066         sprintf(string, partCommandString,
10067                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10068         commandOutput = popen(string, "r");
10069
10070         if (commandOutput == NULL) {
10071             DisplayError(_("Failed to invoke cmail"), 0);
10072         } else {
10073             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10074                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10075             }
10076             if (nBuffers > 1) {
10077                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10078                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10079                 nBytes = MSG_SIZ - 1;
10080             } else {
10081                 (void) memcpy(msg, buffer, nBytes);
10082             }
10083             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10084
10085             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10086                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10087
10088                 archived = TRUE;
10089                 for (i = 0; i < nCmailGames; i ++) {
10090                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10091                         archived = FALSE;
10092                     }
10093                 }
10094                 if (   archived
10095                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10096                         != NULL)) {
10097                     sprintf(buffer, "%s/%s.%s.archive",
10098                             arcDir,
10099                             appData.cmailGameName,
10100                             gameInfo.date);
10101                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10102                     cmailMsgLoaded = FALSE;
10103                 }
10104             }
10105
10106             DisplayInformation(msg);
10107             pclose(commandOutput);
10108         }
10109     } else {
10110         if ((*cmailMsg) != '\0') {
10111             DisplayInformation(cmailMsg);
10112         }
10113     }
10114
10115     return;
10116 #endif /* !WIN32 */
10117 }
10118
10119 char *
10120 CmailMsg()
10121 {
10122 #if WIN32
10123     return NULL;
10124 #else
10125     int  prependComma = 0;
10126     char number[5];
10127     char string[MSG_SIZ];       /* Space for game-list */
10128     int  i;
10129     
10130     if (!cmailMsgLoaded) return "";
10131
10132     if (cmailMailedMove) {
10133         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10134     } else {
10135         /* Create a list of games left */
10136         sprintf(string, "[");
10137         for (i = 0; i < nCmailGames; i ++) {
10138             if (! (   cmailMoveRegistered[i]
10139                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10140                 if (prependComma) {
10141                     sprintf(number, ",%d", i + 1);
10142                 } else {
10143                     sprintf(number, "%d", i + 1);
10144                     prependComma = 1;
10145                 }
10146                 
10147                 strcat(string, number);
10148             }
10149         }
10150         strcat(string, "]");
10151
10152         if (nCmailMovesRegistered + nCmailResults == 0) {
10153             switch (nCmailGames) {
10154               case 1:
10155                 sprintf(cmailMsg,
10156                         _("Still need to make move for game\n"));
10157                 break;
10158                 
10159               case 2:
10160                 sprintf(cmailMsg,
10161                         _("Still need to make moves for both games\n"));
10162                 break;
10163                 
10164               default:
10165                 sprintf(cmailMsg,
10166                         _("Still need to make moves for all %d games\n"),
10167                         nCmailGames);
10168                 break;
10169             }
10170         } else {
10171             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10172               case 1:
10173                 sprintf(cmailMsg,
10174                         _("Still need to make a move for game %s\n"),
10175                         string);
10176                 break;
10177                 
10178               case 0:
10179                 if (nCmailResults == nCmailGames) {
10180                     sprintf(cmailMsg, _("No unfinished games\n"));
10181                 } else {
10182                     sprintf(cmailMsg, _("Ready to send mail\n"));
10183                 }
10184                 break;
10185                 
10186               default:
10187                 sprintf(cmailMsg,
10188                         _("Still need to make moves for games %s\n"),
10189                         string);
10190             }
10191         }
10192     }
10193     return cmailMsg;
10194 #endif /* WIN32 */
10195 }
10196
10197 void
10198 ResetGameEvent()
10199 {
10200     if (gameMode == Training)
10201       SetTrainingModeOff();
10202
10203     Reset(TRUE, TRUE);
10204     cmailMsgLoaded = FALSE;
10205     if (appData.icsActive) {
10206       SendToICS(ics_prefix);
10207       SendToICS("refresh\n");
10208     }
10209 }
10210
10211 void
10212 ExitEvent(status)
10213      int status;
10214 {
10215     exiting++;
10216     if (exiting > 2) {
10217       /* Give up on clean exit */
10218       exit(status);
10219     }
10220     if (exiting > 1) {
10221       /* Keep trying for clean exit */
10222       return;
10223     }
10224
10225     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10226
10227     if (telnetISR != NULL) {
10228       RemoveInputSource(telnetISR);
10229     }
10230     if (icsPR != NoProc) {
10231       DestroyChildProcess(icsPR, TRUE);
10232     }
10233 #if 0
10234     /* Save game if resource set and not already saved by GameEnds() */
10235     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10236                              && forwardMostMove > 0) {
10237       if (*appData.saveGameFile != NULLCHAR) {
10238         SaveGameToFile(appData.saveGameFile, TRUE);
10239       } else if (appData.autoSaveGames) {
10240         AutoSaveGame();
10241       }
10242       if (*appData.savePositionFile != NULLCHAR) {
10243         SavePositionToFile(appData.savePositionFile);
10244       }
10245     }
10246     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10247 #else
10248     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10249     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10250 #endif
10251     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10252     /* make sure this other one finishes before killing it!                  */
10253     if(endingGame) { int count = 0;
10254         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10255         while(endingGame && count++ < 10) DoSleep(1);
10256         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10257     }
10258
10259     /* Kill off chess programs */
10260     if (first.pr != NoProc) {
10261         ExitAnalyzeMode();
10262         
10263         DoSleep( appData.delayBeforeQuit );
10264         SendToProgram("quit\n", &first);
10265         DoSleep( appData.delayAfterQuit );
10266         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10267     }
10268     if (second.pr != NoProc) {
10269         DoSleep( appData.delayBeforeQuit );
10270         SendToProgram("quit\n", &second);
10271         DoSleep( appData.delayAfterQuit );
10272         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10273     }
10274     if (first.isr != NULL) {
10275         RemoveInputSource(first.isr);
10276     }
10277     if (second.isr != NULL) {
10278         RemoveInputSource(second.isr);
10279     }
10280
10281     ShutDownFrontEnd();
10282     exit(status);
10283 }
10284
10285 void
10286 PauseEvent()
10287 {
10288     if (appData.debugMode)
10289         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10290     if (pausing) {
10291         pausing = FALSE;
10292         ModeHighlight();
10293         if (gameMode == MachinePlaysWhite ||
10294             gameMode == MachinePlaysBlack) {
10295             StartClocks();
10296         } else {
10297             DisplayBothClocks();
10298         }
10299         if (gameMode == PlayFromGameFile) {
10300             if (appData.timeDelay >= 0) 
10301                 AutoPlayGameLoop();
10302         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10303             Reset(FALSE, TRUE);
10304             SendToICS(ics_prefix);
10305             SendToICS("refresh\n");
10306         } else if (currentMove < forwardMostMove) {
10307             ForwardInner(forwardMostMove);
10308         }
10309         pauseExamInvalid = FALSE;
10310     } else {
10311         switch (gameMode) {
10312           default:
10313             return;
10314           case IcsExamining:
10315             pauseExamForwardMostMove = forwardMostMove;
10316             pauseExamInvalid = FALSE;
10317             /* fall through */
10318           case IcsObserving:
10319           case IcsPlayingWhite:
10320           case IcsPlayingBlack:
10321             pausing = TRUE;
10322             ModeHighlight();
10323             return;
10324           case PlayFromGameFile:
10325             (void) StopLoadGameTimer();
10326             pausing = TRUE;
10327             ModeHighlight();
10328             break;
10329           case BeginningOfGame:
10330             if (appData.icsActive) return;
10331             /* else fall through */
10332           case MachinePlaysWhite:
10333           case MachinePlaysBlack:
10334           case TwoMachinesPlay:
10335             if (forwardMostMove == 0)
10336               return;           /* don't pause if no one has moved */
10337             if ((gameMode == MachinePlaysWhite &&
10338                  !WhiteOnMove(forwardMostMove)) ||
10339                 (gameMode == MachinePlaysBlack &&
10340                  WhiteOnMove(forwardMostMove))) {
10341                 StopClocks();
10342             }
10343             pausing = TRUE;
10344             ModeHighlight();
10345             break;
10346         }
10347     }
10348 }
10349
10350 void
10351 EditCommentEvent()
10352 {
10353     char title[MSG_SIZ];
10354
10355     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10356         strcpy(title, _("Edit comment"));
10357     } else {
10358         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10359                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10360                 parseList[currentMove - 1]);
10361     }
10362
10363     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10364 }
10365
10366
10367 void
10368 EditTagsEvent()
10369 {
10370     char *tags = PGNTags(&gameInfo);
10371     EditTagsPopUp(tags);
10372     free(tags);
10373 }
10374
10375 void
10376 AnalyzeModeEvent()
10377 {
10378     if (appData.noChessProgram || gameMode == AnalyzeMode)
10379       return;
10380
10381     if (gameMode != AnalyzeFile) {
10382         if (!appData.icsEngineAnalyze) {
10383                EditGameEvent();
10384                if (gameMode != EditGame) return;
10385         }
10386         ResurrectChessProgram();
10387         SendToProgram("analyze\n", &first);
10388         first.analyzing = TRUE;
10389         /*first.maybeThinking = TRUE;*/
10390         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10391         AnalysisPopUp(_("Analysis"),
10392                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10393     }
10394     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10395     pausing = FALSE;
10396     ModeHighlight();
10397     SetGameInfo();
10398
10399     StartAnalysisClock();
10400     GetTimeMark(&lastNodeCountTime);
10401     lastNodeCount = 0;
10402 }
10403
10404 void
10405 AnalyzeFileEvent()
10406 {
10407     if (appData.noChessProgram || gameMode == AnalyzeFile)
10408       return;
10409
10410     if (gameMode != AnalyzeMode) {
10411         EditGameEvent();
10412         if (gameMode != EditGame) return;
10413         ResurrectChessProgram();
10414         SendToProgram("analyze\n", &first);
10415         first.analyzing = TRUE;
10416         /*first.maybeThinking = TRUE;*/
10417         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10418         AnalysisPopUp(_("Analysis"),
10419                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10420     }
10421     gameMode = AnalyzeFile;
10422     pausing = FALSE;
10423     ModeHighlight();
10424     SetGameInfo();
10425
10426     StartAnalysisClock();
10427     GetTimeMark(&lastNodeCountTime);
10428     lastNodeCount = 0;
10429 }
10430
10431 void
10432 MachineWhiteEvent()
10433 {
10434     char buf[MSG_SIZ];
10435     char *bookHit = NULL;
10436
10437     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10438       return;
10439
10440
10441     if (gameMode == PlayFromGameFile || 
10442         gameMode == TwoMachinesPlay  || 
10443         gameMode == Training         || 
10444         gameMode == AnalyzeMode      || 
10445         gameMode == EndOfGame)
10446         EditGameEvent();
10447
10448     if (gameMode == EditPosition) 
10449         EditPositionDone();
10450
10451     if (!WhiteOnMove(currentMove)) {
10452         DisplayError(_("It is not White's turn"), 0);
10453         return;
10454     }
10455   
10456     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10457       ExitAnalyzeMode();
10458
10459     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10460         gameMode == AnalyzeFile)
10461         TruncateGame();
10462
10463     ResurrectChessProgram();    /* in case it isn't running */
10464     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10465         gameMode = MachinePlaysWhite;
10466         ResetClocks();
10467     } else
10468     gameMode = MachinePlaysWhite;
10469     pausing = FALSE;
10470     ModeHighlight();
10471     SetGameInfo();
10472     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10473     DisplayTitle(buf);
10474     if (first.sendName) {
10475       sprintf(buf, "name %s\n", gameInfo.black);
10476       SendToProgram(buf, &first);
10477     }
10478     if (first.sendTime) {
10479       if (first.useColors) {
10480         SendToProgram("black\n", &first); /*gnu kludge*/
10481       }
10482       SendTimeRemaining(&first, TRUE);
10483     }
10484     if (first.useColors) {
10485       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10486     }
10487     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10488     SetMachineThinkingEnables();
10489     first.maybeThinking = TRUE;
10490     StartClocks();
10491
10492     if (appData.autoFlipView && !flipView) {
10493       flipView = !flipView;
10494       DrawPosition(FALSE, NULL);
10495       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10496     }
10497
10498     if(bookHit) { // [HGM] book: simulate book reply
10499         static char bookMove[MSG_SIZ]; // a bit generous?
10500
10501         programStats.nodes = programStats.depth = programStats.time = 
10502         programStats.score = programStats.got_only_move = 0;
10503         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10504
10505         strcpy(bookMove, "move ");
10506         strcat(bookMove, bookHit);
10507         HandleMachineMove(bookMove, &first);
10508     }
10509 }
10510
10511 void
10512 MachineBlackEvent()
10513 {
10514     char buf[MSG_SIZ];
10515    char *bookHit = NULL;
10516
10517     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10518         return;
10519
10520
10521     if (gameMode == PlayFromGameFile || 
10522         gameMode == TwoMachinesPlay  || 
10523         gameMode == Training         || 
10524         gameMode == AnalyzeMode      || 
10525         gameMode == EndOfGame)
10526         EditGameEvent();
10527
10528     if (gameMode == EditPosition) 
10529         EditPositionDone();
10530
10531     if (WhiteOnMove(currentMove)) {
10532         DisplayError(_("It is not Black's turn"), 0);
10533         return;
10534     }
10535     
10536     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10537       ExitAnalyzeMode();
10538
10539     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10540         gameMode == AnalyzeFile)
10541         TruncateGame();
10542
10543     ResurrectChessProgram();    /* in case it isn't running */
10544     gameMode = MachinePlaysBlack;
10545     pausing = FALSE;
10546     ModeHighlight();
10547     SetGameInfo();
10548     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10549     DisplayTitle(buf);
10550     if (first.sendName) {
10551       sprintf(buf, "name %s\n", gameInfo.white);
10552       SendToProgram(buf, &first);
10553     }
10554     if (first.sendTime) {
10555       if (first.useColors) {
10556         SendToProgram("white\n", &first); /*gnu kludge*/
10557       }
10558       SendTimeRemaining(&first, FALSE);
10559     }
10560     if (first.useColors) {
10561       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10562     }
10563     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10564     SetMachineThinkingEnables();
10565     first.maybeThinking = TRUE;
10566     StartClocks();
10567
10568     if (appData.autoFlipView && flipView) {
10569       flipView = !flipView;
10570       DrawPosition(FALSE, NULL);
10571       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10572     }
10573     if(bookHit) { // [HGM] book: simulate book reply
10574         static char bookMove[MSG_SIZ]; // a bit generous?
10575
10576         programStats.nodes = programStats.depth = programStats.time = 
10577         programStats.score = programStats.got_only_move = 0;
10578         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10579
10580         strcpy(bookMove, "move ");
10581         strcat(bookMove, bookHit);
10582         HandleMachineMove(bookMove, &first);
10583     }
10584 }
10585
10586
10587 void
10588 DisplayTwoMachinesTitle()
10589 {
10590     char buf[MSG_SIZ];
10591     if (appData.matchGames > 0) {
10592         if (first.twoMachinesColor[0] == 'w') {
10593             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10594                     gameInfo.white, gameInfo.black,
10595                     first.matchWins, second.matchWins,
10596                     matchGame - 1 - (first.matchWins + second.matchWins));
10597         } else {
10598             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10599                     gameInfo.white, gameInfo.black,
10600                     second.matchWins, first.matchWins,
10601                     matchGame - 1 - (first.matchWins + second.matchWins));
10602         }
10603     } else {
10604         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10605     }
10606     DisplayTitle(buf);
10607 }
10608
10609 void
10610 TwoMachinesEvent P((void))
10611 {
10612     int i;
10613     char buf[MSG_SIZ];
10614     ChessProgramState *onmove;
10615     char *bookHit = NULL;
10616     
10617     if (appData.noChessProgram) return;
10618
10619     switch (gameMode) {
10620       case TwoMachinesPlay:
10621         return;
10622       case MachinePlaysWhite:
10623       case MachinePlaysBlack:
10624         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10625             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10626             return;
10627         }
10628         /* fall through */
10629       case BeginningOfGame:
10630       case PlayFromGameFile:
10631       case EndOfGame:
10632         EditGameEvent();
10633         if (gameMode != EditGame) return;
10634         break;
10635       case EditPosition:
10636         EditPositionDone();
10637         break;
10638       case AnalyzeMode:
10639       case AnalyzeFile:
10640         ExitAnalyzeMode();
10641         break;
10642       case EditGame:
10643       default:
10644         break;
10645     }
10646
10647     forwardMostMove = currentMove;
10648     ResurrectChessProgram();    /* in case first program isn't running */
10649
10650     if (second.pr == NULL) {
10651         StartChessProgram(&second);
10652         if (second.protocolVersion == 1) {
10653           TwoMachinesEventIfReady();
10654         } else {
10655           /* kludge: allow timeout for initial "feature" command */
10656           FreezeUI();
10657           DisplayMessage("", _("Starting second chess program"));
10658           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10659         }
10660         return;
10661     }
10662     DisplayMessage("", "");
10663     InitChessProgram(&second, FALSE);
10664     SendToProgram("force\n", &second);
10665     if (startedFromSetupPosition) {
10666         SendBoard(&second, backwardMostMove);
10667     if (appData.debugMode) {
10668         fprintf(debugFP, "Two Machines\n");
10669     }
10670     }
10671     for (i = backwardMostMove; i < forwardMostMove; i++) {
10672         SendMoveToProgram(i, &second);
10673     }
10674
10675     gameMode = TwoMachinesPlay;
10676     pausing = FALSE;
10677     ModeHighlight();
10678     SetGameInfo();
10679     DisplayTwoMachinesTitle();
10680     firstMove = TRUE;
10681     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10682         onmove = &first;
10683     } else {
10684         onmove = &second;
10685     }
10686
10687     SendToProgram(first.computerString, &first);
10688     if (first.sendName) {
10689       sprintf(buf, "name %s\n", second.tidy);
10690       SendToProgram(buf, &first);
10691     }
10692     SendToProgram(second.computerString, &second);
10693     if (second.sendName) {
10694       sprintf(buf, "name %s\n", first.tidy);
10695       SendToProgram(buf, &second);
10696     }
10697
10698     ResetClocks();
10699     if (!first.sendTime || !second.sendTime) {
10700         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10701         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10702     }
10703     if (onmove->sendTime) {
10704       if (onmove->useColors) {
10705         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10706       }
10707       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10708     }
10709     if (onmove->useColors) {
10710       SendToProgram(onmove->twoMachinesColor, onmove);
10711     }
10712     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10713 //    SendToProgram("go\n", onmove);
10714     onmove->maybeThinking = TRUE;
10715     SetMachineThinkingEnables();
10716
10717     StartClocks();
10718
10719     if(bookHit) { // [HGM] book: simulate book reply
10720         static char bookMove[MSG_SIZ]; // a bit generous?
10721
10722         programStats.nodes = programStats.depth = programStats.time = 
10723         programStats.score = programStats.got_only_move = 0;
10724         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10725
10726         strcpy(bookMove, "move ");
10727         strcat(bookMove, bookHit);
10728         HandleMachineMove(bookMove, &first);
10729     }
10730 }
10731
10732 void
10733 TrainingEvent()
10734 {
10735     if (gameMode == Training) {
10736       SetTrainingModeOff();
10737       gameMode = PlayFromGameFile;
10738       DisplayMessage("", _("Training mode off"));
10739     } else {
10740       gameMode = Training;
10741       animateTraining = appData.animate;
10742
10743       /* make sure we are not already at the end of the game */
10744       if (currentMove < forwardMostMove) {
10745         SetTrainingModeOn();
10746         DisplayMessage("", _("Training mode on"));
10747       } else {
10748         gameMode = PlayFromGameFile;
10749         DisplayError(_("Already at end of game"), 0);
10750       }
10751     }
10752     ModeHighlight();
10753 }
10754
10755 void
10756 IcsClientEvent()
10757 {
10758     if (!appData.icsActive) return;
10759     switch (gameMode) {
10760       case IcsPlayingWhite:
10761       case IcsPlayingBlack:
10762       case IcsObserving:
10763       case IcsIdle:
10764       case BeginningOfGame:
10765       case IcsExamining:
10766         return;
10767
10768       case EditGame:
10769         break;
10770
10771       case EditPosition:
10772         EditPositionDone();
10773         break;
10774
10775       case AnalyzeMode:
10776       case AnalyzeFile:
10777         ExitAnalyzeMode();
10778         break;
10779         
10780       default:
10781         EditGameEvent();
10782         break;
10783     }
10784
10785     gameMode = IcsIdle;
10786     ModeHighlight();
10787     return;
10788 }
10789
10790
10791 void
10792 EditGameEvent()
10793 {
10794     int i;
10795
10796     switch (gameMode) {
10797       case Training:
10798         SetTrainingModeOff();
10799         break;
10800       case MachinePlaysWhite:
10801       case MachinePlaysBlack:
10802       case BeginningOfGame:
10803         SendToProgram("force\n", &first);
10804         SetUserThinkingEnables();
10805         break;
10806       case PlayFromGameFile:
10807         (void) StopLoadGameTimer();
10808         if (gameFileFP != NULL) {
10809             gameFileFP = NULL;
10810         }
10811         break;
10812       case EditPosition:
10813         EditPositionDone();
10814         break;
10815       case AnalyzeMode:
10816       case AnalyzeFile:
10817         ExitAnalyzeMode();
10818         SendToProgram("force\n", &first);
10819         break;
10820       case TwoMachinesPlay:
10821         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10822         ResurrectChessProgram();
10823         SetUserThinkingEnables();
10824         break;
10825       case EndOfGame:
10826         ResurrectChessProgram();
10827         break;
10828       case IcsPlayingBlack:
10829       case IcsPlayingWhite:
10830         DisplayError(_("Warning: You are still playing a game"), 0);
10831         break;
10832       case IcsObserving:
10833         DisplayError(_("Warning: You are still observing a game"), 0);
10834         break;
10835       case IcsExamining:
10836         DisplayError(_("Warning: You are still examining a game"), 0);
10837         break;
10838       case IcsIdle:
10839         break;
10840       case EditGame:
10841       default:
10842         return;
10843     }
10844     
10845     pausing = FALSE;
10846     StopClocks();
10847     first.offeredDraw = second.offeredDraw = 0;
10848
10849     if (gameMode == PlayFromGameFile) {
10850         whiteTimeRemaining = timeRemaining[0][currentMove];
10851         blackTimeRemaining = timeRemaining[1][currentMove];
10852         DisplayTitle("");
10853     }
10854
10855     if (gameMode == MachinePlaysWhite ||
10856         gameMode == MachinePlaysBlack ||
10857         gameMode == TwoMachinesPlay ||
10858         gameMode == EndOfGame) {
10859         i = forwardMostMove;
10860         while (i > currentMove) {
10861             SendToProgram("undo\n", &first);
10862             i--;
10863         }
10864         whiteTimeRemaining = timeRemaining[0][currentMove];
10865         blackTimeRemaining = timeRemaining[1][currentMove];
10866         DisplayBothClocks();
10867         if (whiteFlag || blackFlag) {
10868             whiteFlag = blackFlag = 0;
10869         }
10870         DisplayTitle("");
10871     }           
10872     
10873     gameMode = EditGame;
10874     ModeHighlight();
10875     SetGameInfo();
10876 }
10877
10878
10879 void
10880 EditPositionEvent()
10881 {
10882     if (gameMode == EditPosition) {
10883         EditGameEvent();
10884         return;
10885     }
10886     
10887     EditGameEvent();
10888     if (gameMode != EditGame) return;
10889     
10890     gameMode = EditPosition;
10891     ModeHighlight();
10892     SetGameInfo();
10893     if (currentMove > 0)
10894       CopyBoard(boards[0], boards[currentMove]);
10895     
10896     blackPlaysFirst = !WhiteOnMove(currentMove);
10897     ResetClocks();
10898     currentMove = forwardMostMove = backwardMostMove = 0;
10899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10900     DisplayMove(-1);
10901 }
10902
10903 void
10904 ExitAnalyzeMode()
10905 {
10906     /* [DM] icsEngineAnalyze - possible call from other functions */
10907     if (appData.icsEngineAnalyze) {
10908         appData.icsEngineAnalyze = FALSE;
10909
10910         DisplayMessage("",_("Close ICS engine analyze..."));
10911     }
10912     if (first.analysisSupport && first.analyzing) {
10913       SendToProgram("exit\n", &first);
10914       first.analyzing = FALSE;
10915     }
10916     AnalysisPopDown();
10917     thinkOutput[0] = NULLCHAR;
10918 }
10919
10920 void
10921 EditPositionDone()
10922 {
10923     startedFromSetupPosition = TRUE;
10924     InitChessProgram(&first, FALSE);
10925     SendToProgram("force\n", &first);
10926     if (blackPlaysFirst) {
10927         strcpy(moveList[0], "");
10928         strcpy(parseList[0], "");
10929         currentMove = forwardMostMove = backwardMostMove = 1;
10930         CopyBoard(boards[1], boards[0]);
10931         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10932         { int i;
10933           epStatus[1] = epStatus[0];
10934           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10935         }
10936     } else {
10937         currentMove = forwardMostMove = backwardMostMove = 0;
10938     }
10939     SendBoard(&first, forwardMostMove);
10940     if (appData.debugMode) {
10941         fprintf(debugFP, "EditPosDone\n");
10942     }
10943     DisplayTitle("");
10944     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10945     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10946     gameMode = EditGame;
10947     ModeHighlight();
10948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10949     ClearHighlights(); /* [AS] */
10950 }
10951
10952 /* Pause for `ms' milliseconds */
10953 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10954 void
10955 TimeDelay(ms)
10956      long ms;
10957 {
10958     TimeMark m1, m2;
10959
10960     GetTimeMark(&m1);
10961     do {
10962         GetTimeMark(&m2);
10963     } while (SubtractTimeMarks(&m2, &m1) < ms);
10964 }
10965
10966 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10967 void
10968 SendMultiLineToICS(buf)
10969      char *buf;
10970 {
10971     char temp[MSG_SIZ+1], *p;
10972     int len;
10973
10974     len = strlen(buf);
10975     if (len > MSG_SIZ)
10976       len = MSG_SIZ;
10977   
10978     strncpy(temp, buf, len);
10979     temp[len] = 0;
10980
10981     p = temp;
10982     while (*p) {
10983         if (*p == '\n' || *p == '\r')
10984           *p = ' ';
10985         ++p;
10986     }
10987
10988     strcat(temp, "\n");
10989     SendToICS(temp);
10990     SendToPlayer(temp, strlen(temp));
10991 }
10992
10993 void
10994 SetWhiteToPlayEvent()
10995 {
10996     if (gameMode == EditPosition) {
10997         blackPlaysFirst = FALSE;
10998         DisplayBothClocks();    /* works because currentMove is 0 */
10999     } else if (gameMode == IcsExamining) {
11000         SendToICS(ics_prefix);
11001         SendToICS("tomove white\n");
11002     }
11003 }
11004
11005 void
11006 SetBlackToPlayEvent()
11007 {
11008     if (gameMode == EditPosition) {
11009         blackPlaysFirst = TRUE;
11010         currentMove = 1;        /* kludge */
11011         DisplayBothClocks();
11012         currentMove = 0;
11013     } else if (gameMode == IcsExamining) {
11014         SendToICS(ics_prefix);
11015         SendToICS("tomove black\n");
11016     }
11017 }
11018
11019 void
11020 EditPositionMenuEvent(selection, x, y)
11021      ChessSquare selection;
11022      int x, y;
11023 {
11024     char buf[MSG_SIZ];
11025     ChessSquare piece = boards[0][y][x];
11026
11027     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11028
11029     switch (selection) {
11030       case ClearBoard:
11031         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11032             SendToICS(ics_prefix);
11033             SendToICS("bsetup clear\n");
11034         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11035             SendToICS(ics_prefix);
11036             SendToICS("clearboard\n");
11037         } else {
11038             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11039                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11040                 for (y = 0; y < BOARD_HEIGHT; y++) {
11041                     if (gameMode == IcsExamining) {
11042                         if (boards[currentMove][y][x] != EmptySquare) {
11043                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11044                                     AAA + x, ONE + y);
11045                             SendToICS(buf);
11046                         }
11047                     } else {
11048                         boards[0][y][x] = p;
11049                     }
11050                 }
11051             }
11052         }
11053         if (gameMode == EditPosition) {
11054             DrawPosition(FALSE, boards[0]);
11055         }
11056         break;
11057
11058       case WhitePlay:
11059         SetWhiteToPlayEvent();
11060         break;
11061
11062       case BlackPlay:
11063         SetBlackToPlayEvent();
11064         break;
11065
11066       case EmptySquare:
11067         if (gameMode == IcsExamining) {
11068             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11069             SendToICS(buf);
11070         } else {
11071             boards[0][y][x] = EmptySquare;
11072             DrawPosition(FALSE, boards[0]);
11073         }
11074         break;
11075
11076       case PromotePiece:
11077         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11078            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11079             selection = (ChessSquare) (PROMOTED piece);
11080         } else if(piece == EmptySquare) selection = WhiteSilver;
11081         else selection = (ChessSquare)((int)piece - 1);
11082         goto defaultlabel;
11083
11084       case DemotePiece:
11085         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11086            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11087             selection = (ChessSquare) (DEMOTED piece);
11088         } else if(piece == EmptySquare) selection = BlackSilver;
11089         else selection = (ChessSquare)((int)piece + 1);       
11090         goto defaultlabel;
11091
11092       case WhiteQueen:
11093       case BlackQueen:
11094         if(gameInfo.variant == VariantShatranj ||
11095            gameInfo.variant == VariantXiangqi  ||
11096            gameInfo.variant == VariantCourier    )
11097             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11098         goto defaultlabel;
11099
11100       case WhiteKing:
11101       case BlackKing:
11102         if(gameInfo.variant == VariantXiangqi)
11103             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11104         if(gameInfo.variant == VariantKnightmate)
11105             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11106       default:
11107         defaultlabel:
11108         if (gameMode == IcsExamining) {
11109             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11110                     PieceToChar(selection), AAA + x, ONE + y);
11111             SendToICS(buf);
11112         } else {
11113             boards[0][y][x] = selection;
11114             DrawPosition(FALSE, boards[0]);
11115         }
11116         break;
11117     }
11118 }
11119
11120
11121 void
11122 DropMenuEvent(selection, x, y)
11123      ChessSquare selection;
11124      int x, y;
11125 {
11126     ChessMove moveType;
11127
11128     switch (gameMode) {
11129       case IcsPlayingWhite:
11130       case MachinePlaysBlack:
11131         if (!WhiteOnMove(currentMove)) {
11132             DisplayMoveError(_("It is Black's turn"));
11133             return;
11134         }
11135         moveType = WhiteDrop;
11136         break;
11137       case IcsPlayingBlack:
11138       case MachinePlaysWhite:
11139         if (WhiteOnMove(currentMove)) {
11140             DisplayMoveError(_("It is White's turn"));
11141             return;
11142         }
11143         moveType = BlackDrop;
11144         break;
11145       case EditGame:
11146         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11147         break;
11148       default:
11149         return;
11150     }
11151
11152     if (moveType == BlackDrop && selection < BlackPawn) {
11153       selection = (ChessSquare) ((int) selection
11154                                  + (int) BlackPawn - (int) WhitePawn);
11155     }
11156     if (boards[currentMove][y][x] != EmptySquare) {
11157         DisplayMoveError(_("That square is occupied"));
11158         return;
11159     }
11160
11161     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11162 }
11163
11164 void
11165 AcceptEvent()
11166 {
11167     /* Accept a pending offer of any kind from opponent */
11168     
11169     if (appData.icsActive) {
11170         SendToICS(ics_prefix);
11171         SendToICS("accept\n");
11172     } else if (cmailMsgLoaded) {
11173         if (currentMove == cmailOldMove &&
11174             commentList[cmailOldMove] != NULL &&
11175             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11176                    "Black offers a draw" : "White offers a draw")) {
11177             TruncateGame();
11178             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11179             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11180         } else {
11181             DisplayError(_("There is no pending offer on this move"), 0);
11182             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11183         }
11184     } else {
11185         /* Not used for offers from chess program */
11186     }
11187 }
11188
11189 void
11190 DeclineEvent()
11191 {
11192     /* Decline a pending offer of any kind from opponent */
11193     
11194     if (appData.icsActive) {
11195         SendToICS(ics_prefix);
11196         SendToICS("decline\n");
11197     } else if (cmailMsgLoaded) {
11198         if (currentMove == cmailOldMove &&
11199             commentList[cmailOldMove] != NULL &&
11200             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11201                    "Black offers a draw" : "White offers a draw")) {
11202 #ifdef NOTDEF
11203             AppendComment(cmailOldMove, "Draw declined");
11204             DisplayComment(cmailOldMove - 1, "Draw declined");
11205 #endif /*NOTDEF*/
11206         } else {
11207             DisplayError(_("There is no pending offer on this move"), 0);
11208         }
11209     } else {
11210         /* Not used for offers from chess program */
11211     }
11212 }
11213
11214 void
11215 RematchEvent()
11216 {
11217     /* Issue ICS rematch command */
11218     if (appData.icsActive) {
11219         SendToICS(ics_prefix);
11220         SendToICS("rematch\n");
11221     }
11222 }
11223
11224 void
11225 CallFlagEvent()
11226 {
11227     /* Call your opponent's flag (claim a win on time) */
11228     if (appData.icsActive) {
11229         SendToICS(ics_prefix);
11230         SendToICS("flag\n");
11231     } else {
11232         switch (gameMode) {
11233           default:
11234             return;
11235           case MachinePlaysWhite:
11236             if (whiteFlag) {
11237                 if (blackFlag)
11238                   GameEnds(GameIsDrawn, "Both players ran out of time",
11239                            GE_PLAYER);
11240                 else
11241                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11242             } else {
11243                 DisplayError(_("Your opponent is not out of time"), 0);
11244             }
11245             break;
11246           case MachinePlaysBlack:
11247             if (blackFlag) {
11248                 if (whiteFlag)
11249                   GameEnds(GameIsDrawn, "Both players ran out of time",
11250                            GE_PLAYER);
11251                 else
11252                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11253             } else {
11254                 DisplayError(_("Your opponent is not out of time"), 0);
11255             }
11256             break;
11257         }
11258     }
11259 }
11260
11261 void
11262 DrawEvent()
11263 {
11264     /* Offer draw or accept pending draw offer from opponent */
11265     
11266     if (appData.icsActive) {
11267         /* Note: tournament rules require draw offers to be
11268            made after you make your move but before you punch
11269            your clock.  Currently ICS doesn't let you do that;
11270            instead, you immediately punch your clock after making
11271            a move, but you can offer a draw at any time. */
11272         
11273         SendToICS(ics_prefix);
11274         SendToICS("draw\n");
11275     } else if (cmailMsgLoaded) {
11276         if (currentMove == cmailOldMove &&
11277             commentList[cmailOldMove] != NULL &&
11278             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11279                    "Black offers a draw" : "White offers a draw")) {
11280             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11281             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11282         } else if (currentMove == cmailOldMove + 1) {
11283             char *offer = WhiteOnMove(cmailOldMove) ?
11284               "White offers a draw" : "Black offers a draw";
11285             AppendComment(currentMove, offer);
11286             DisplayComment(currentMove - 1, offer);
11287             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11288         } else {
11289             DisplayError(_("You must make your move before offering a draw"), 0);
11290             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11291         }
11292     } else if (first.offeredDraw) {
11293         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11294     } else {
11295         if (first.sendDrawOffers) {
11296             SendToProgram("draw\n", &first);
11297             userOfferedDraw = TRUE;
11298         }
11299     }
11300 }
11301
11302 void
11303 AdjournEvent()
11304 {
11305     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11306     
11307     if (appData.icsActive) {
11308         SendToICS(ics_prefix);
11309         SendToICS("adjourn\n");
11310     } else {
11311         /* Currently GNU Chess doesn't offer or accept Adjourns */
11312     }
11313 }
11314
11315
11316 void
11317 AbortEvent()
11318 {
11319     /* Offer Abort or accept pending Abort offer from opponent */
11320     
11321     if (appData.icsActive) {
11322         SendToICS(ics_prefix);
11323         SendToICS("abort\n");
11324     } else {
11325         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11326     }
11327 }
11328
11329 void
11330 ResignEvent()
11331 {
11332     /* Resign.  You can do this even if it's not your turn. */
11333     
11334     if (appData.icsActive) {
11335         SendToICS(ics_prefix);
11336         SendToICS("resign\n");
11337     } else {
11338         switch (gameMode) {
11339           case MachinePlaysWhite:
11340             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11341             break;
11342           case MachinePlaysBlack:
11343             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11344             break;
11345           case EditGame:
11346             if (cmailMsgLoaded) {
11347                 TruncateGame();
11348                 if (WhiteOnMove(cmailOldMove)) {
11349                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11350                 } else {
11351                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11352                 }
11353                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11354             }
11355             break;
11356           default:
11357             break;
11358         }
11359     }
11360 }
11361
11362
11363 void
11364 StopObservingEvent()
11365 {
11366     /* Stop observing current games */
11367     SendToICS(ics_prefix);
11368     SendToICS("unobserve\n");
11369 }
11370
11371 void
11372 StopExaminingEvent()
11373 {
11374     /* Stop observing current game */
11375     SendToICS(ics_prefix);
11376     SendToICS("unexamine\n");
11377 }
11378
11379 void
11380 ForwardInner(target)
11381      int target;
11382 {
11383     int limit;
11384
11385     if (appData.debugMode)
11386         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11387                 target, currentMove, forwardMostMove);
11388
11389     if (gameMode == EditPosition)
11390       return;
11391
11392     if (gameMode == PlayFromGameFile && !pausing)
11393       PauseEvent();
11394     
11395     if (gameMode == IcsExamining && pausing)
11396       limit = pauseExamForwardMostMove;
11397     else
11398       limit = forwardMostMove;
11399     
11400     if (target > limit) target = limit;
11401
11402     if (target > 0 && moveList[target - 1][0]) {
11403         int fromX, fromY, toX, toY;
11404         toX = moveList[target - 1][2] - AAA;
11405         toY = moveList[target - 1][3] - ONE;
11406         if (moveList[target - 1][1] == '@') {
11407             if (appData.highlightLastMove) {
11408                 SetHighlights(-1, -1, toX, toY);
11409             }
11410         } else {
11411             fromX = moveList[target - 1][0] - AAA;
11412             fromY = moveList[target - 1][1] - ONE;
11413             if (target == currentMove + 1) {
11414                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11415             }
11416             if (appData.highlightLastMove) {
11417                 SetHighlights(fromX, fromY, toX, toY);
11418             }
11419         }
11420     }
11421     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11422         gameMode == Training || gameMode == PlayFromGameFile || 
11423         gameMode == AnalyzeFile) {
11424         while (currentMove < target) {
11425             SendMoveToProgram(currentMove++, &first);
11426         }
11427     } else {
11428         currentMove = target;
11429     }
11430     
11431     if (gameMode == EditGame || gameMode == EndOfGame) {
11432         whiteTimeRemaining = timeRemaining[0][currentMove];
11433         blackTimeRemaining = timeRemaining[1][currentMove];
11434     }
11435     DisplayBothClocks();
11436     DisplayMove(currentMove - 1);
11437     DrawPosition(FALSE, boards[currentMove]);
11438     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11439     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11440         DisplayComment(currentMove - 1, commentList[currentMove]);
11441     }
11442 }
11443
11444
11445 void
11446 ForwardEvent()
11447 {
11448     if (gameMode == IcsExamining && !pausing) {
11449         SendToICS(ics_prefix);
11450         SendToICS("forward\n");
11451     } else {
11452         ForwardInner(currentMove + 1);
11453     }
11454 }
11455
11456 void
11457 ToEndEvent()
11458 {
11459     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11460         /* to optimze, we temporarily turn off analysis mode while we feed
11461          * the remaining moves to the engine. Otherwise we get analysis output
11462          * after each move.
11463          */ 
11464         if (first.analysisSupport) {
11465           SendToProgram("exit\nforce\n", &first);
11466           first.analyzing = FALSE;
11467         }
11468     }
11469         
11470     if (gameMode == IcsExamining && !pausing) {
11471         SendToICS(ics_prefix);
11472         SendToICS("forward 999999\n");
11473     } else {
11474         ForwardInner(forwardMostMove);
11475     }
11476
11477     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11478         /* we have fed all the moves, so reactivate analysis mode */
11479         SendToProgram("analyze\n", &first);
11480         first.analyzing = TRUE;
11481         /*first.maybeThinking = TRUE;*/
11482         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11483     }
11484 }
11485
11486 void
11487 BackwardInner(target)
11488      int target;
11489 {
11490     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11491
11492     if (appData.debugMode)
11493         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11494                 target, currentMove, forwardMostMove);
11495
11496     if (gameMode == EditPosition) return;
11497     if (currentMove <= backwardMostMove) {
11498         ClearHighlights();
11499         DrawPosition(full_redraw, boards[currentMove]);
11500         return;
11501     }
11502     if (gameMode == PlayFromGameFile && !pausing)
11503       PauseEvent();
11504     
11505     if (moveList[target][0]) {
11506         int fromX, fromY, toX, toY;
11507         toX = moveList[target][2] - AAA;
11508         toY = moveList[target][3] - ONE;
11509         if (moveList[target][1] == '@') {
11510             if (appData.highlightLastMove) {
11511                 SetHighlights(-1, -1, toX, toY);
11512             }
11513         } else {
11514             fromX = moveList[target][0] - AAA;
11515             fromY = moveList[target][1] - ONE;
11516             if (target == currentMove - 1) {
11517                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11518             }
11519             if (appData.highlightLastMove) {
11520                 SetHighlights(fromX, fromY, toX, toY);
11521             }
11522         }
11523     }
11524     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11525         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11526         while (currentMove > target) {
11527             SendToProgram("undo\n", &first);
11528             currentMove--;
11529         }
11530     } else {
11531         currentMove = target;
11532     }
11533     
11534     if (gameMode == EditGame || gameMode == EndOfGame) {
11535         whiteTimeRemaining = timeRemaining[0][currentMove];
11536         blackTimeRemaining = timeRemaining[1][currentMove];
11537     }
11538     DisplayBothClocks();
11539     DisplayMove(currentMove - 1);
11540     DrawPosition(full_redraw, boards[currentMove]);
11541     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11542     // [HGM] PV info: routine tests if comment empty
11543     DisplayComment(currentMove - 1, commentList[currentMove]);
11544 }
11545
11546 void
11547 BackwardEvent()
11548 {
11549     if (gameMode == IcsExamining && !pausing) {
11550         SendToICS(ics_prefix);
11551         SendToICS("backward\n");
11552     } else {
11553         BackwardInner(currentMove - 1);
11554     }
11555 }
11556
11557 void
11558 ToStartEvent()
11559 {
11560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11561         /* to optimze, we temporarily turn off analysis mode while we undo
11562          * all the moves. Otherwise we get analysis output after each undo.
11563          */ 
11564         if (first.analysisSupport) {
11565           SendToProgram("exit\nforce\n", &first);
11566           first.analyzing = FALSE;
11567         }
11568     }
11569
11570     if (gameMode == IcsExamining && !pausing) {
11571         SendToICS(ics_prefix);
11572         SendToICS("backward 999999\n");
11573     } else {
11574         BackwardInner(backwardMostMove);
11575     }
11576
11577     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11578         /* we have fed all the moves, so reactivate analysis mode */
11579         SendToProgram("analyze\n", &first);
11580         first.analyzing = TRUE;
11581         /*first.maybeThinking = TRUE;*/
11582         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11583     }
11584 }
11585
11586 void
11587 ToNrEvent(int to)
11588 {
11589   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11590   if (to >= forwardMostMove) to = forwardMostMove;
11591   if (to <= backwardMostMove) to = backwardMostMove;
11592   if (to < currentMove) {
11593     BackwardInner(to);
11594   } else {
11595     ForwardInner(to);
11596   }
11597 }
11598
11599 void
11600 RevertEvent()
11601 {
11602     if (gameMode != IcsExamining) {
11603         DisplayError(_("You are not examining a game"), 0);
11604         return;
11605     }
11606     if (pausing) {
11607         DisplayError(_("You can't revert while pausing"), 0);
11608         return;
11609     }
11610     SendToICS(ics_prefix);
11611     SendToICS("revert\n");
11612 }
11613
11614 void
11615 RetractMoveEvent()
11616 {
11617     switch (gameMode) {
11618       case MachinePlaysWhite:
11619       case MachinePlaysBlack:
11620         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11621             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11622             return;
11623         }
11624         if (forwardMostMove < 2) return;
11625         currentMove = forwardMostMove = forwardMostMove - 2;
11626         whiteTimeRemaining = timeRemaining[0][currentMove];
11627         blackTimeRemaining = timeRemaining[1][currentMove];
11628         DisplayBothClocks();
11629         DisplayMove(currentMove - 1);
11630         ClearHighlights();/*!! could figure this out*/
11631         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11632         SendToProgram("remove\n", &first);
11633         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11634         break;
11635
11636       case BeginningOfGame:
11637       default:
11638         break;
11639
11640       case IcsPlayingWhite:
11641       case IcsPlayingBlack:
11642         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11643             SendToICS(ics_prefix);
11644             SendToICS("takeback 2\n");
11645         } else {
11646             SendToICS(ics_prefix);
11647             SendToICS("takeback 1\n");
11648         }
11649         break;
11650     }
11651 }
11652
11653 void
11654 MoveNowEvent()
11655 {
11656     ChessProgramState *cps;
11657
11658     switch (gameMode) {
11659       case MachinePlaysWhite:
11660         if (!WhiteOnMove(forwardMostMove)) {
11661             DisplayError(_("It is your turn"), 0);
11662             return;
11663         }
11664         cps = &first;
11665         break;
11666       case MachinePlaysBlack:
11667         if (WhiteOnMove(forwardMostMove)) {
11668             DisplayError(_("It is your turn"), 0);
11669             return;
11670         }
11671         cps = &first;
11672         break;
11673       case TwoMachinesPlay:
11674         if (WhiteOnMove(forwardMostMove) ==
11675             (first.twoMachinesColor[0] == 'w')) {
11676             cps = &first;
11677         } else {
11678             cps = &second;
11679         }
11680         break;
11681       case BeginningOfGame:
11682       default:
11683         return;
11684     }
11685     SendToProgram("?\n", cps);
11686 }
11687
11688 void
11689 TruncateGameEvent()
11690 {
11691     EditGameEvent();
11692     if (gameMode != EditGame) return;
11693     TruncateGame();
11694 }
11695
11696 void
11697 TruncateGame()
11698 {
11699     if (forwardMostMove > currentMove) {
11700         if (gameInfo.resultDetails != NULL) {
11701             free(gameInfo.resultDetails);
11702             gameInfo.resultDetails = NULL;
11703             gameInfo.result = GameUnfinished;
11704         }
11705         forwardMostMove = currentMove;
11706         HistorySet(parseList, backwardMostMove, forwardMostMove,
11707                    currentMove-1);
11708     }
11709 }
11710
11711 void
11712 HintEvent()
11713 {
11714     if (appData.noChessProgram) return;
11715     switch (gameMode) {
11716       case MachinePlaysWhite:
11717         if (WhiteOnMove(forwardMostMove)) {
11718             DisplayError(_("Wait until your turn"), 0);
11719             return;
11720         }
11721         break;
11722       case BeginningOfGame:
11723       case MachinePlaysBlack:
11724         if (!WhiteOnMove(forwardMostMove)) {
11725             DisplayError(_("Wait until your turn"), 0);
11726             return;
11727         }
11728         break;
11729       default:
11730         DisplayError(_("No hint available"), 0);
11731         return;
11732     }
11733     SendToProgram("hint\n", &first);
11734     hintRequested = TRUE;
11735 }
11736
11737 void
11738 BookEvent()
11739 {
11740     if (appData.noChessProgram) return;
11741     switch (gameMode) {
11742       case MachinePlaysWhite:
11743         if (WhiteOnMove(forwardMostMove)) {
11744             DisplayError(_("Wait until your turn"), 0);
11745             return;
11746         }
11747         break;
11748       case BeginningOfGame:
11749       case MachinePlaysBlack:
11750         if (!WhiteOnMove(forwardMostMove)) {
11751             DisplayError(_("Wait until your turn"), 0);
11752             return;
11753         }
11754         break;
11755       case EditPosition:
11756         EditPositionDone();
11757         break;
11758       case TwoMachinesPlay:
11759         return;
11760       default:
11761         break;
11762     }
11763     SendToProgram("bk\n", &first);
11764     bookOutput[0] = NULLCHAR;
11765     bookRequested = TRUE;
11766 }
11767
11768 void
11769 AboutGameEvent()
11770 {
11771     char *tags = PGNTags(&gameInfo);
11772     TagsPopUp(tags, CmailMsg());
11773     free(tags);
11774 }
11775
11776 /* end button procedures */
11777
11778 void
11779 PrintPosition(fp, move)
11780      FILE *fp;
11781      int move;
11782 {
11783     int i, j;
11784     
11785     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11786         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11787             char c = PieceToChar(boards[move][i][j]);
11788             fputc(c == 'x' ? '.' : c, fp);
11789             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11790         }
11791     }
11792     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11793       fprintf(fp, "white to play\n");
11794     else
11795       fprintf(fp, "black to play\n");
11796 }
11797
11798 void
11799 PrintOpponents(fp)
11800      FILE *fp;
11801 {
11802     if (gameInfo.white != NULL) {
11803         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11804     } else {
11805         fprintf(fp, "\n");
11806     }
11807 }
11808
11809 /* Find last component of program's own name, using some heuristics */
11810 void
11811 TidyProgramName(prog, host, buf)
11812      char *prog, *host, buf[MSG_SIZ];
11813 {
11814     char *p, *q;
11815     int local = (strcmp(host, "localhost") == 0);
11816     while (!local && (p = strchr(prog, ';')) != NULL) {
11817         p++;
11818         while (*p == ' ') p++;
11819         prog = p;
11820     }
11821     if (*prog == '"' || *prog == '\'') {
11822         q = strchr(prog + 1, *prog);
11823     } else {
11824         q = strchr(prog, ' ');
11825     }
11826     if (q == NULL) q = prog + strlen(prog);
11827     p = q;
11828     while (p >= prog && *p != '/' && *p != '\\') p--;
11829     p++;
11830     if(p == prog && *p == '"') p++;
11831     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11832     memcpy(buf, p, q - p);
11833     buf[q - p] = NULLCHAR;
11834     if (!local) {
11835         strcat(buf, "@");
11836         strcat(buf, host);
11837     }
11838 }
11839
11840 char *
11841 TimeControlTagValue()
11842 {
11843     char buf[MSG_SIZ];
11844     if (!appData.clockMode) {
11845         strcpy(buf, "-");
11846     } else if (movesPerSession > 0) {
11847         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11848     } else if (timeIncrement == 0) {
11849         sprintf(buf, "%ld", timeControl/1000);
11850     } else {
11851         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11852     }
11853     return StrSave(buf);
11854 }
11855
11856 void
11857 SetGameInfo()
11858 {
11859     /* This routine is used only for certain modes */
11860     VariantClass v = gameInfo.variant;
11861     ClearGameInfo(&gameInfo);
11862     gameInfo.variant = v;
11863
11864     switch (gameMode) {
11865       case MachinePlaysWhite:
11866         gameInfo.event = StrSave( appData.pgnEventHeader );
11867         gameInfo.site = StrSave(HostName());
11868         gameInfo.date = PGNDate();
11869         gameInfo.round = StrSave("-");
11870         gameInfo.white = StrSave(first.tidy);
11871         gameInfo.black = StrSave(UserName());
11872         gameInfo.timeControl = TimeControlTagValue();
11873         break;
11874
11875       case MachinePlaysBlack:
11876         gameInfo.event = StrSave( appData.pgnEventHeader );
11877         gameInfo.site = StrSave(HostName());
11878         gameInfo.date = PGNDate();
11879         gameInfo.round = StrSave("-");
11880         gameInfo.white = StrSave(UserName());
11881         gameInfo.black = StrSave(first.tidy);
11882         gameInfo.timeControl = TimeControlTagValue();
11883         break;
11884
11885       case TwoMachinesPlay:
11886         gameInfo.event = StrSave( appData.pgnEventHeader );
11887         gameInfo.site = StrSave(HostName());
11888         gameInfo.date = PGNDate();
11889         if (matchGame > 0) {
11890             char buf[MSG_SIZ];
11891             sprintf(buf, "%d", matchGame);
11892             gameInfo.round = StrSave(buf);
11893         } else {
11894             gameInfo.round = StrSave("-");
11895         }
11896         if (first.twoMachinesColor[0] == 'w') {
11897             gameInfo.white = StrSave(first.tidy);
11898             gameInfo.black = StrSave(second.tidy);
11899         } else {
11900             gameInfo.white = StrSave(second.tidy);
11901             gameInfo.black = StrSave(first.tidy);
11902         }
11903         gameInfo.timeControl = TimeControlTagValue();
11904         break;
11905
11906       case EditGame:
11907         gameInfo.event = StrSave("Edited game");
11908         gameInfo.site = StrSave(HostName());
11909         gameInfo.date = PGNDate();
11910         gameInfo.round = StrSave("-");
11911         gameInfo.white = StrSave("-");
11912         gameInfo.black = StrSave("-");
11913         break;
11914
11915       case EditPosition:
11916         gameInfo.event = StrSave("Edited position");
11917         gameInfo.site = StrSave(HostName());
11918         gameInfo.date = PGNDate();
11919         gameInfo.round = StrSave("-");
11920         gameInfo.white = StrSave("-");
11921         gameInfo.black = StrSave("-");
11922         break;
11923
11924       case IcsPlayingWhite:
11925       case IcsPlayingBlack:
11926       case IcsObserving:
11927       case IcsExamining:
11928         break;
11929
11930       case PlayFromGameFile:
11931         gameInfo.event = StrSave("Game from non-PGN file");
11932         gameInfo.site = StrSave(HostName());
11933         gameInfo.date = PGNDate();
11934         gameInfo.round = StrSave("-");
11935         gameInfo.white = StrSave("?");
11936         gameInfo.black = StrSave("?");
11937         break;
11938
11939       default:
11940         break;
11941     }
11942 }
11943
11944 void
11945 ReplaceComment(index, text)
11946      int index;
11947      char *text;
11948 {
11949     int len;
11950
11951     while (*text == '\n') text++;
11952     len = strlen(text);
11953     while (len > 0 && text[len - 1] == '\n') len--;
11954
11955     if (commentList[index] != NULL)
11956       free(commentList[index]);
11957
11958     if (len == 0) {
11959         commentList[index] = NULL;
11960         return;
11961     }
11962     commentList[index] = (char *) malloc(len + 2);
11963     strncpy(commentList[index], text, len);
11964     commentList[index][len] = '\n';
11965     commentList[index][len + 1] = NULLCHAR;
11966 }
11967
11968 void
11969 CrushCRs(text)
11970      char *text;
11971 {
11972   char *p = text;
11973   char *q = text;
11974   char ch;
11975
11976   do {
11977     ch = *p++;
11978     if (ch == '\r') continue;
11979     *q++ = ch;
11980   } while (ch != '\0');
11981 }
11982
11983 void
11984 AppendComment(index, text)
11985      int index;
11986      char *text;
11987 {
11988     int oldlen, len;
11989     char *old;
11990
11991     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11992
11993     CrushCRs(text);
11994     while (*text == '\n') text++;
11995     len = strlen(text);
11996     while (len > 0 && text[len - 1] == '\n') len--;
11997
11998     if (len == 0) return;
11999
12000     if (commentList[index] != NULL) {
12001         old = commentList[index];
12002         oldlen = strlen(old);
12003         commentList[index] = (char *) malloc(oldlen + len + 2);
12004         strcpy(commentList[index], old);
12005         free(old);
12006         strncpy(&commentList[index][oldlen], text, len);
12007         commentList[index][oldlen + len] = '\n';
12008         commentList[index][oldlen + len + 1] = NULLCHAR;
12009     } else {
12010         commentList[index] = (char *) malloc(len + 2);
12011         strncpy(commentList[index], text, len);
12012         commentList[index][len] = '\n';
12013         commentList[index][len + 1] = NULLCHAR;
12014     }
12015 }
12016
12017 static char * FindStr( char * text, char * sub_text )
12018 {
12019     char * result = strstr( text, sub_text );
12020
12021     if( result != NULL ) {
12022         result += strlen( sub_text );
12023     }
12024
12025     return result;
12026 }
12027
12028 /* [AS] Try to extract PV info from PGN comment */
12029 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12030 char *GetInfoFromComment( int index, char * text )
12031 {
12032     char * sep = text;
12033
12034     if( text != NULL && index > 0 ) {
12035         int score = 0;
12036         int depth = 0;
12037         int time = -1, sec = 0, deci;
12038         char * s_eval = FindStr( text, "[%eval " );
12039         char * s_emt = FindStr( text, "[%emt " );
12040
12041         if( s_eval != NULL || s_emt != NULL ) {
12042             /* New style */
12043             char delim;
12044
12045             if( s_eval != NULL ) {
12046                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12047                     return text;
12048                 }
12049
12050                 if( delim != ']' ) {
12051                     return text;
12052                 }
12053             }
12054
12055             if( s_emt != NULL ) {
12056             }
12057         }
12058         else {
12059             /* We expect something like: [+|-]nnn.nn/dd */
12060             int score_lo = 0;
12061
12062             sep = strchr( text, '/' );
12063             if( sep == NULL || sep < (text+4) ) {
12064                 return text;
12065             }
12066
12067             time = -1; sec = -1; deci = -1;
12068             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12069                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12070                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12071                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12072                 return text;
12073             }
12074
12075             if( score_lo < 0 || score_lo >= 100 ) {
12076                 return text;
12077             }
12078
12079             if(sec >= 0) time = 600*time + 10*sec; else
12080             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12081
12082             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12083
12084             /* [HGM] PV time: now locate end of PV info */
12085             while( *++sep >= '0' && *sep <= '9'); // strip depth
12086             if(time >= 0)
12087             while( *++sep >= '0' && *sep <= '9'); // strip time
12088             if(sec >= 0)
12089             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12090             if(deci >= 0)
12091             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12092             while(*sep == ' ') sep++;
12093         }
12094
12095         if( depth <= 0 ) {
12096             return text;
12097         }
12098
12099         if( time < 0 ) {
12100             time = -1;
12101         }
12102
12103         pvInfoList[index-1].depth = depth;
12104         pvInfoList[index-1].score = score;
12105         pvInfoList[index-1].time  = 10*time; // centi-sec
12106     }
12107     return sep;
12108 }
12109
12110 void
12111 SendToProgram(message, cps)
12112      char *message;
12113      ChessProgramState *cps;
12114 {
12115     int count, outCount, error;
12116     char buf[MSG_SIZ];
12117
12118     if (cps->pr == NULL) return;
12119     Attention(cps);
12120     
12121     if (appData.debugMode) {
12122         TimeMark now;
12123         GetTimeMark(&now);
12124         fprintf(debugFP, "%ld >%-6s: %s", 
12125                 SubtractTimeMarks(&now, &programStartTime),
12126                 cps->which, message);
12127     }
12128     
12129     count = strlen(message);
12130     outCount = OutputToProcess(cps->pr, message, count, &error);
12131     if (outCount < count && !exiting 
12132                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12133         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12134         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12135             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12136                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12137                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12138             } else {
12139                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12140             }
12141             gameInfo.resultDetails = buf;
12142         }
12143         DisplayFatalError(buf, error, 1);
12144     }
12145 }
12146
12147 void
12148 ReceiveFromProgram(isr, closure, message, count, error)
12149      InputSourceRef isr;
12150      VOIDSTAR closure;
12151      char *message;
12152      int count;
12153      int error;
12154 {
12155     char *end_str;
12156     char buf[MSG_SIZ];
12157     ChessProgramState *cps = (ChessProgramState *)closure;
12158
12159     if (isr != cps->isr) return; /* Killed intentionally */
12160     if (count <= 0) {
12161         if (count == 0) {
12162             sprintf(buf,
12163                     _("Error: %s chess program (%s) exited unexpectedly"),
12164                     cps->which, cps->program);
12165         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12166                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12167                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12168                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12169                 } else {
12170                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12171                 }
12172                 gameInfo.resultDetails = buf;
12173             }
12174             RemoveInputSource(cps->isr);
12175             DisplayFatalError(buf, 0, 1);
12176         } else {
12177             sprintf(buf,
12178                     _("Error reading from %s chess program (%s)"),
12179                     cps->which, cps->program);
12180             RemoveInputSource(cps->isr);
12181
12182             /* [AS] Program is misbehaving badly... kill it */
12183             if( count == -2 ) {
12184                 DestroyChildProcess( cps->pr, 9 );
12185                 cps->pr = NoProc;
12186             }
12187
12188             DisplayFatalError(buf, error, 1);
12189         }
12190         return;
12191     }
12192     
12193     if ((end_str = strchr(message, '\r')) != NULL)
12194       *end_str = NULLCHAR;
12195     if ((end_str = strchr(message, '\n')) != NULL)
12196       *end_str = NULLCHAR;
12197     
12198     if (appData.debugMode) {
12199         TimeMark now; int print = 1;
12200         char *quote = ""; char c; int i;
12201
12202         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12203                 char start = message[0];
12204                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12205                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12206                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12207                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12208                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12209                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12210                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12211                         { quote = "# "; print = (appData.engineComments == 2); }
12212                 message[0] = start; // restore original message
12213         }
12214         if(print) {
12215                 GetTimeMark(&now);
12216                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12217                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12218                         quote,
12219                         message);
12220         }
12221     }
12222
12223     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12224     if (appData.icsEngineAnalyze) {
12225         if (strstr(message, "whisper") != NULL ||
12226              strstr(message, "kibitz") != NULL || 
12227             strstr(message, "tellics") != NULL) return;
12228     }
12229
12230     HandleMachineMove(message, cps);
12231 }
12232
12233
12234 void
12235 SendTimeControl(cps, mps, tc, inc, sd, st)
12236      ChessProgramState *cps;
12237      int mps, inc, sd, st;
12238      long tc;
12239 {
12240     char buf[MSG_SIZ];
12241     int seconds;
12242
12243     if( timeControl_2 > 0 ) {
12244         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12245             tc = timeControl_2;
12246         }
12247     }
12248     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12249     inc /= cps->timeOdds;
12250     st  /= cps->timeOdds;
12251
12252     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12253
12254     if (st > 0) {
12255       /* Set exact time per move, normally using st command */
12256       if (cps->stKludge) {
12257         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12258         seconds = st % 60;
12259         if (seconds == 0) {
12260           sprintf(buf, "level 1 %d\n", st/60);
12261         } else {
12262           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12263         }
12264       } else {
12265         sprintf(buf, "st %d\n", st);
12266       }
12267     } else {
12268       /* Set conventional or incremental time control, using level command */
12269       if (seconds == 0) {
12270         /* Note old gnuchess bug -- minutes:seconds used to not work.
12271            Fixed in later versions, but still avoid :seconds
12272            when seconds is 0. */
12273         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12274       } else {
12275         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12276                 seconds, inc/1000);
12277       }
12278     }
12279     SendToProgram(buf, cps);
12280
12281     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12282     /* Orthogonally, limit search to given depth */
12283     if (sd > 0) {
12284       if (cps->sdKludge) {
12285         sprintf(buf, "depth\n%d\n", sd);
12286       } else {
12287         sprintf(buf, "sd %d\n", sd);
12288       }
12289       SendToProgram(buf, cps);
12290     }
12291
12292     if(cps->nps > 0) { /* [HGM] nps */
12293         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12294         else {
12295                 sprintf(buf, "nps %d\n", cps->nps);
12296               SendToProgram(buf, cps);
12297         }
12298     }
12299 }
12300
12301 ChessProgramState *WhitePlayer()
12302 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12303 {
12304     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12305        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12306         return &second;
12307     return &first;
12308 }
12309
12310 void
12311 SendTimeRemaining(cps, machineWhite)
12312      ChessProgramState *cps;
12313      int /*boolean*/ machineWhite;
12314 {
12315     char message[MSG_SIZ];
12316     long time, otime;
12317
12318     /* Note: this routine must be called when the clocks are stopped
12319        or when they have *just* been set or switched; otherwise
12320        it will be off by the time since the current tick started.
12321     */
12322     if (machineWhite) {
12323         time = whiteTimeRemaining / 10;
12324         otime = blackTimeRemaining / 10;
12325     } else {
12326         time = blackTimeRemaining / 10;
12327         otime = whiteTimeRemaining / 10;
12328     }
12329     /* [HGM] translate opponent's time by time-odds factor */
12330     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12331     if (appData.debugMode) {
12332         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12333     }
12334
12335     if (time <= 0) time = 1;
12336     if (otime <= 0) otime = 1;
12337     
12338     sprintf(message, "time %ld\n", time);
12339     SendToProgram(message, cps);
12340
12341     sprintf(message, "otim %ld\n", otime);
12342     SendToProgram(message, cps);
12343 }
12344
12345 int
12346 BoolFeature(p, name, loc, cps)
12347      char **p;
12348      char *name;
12349      int *loc;
12350      ChessProgramState *cps;
12351 {
12352   char buf[MSG_SIZ];
12353   int len = strlen(name);
12354   int val;
12355   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12356     (*p) += len + 1;
12357     sscanf(*p, "%d", &val);
12358     *loc = (val != 0);
12359     while (**p && **p != ' ') (*p)++;
12360     sprintf(buf, "accepted %s\n", name);
12361     SendToProgram(buf, cps);
12362     return TRUE;
12363   }
12364   return FALSE;
12365 }
12366
12367 int
12368 IntFeature(p, name, loc, cps)
12369      char **p;
12370      char *name;
12371      int *loc;
12372      ChessProgramState *cps;
12373 {
12374   char buf[MSG_SIZ];
12375   int len = strlen(name);
12376   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12377     (*p) += len + 1;
12378     sscanf(*p, "%d", loc);
12379     while (**p && **p != ' ') (*p)++;
12380     sprintf(buf, "accepted %s\n", name);
12381     SendToProgram(buf, cps);
12382     return TRUE;
12383   }
12384   return FALSE;
12385 }
12386
12387 int
12388 StringFeature(p, name, loc, cps)
12389      char **p;
12390      char *name;
12391      char loc[];
12392      ChessProgramState *cps;
12393 {
12394   char buf[MSG_SIZ];
12395   int len = strlen(name);
12396   if (strncmp((*p), name, len) == 0
12397       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12398     (*p) += len + 2;
12399     sscanf(*p, "%[^\"]", loc);
12400     while (**p && **p != '\"') (*p)++;
12401     if (**p == '\"') (*p)++;
12402     sprintf(buf, "accepted %s\n", name);
12403     SendToProgram(buf, cps);
12404     return TRUE;
12405   }
12406   return FALSE;
12407 }
12408
12409 int 
12410 ParseOption(Option *opt, ChessProgramState *cps)
12411 // [HGM] options: process the string that defines an engine option, and determine
12412 // name, type, default value, and allowed value range
12413 {
12414         char *p, *q, buf[MSG_SIZ];
12415         int n, min = (-1)<<31, max = 1<<31, def;
12416
12417         if(p = strstr(opt->name, " -spin ")) {
12418             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12419             if(max < min) max = min; // enforce consistency
12420             if(def < min) def = min;
12421             if(def > max) def = max;
12422             opt->value = def;
12423             opt->min = min;
12424             opt->max = max;
12425             opt->type = Spin;
12426         } else if(p = strstr(opt->name, " -string ")) {
12427             opt->textValue = p+9;
12428             opt->type = TextBox;
12429         } else if(p = strstr(opt->name, " -check ")) {
12430             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12431             opt->value = (def != 0);
12432             opt->type = CheckBox;
12433         } else if(p = strstr(opt->name, " -combo ")) {
12434             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12435             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12436             opt->value = n = 0;
12437             while(q = StrStr(q, " /// ")) {
12438                 n++; *q = 0;    // count choices, and null-terminate each of them
12439                 q += 5;
12440                 if(*q == '*') { // remember default, which is marked with * prefix
12441                     q++;
12442                     opt->value = n;
12443                 }
12444                 cps->comboList[cps->comboCnt++] = q;
12445             }
12446             cps->comboList[cps->comboCnt++] = NULL;
12447             opt->max = n + 1;
12448             opt->type = ComboBox;
12449         } else if(p = strstr(opt->name, " -button")) {
12450             opt->type = Button;
12451         } else if(p = strstr(opt->name, " -save")) {
12452             opt->type = SaveButton;
12453         } else return FALSE;
12454         *p = 0; // terminate option name
12455         // now look if the command-line options define a setting for this engine option.
12456         if(cps->optionSettings && cps->optionSettings[0])
12457             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12458         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12459                 sprintf(buf, "option %s", p);
12460                 if(p = strstr(buf, ",")) *p = 0;
12461                 strcat(buf, "\n");
12462                 SendToProgram(buf, cps);
12463         }
12464         return TRUE;
12465 }
12466
12467 void
12468 FeatureDone(cps, val)
12469      ChessProgramState* cps;
12470      int val;
12471 {
12472   DelayedEventCallback cb = GetDelayedEvent();
12473   if ((cb == InitBackEnd3 && cps == &first) ||
12474       (cb == TwoMachinesEventIfReady && cps == &second)) {
12475     CancelDelayedEvent();
12476     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12477   }
12478   cps->initDone = val;
12479 }
12480
12481 /* Parse feature command from engine */
12482 void
12483 ParseFeatures(args, cps)
12484      char* args;
12485      ChessProgramState *cps;  
12486 {
12487   char *p = args;
12488   char *q;
12489   int val;
12490   char buf[MSG_SIZ];
12491
12492   for (;;) {
12493     while (*p == ' ') p++;
12494     if (*p == NULLCHAR) return;
12495
12496     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12497     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12498     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12499     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12500     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12501     if (BoolFeature(&p, "reuse", &val, cps)) {
12502       /* Engine can disable reuse, but can't enable it if user said no */
12503       if (!val) cps->reuse = FALSE;
12504       continue;
12505     }
12506     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12507     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12508       if (gameMode == TwoMachinesPlay) {
12509         DisplayTwoMachinesTitle();
12510       } else {
12511         DisplayTitle("");
12512       }
12513       continue;
12514     }
12515     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12516     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12517     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12518     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12519     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12520     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12521     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12522     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12523     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12524     if (IntFeature(&p, "done", &val, cps)) {
12525       FeatureDone(cps, val);
12526       continue;
12527     }
12528     /* Added by Tord: */
12529     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12530     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12531     /* End of additions by Tord */
12532
12533     /* [HGM] added features: */
12534     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12535     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12536     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12537     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12538     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12539     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12540     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12541         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12542         if(cps->nrOptions >= MAX_OPTIONS) {
12543             cps->nrOptions--;
12544             sprintf(buf, "%s engine has too many options\n", cps->which);
12545             DisplayError(buf, 0);
12546         }
12547         continue;
12548     }
12549     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12550     /* End of additions by HGM */
12551
12552     /* unknown feature: complain and skip */
12553     q = p;
12554     while (*q && *q != '=') q++;
12555     sprintf(buf, "rejected %.*s\n", q-p, p);
12556     SendToProgram(buf, cps);
12557     p = q;
12558     if (*p == '=') {
12559       p++;
12560       if (*p == '\"') {
12561         p++;
12562         while (*p && *p != '\"') p++;
12563         if (*p == '\"') p++;
12564       } else {
12565         while (*p && *p != ' ') p++;
12566       }
12567     }
12568   }
12569
12570 }
12571
12572 void
12573 PeriodicUpdatesEvent(newState)
12574      int newState;
12575 {
12576     if (newState == appData.periodicUpdates)
12577       return;
12578
12579     appData.periodicUpdates=newState;
12580
12581     /* Display type changes, so update it now */
12582     DisplayAnalysis();
12583
12584     /* Get the ball rolling again... */
12585     if (newState) {
12586         AnalysisPeriodicEvent(1);
12587         StartAnalysisClock();
12588     }
12589 }
12590
12591 void
12592 PonderNextMoveEvent(newState)
12593      int newState;
12594 {
12595     if (newState == appData.ponderNextMove) return;
12596     if (gameMode == EditPosition) EditPositionDone();
12597     if (newState) {
12598         SendToProgram("hard\n", &first);
12599         if (gameMode == TwoMachinesPlay) {
12600             SendToProgram("hard\n", &second);
12601         }
12602     } else {
12603         SendToProgram("easy\n", &first);
12604         thinkOutput[0] = NULLCHAR;
12605         if (gameMode == TwoMachinesPlay) {
12606             SendToProgram("easy\n", &second);
12607         }
12608     }
12609     appData.ponderNextMove = newState;
12610 }
12611
12612 void
12613 NewSettingEvent(option, command, value)
12614      char *command;
12615      int option, value;
12616 {
12617     char buf[MSG_SIZ];
12618
12619     if (gameMode == EditPosition) EditPositionDone();
12620     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12621     SendToProgram(buf, &first);
12622     if (gameMode == TwoMachinesPlay) {
12623         SendToProgram(buf, &second);
12624     }
12625 }
12626
12627 void
12628 ShowThinkingEvent()
12629 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12630 {
12631     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12632     int newState = appData.showThinking
12633         // [HGM] thinking: other features now need thinking output as well
12634         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12635     
12636     if (oldState == newState) return;
12637     oldState = newState;
12638     if (gameMode == EditPosition) EditPositionDone();
12639     if (oldState) {
12640         SendToProgram("post\n", &first);
12641         if (gameMode == TwoMachinesPlay) {
12642             SendToProgram("post\n", &second);
12643         }
12644     } else {
12645         SendToProgram("nopost\n", &first);
12646         thinkOutput[0] = NULLCHAR;
12647         if (gameMode == TwoMachinesPlay) {
12648             SendToProgram("nopost\n", &second);
12649         }
12650     }
12651 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12652 }
12653
12654 void
12655 AskQuestionEvent(title, question, replyPrefix, which)
12656      char *title; char *question; char *replyPrefix; char *which;
12657 {
12658   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12659   if (pr == NoProc) return;
12660   AskQuestion(title, question, replyPrefix, pr);
12661 }
12662
12663 void
12664 DisplayMove(moveNumber)
12665      int moveNumber;
12666 {
12667     char message[MSG_SIZ];
12668     char res[MSG_SIZ];
12669     char cpThinkOutput[MSG_SIZ];
12670
12671     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12672     
12673     if (moveNumber == forwardMostMove - 1 || 
12674         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12675
12676         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12677
12678         if (strchr(cpThinkOutput, '\n')) {
12679             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12680         }
12681     } else {
12682         *cpThinkOutput = NULLCHAR;
12683     }
12684
12685     /* [AS] Hide thinking from human user */
12686     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12687         *cpThinkOutput = NULLCHAR;
12688         if( thinkOutput[0] != NULLCHAR ) {
12689             int i;
12690
12691             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12692                 cpThinkOutput[i] = '.';
12693             }
12694             cpThinkOutput[i] = NULLCHAR;
12695             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12696         }
12697     }
12698
12699     if (moveNumber == forwardMostMove - 1 &&
12700         gameInfo.resultDetails != NULL) {
12701         if (gameInfo.resultDetails[0] == NULLCHAR) {
12702             sprintf(res, " %s", PGNResult(gameInfo.result));
12703         } else {
12704             sprintf(res, " {%s} %s",
12705                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12706         }
12707     } else {
12708         res[0] = NULLCHAR;
12709     }
12710
12711     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12712         DisplayMessage(res, cpThinkOutput);
12713     } else {
12714         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12715                 WhiteOnMove(moveNumber) ? " " : ".. ",
12716                 parseList[moveNumber], res);
12717         DisplayMessage(message, cpThinkOutput);
12718     }
12719 }
12720
12721 void
12722 DisplayAnalysisText(text)
12723      char *text;
12724 {
12725     char buf[MSG_SIZ];
12726
12727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12728                || appData.icsEngineAnalyze) {
12729         sprintf(buf, "Analysis (%s)", first.tidy);
12730         AnalysisPopUp(buf, text);
12731     }
12732 }
12733
12734 static int
12735 only_one_move(str)
12736      char *str;
12737 {
12738     while (*str && isspace(*str)) ++str;
12739     while (*str && !isspace(*str)) ++str;
12740     if (!*str) return 1;
12741     while (*str && isspace(*str)) ++str;
12742     if (!*str) return 1;
12743     return 0;
12744 }
12745
12746 void
12747 DisplayAnalysis()
12748 {
12749     char buf[MSG_SIZ];
12750     char lst[MSG_SIZ / 2];
12751     double nps;
12752     static char *xtra[] = { "", " (--)", " (++)" };
12753     int h, m, s, cs;
12754   
12755     if (programStats.time == 0) {
12756         programStats.time = 1;
12757     }
12758   
12759     if (programStats.got_only_move) {
12760         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12761     } else {
12762         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12763
12764         nps = (u64ToDouble(programStats.nodes) /
12765              ((double)programStats.time /100.0));
12766
12767         cs = programStats.time % 100;
12768         s = programStats.time / 100;
12769         h = (s / (60*60));
12770         s = s - h*60*60;
12771         m = (s/60);
12772         s = s - m*60;
12773
12774         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12775           if (programStats.move_name[0] != NULLCHAR) {
12776             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12777                     programStats.depth,
12778                     programStats.nr_moves-programStats.moves_left,
12779                     programStats.nr_moves, programStats.move_name,
12780                     ((float)programStats.score)/100.0, lst,
12781                     only_one_move(lst)?
12782                     xtra[programStats.got_fail] : "",
12783                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12784           } else {
12785             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12786                     programStats.depth,
12787                     programStats.nr_moves-programStats.moves_left,
12788                     programStats.nr_moves, ((float)programStats.score)/100.0,
12789                     lst,
12790                     only_one_move(lst)?
12791                     xtra[programStats.got_fail] : "",
12792                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12793           }
12794         } else {
12795             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12796                     programStats.depth,
12797                     ((float)programStats.score)/100.0,
12798                     lst,
12799                     only_one_move(lst)?
12800                     xtra[programStats.got_fail] : "",
12801                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12802         }
12803     }
12804     DisplayAnalysisText(buf);
12805 }
12806
12807 void
12808 DisplayComment(moveNumber, text)
12809      int moveNumber;
12810      char *text;
12811 {
12812     char title[MSG_SIZ];
12813     char buf[8000]; // comment can be long!
12814     int score, depth;
12815
12816     if( appData.autoDisplayComment ) {
12817         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12818             strcpy(title, "Comment");
12819         } else {
12820             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12821                     WhiteOnMove(moveNumber) ? " " : ".. ",
12822                     parseList[moveNumber]);
12823         }
12824         // [HGM] PV info: display PV info together with (or as) comment
12825         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12826             if(text == NULL) text = "";                                           
12827             score = pvInfoList[moveNumber].score;
12828             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12829                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12830             text = buf;
12831         }
12832     } else title[0] = 0;
12833
12834     if (text != NULL)
12835         CommentPopUp(title, text);
12836 }
12837
12838 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12839  * might be busy thinking or pondering.  It can be omitted if your
12840  * gnuchess is configured to stop thinking immediately on any user
12841  * input.  However, that gnuchess feature depends on the FIONREAD
12842  * ioctl, which does not work properly on some flavors of Unix.
12843  */
12844 void
12845 Attention(cps)
12846      ChessProgramState *cps;
12847 {
12848 #if ATTENTION
12849     if (!cps->useSigint) return;
12850     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12851     switch (gameMode) {
12852       case MachinePlaysWhite:
12853       case MachinePlaysBlack:
12854       case TwoMachinesPlay:
12855       case IcsPlayingWhite:
12856       case IcsPlayingBlack:
12857       case AnalyzeMode:
12858       case AnalyzeFile:
12859         /* Skip if we know it isn't thinking */
12860         if (!cps->maybeThinking) return;
12861         if (appData.debugMode)
12862           fprintf(debugFP, "Interrupting %s\n", cps->which);
12863         InterruptChildProcess(cps->pr);
12864         cps->maybeThinking = FALSE;
12865         break;
12866       default:
12867         break;
12868     }
12869 #endif /*ATTENTION*/
12870 }
12871
12872 int
12873 CheckFlags()
12874 {
12875     if (whiteTimeRemaining <= 0) {
12876         if (!whiteFlag) {
12877             whiteFlag = TRUE;
12878             if (appData.icsActive) {
12879                 if (appData.autoCallFlag &&
12880                     gameMode == IcsPlayingBlack && !blackFlag) {
12881                   SendToICS(ics_prefix);
12882                   SendToICS("flag\n");
12883                 }
12884             } else {
12885                 if (blackFlag) {
12886                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12887                 } else {
12888                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12889                     if (appData.autoCallFlag) {
12890                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12891                         return TRUE;
12892                     }
12893                 }
12894             }
12895         }
12896     }
12897     if (blackTimeRemaining <= 0) {
12898         if (!blackFlag) {
12899             blackFlag = TRUE;
12900             if (appData.icsActive) {
12901                 if (appData.autoCallFlag &&
12902                     gameMode == IcsPlayingWhite && !whiteFlag) {
12903                   SendToICS(ics_prefix);
12904                   SendToICS("flag\n");
12905                 }
12906             } else {
12907                 if (whiteFlag) {
12908                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12909                 } else {
12910                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12911                     if (appData.autoCallFlag) {
12912                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12913                         return TRUE;
12914                     }
12915                 }
12916             }
12917         }
12918     }
12919     return FALSE;
12920 }
12921
12922 void
12923 CheckTimeControl()
12924 {
12925     if (!appData.clockMode || appData.icsActive ||
12926         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12927
12928     /*
12929      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12930      */
12931     if ( !WhiteOnMove(forwardMostMove) )
12932         /* White made time control */
12933         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12934         /* [HGM] time odds: correct new time quota for time odds! */
12935                                             / WhitePlayer()->timeOdds;
12936       else
12937         /* Black made time control */
12938         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12939                                             / WhitePlayer()->other->timeOdds;
12940 }
12941
12942 void
12943 DisplayBothClocks()
12944 {
12945     int wom = gameMode == EditPosition ?
12946       !blackPlaysFirst : WhiteOnMove(currentMove);
12947     DisplayWhiteClock(whiteTimeRemaining, wom);
12948     DisplayBlackClock(blackTimeRemaining, !wom);
12949 }
12950
12951
12952 /* Timekeeping seems to be a portability nightmare.  I think everyone
12953    has ftime(), but I'm really not sure, so I'm including some ifdefs
12954    to use other calls if you don't.  Clocks will be less accurate if
12955    you have neither ftime nor gettimeofday.
12956 */
12957
12958 /* VS 2008 requires the #include outside of the function */
12959 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12960 #include <sys/timeb.h>
12961 #endif
12962
12963 /* Get the current time as a TimeMark */
12964 void
12965 GetTimeMark(tm)
12966      TimeMark *tm;
12967 {
12968 #if HAVE_GETTIMEOFDAY
12969
12970     struct timeval timeVal;
12971     struct timezone timeZone;
12972
12973     gettimeofday(&timeVal, &timeZone);
12974     tm->sec = (long) timeVal.tv_sec; 
12975     tm->ms = (int) (timeVal.tv_usec / 1000L);
12976
12977 #else /*!HAVE_GETTIMEOFDAY*/
12978 #if HAVE_FTIME
12979
12980 // include <sys/timeb.h> / moved to just above start of function
12981     struct timeb timeB;
12982
12983     ftime(&timeB);
12984     tm->sec = (long) timeB.time;
12985     tm->ms = (int) timeB.millitm;
12986
12987 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12988     tm->sec = (long) time(NULL);
12989     tm->ms = 0;
12990 #endif
12991 #endif
12992 }
12993
12994 /* Return the difference in milliseconds between two
12995    time marks.  We assume the difference will fit in a long!
12996 */
12997 long
12998 SubtractTimeMarks(tm2, tm1)
12999      TimeMark *tm2, *tm1;
13000 {
13001     return 1000L*(tm2->sec - tm1->sec) +
13002            (long) (tm2->ms - tm1->ms);
13003 }
13004
13005
13006 /*
13007  * Code to manage the game clocks.
13008  *
13009  * In tournament play, black starts the clock and then white makes a move.
13010  * We give the human user a slight advantage if he is playing white---the
13011  * clocks don't run until he makes his first move, so it takes zero time.
13012  * Also, we don't account for network lag, so we could get out of sync
13013  * with GNU Chess's clock -- but then, referees are always right.  
13014  */
13015
13016 static TimeMark tickStartTM;
13017 static long intendedTickLength;
13018
13019 long
13020 NextTickLength(timeRemaining)
13021      long timeRemaining;
13022 {
13023     long nominalTickLength, nextTickLength;
13024
13025     if (timeRemaining > 0L && timeRemaining <= 10000L)
13026       nominalTickLength = 100L;
13027     else
13028       nominalTickLength = 1000L;
13029     nextTickLength = timeRemaining % nominalTickLength;
13030     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13031
13032     return nextTickLength;
13033 }
13034
13035 /* Adjust clock one minute up or down */
13036 void
13037 AdjustClock(Boolean which, int dir)
13038 {
13039     if(which) blackTimeRemaining += 60000*dir;
13040     else      whiteTimeRemaining += 60000*dir;
13041     DisplayBothClocks();
13042 }
13043
13044 /* Stop clocks and reset to a fresh time control */
13045 void
13046 ResetClocks() 
13047 {
13048     (void) StopClockTimer();
13049     if (appData.icsActive) {
13050         whiteTimeRemaining = blackTimeRemaining = 0;
13051     } else { /* [HGM] correct new time quote for time odds */
13052         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13053         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13054     }
13055     if (whiteFlag || blackFlag) {
13056         DisplayTitle("");
13057         whiteFlag = blackFlag = FALSE;
13058     }
13059     DisplayBothClocks();
13060 }
13061
13062 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13063
13064 /* Decrement running clock by amount of time that has passed */
13065 void
13066 DecrementClocks()
13067 {
13068     long timeRemaining;
13069     long lastTickLength, fudge;
13070     TimeMark now;
13071
13072     if (!appData.clockMode) return;
13073     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13074         
13075     GetTimeMark(&now);
13076
13077     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13078
13079     /* Fudge if we woke up a little too soon */
13080     fudge = intendedTickLength - lastTickLength;
13081     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13082
13083     if (WhiteOnMove(forwardMostMove)) {
13084         if(whiteNPS >= 0) lastTickLength = 0;
13085         timeRemaining = whiteTimeRemaining -= lastTickLength;
13086         DisplayWhiteClock(whiteTimeRemaining - fudge,
13087                           WhiteOnMove(currentMove));
13088     } else {
13089         if(blackNPS >= 0) lastTickLength = 0;
13090         timeRemaining = blackTimeRemaining -= lastTickLength;
13091         DisplayBlackClock(blackTimeRemaining - fudge,
13092                           !WhiteOnMove(currentMove));
13093     }
13094
13095     if (CheckFlags()) return;
13096         
13097     tickStartTM = now;
13098     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13099     StartClockTimer(intendedTickLength);
13100
13101     /* if the time remaining has fallen below the alarm threshold, sound the
13102      * alarm. if the alarm has sounded and (due to a takeback or time control
13103      * with increment) the time remaining has increased to a level above the
13104      * threshold, reset the alarm so it can sound again. 
13105      */
13106     
13107     if (appData.icsActive && appData.icsAlarm) {
13108
13109         /* make sure we are dealing with the user's clock */
13110         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13111                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13112            )) return;
13113
13114         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13115             alarmSounded = FALSE;
13116         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13117             PlayAlarmSound();
13118             alarmSounded = TRUE;
13119         }
13120     }
13121 }
13122
13123
13124 /* A player has just moved, so stop the previously running
13125    clock and (if in clock mode) start the other one.
13126    We redisplay both clocks in case we're in ICS mode, because
13127    ICS gives us an update to both clocks after every move.
13128    Note that this routine is called *after* forwardMostMove
13129    is updated, so the last fractional tick must be subtracted
13130    from the color that is *not* on move now.
13131 */
13132 void
13133 SwitchClocks()
13134 {
13135     long lastTickLength;
13136     TimeMark now;
13137     int flagged = FALSE;
13138
13139     GetTimeMark(&now);
13140
13141     if (StopClockTimer() && appData.clockMode) {
13142         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13143         if (WhiteOnMove(forwardMostMove)) {
13144             if(blackNPS >= 0) lastTickLength = 0;
13145             blackTimeRemaining -= lastTickLength;
13146            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13147 //         if(pvInfoList[forwardMostMove-1].time == -1)
13148                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13149                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13150         } else {
13151            if(whiteNPS >= 0) lastTickLength = 0;
13152            whiteTimeRemaining -= lastTickLength;
13153            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13154 //         if(pvInfoList[forwardMostMove-1].time == -1)
13155                  pvInfoList[forwardMostMove-1].time = 
13156                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13157         }
13158         flagged = CheckFlags();
13159     }
13160     CheckTimeControl();
13161
13162     if (flagged || !appData.clockMode) return;
13163
13164     switch (gameMode) {
13165       case MachinePlaysBlack:
13166       case MachinePlaysWhite:
13167       case BeginningOfGame:
13168         if (pausing) return;
13169         break;
13170
13171       case EditGame:
13172       case PlayFromGameFile:
13173       case IcsExamining:
13174         return;
13175
13176       default:
13177         break;
13178     }
13179
13180     tickStartTM = now;
13181     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13182       whiteTimeRemaining : blackTimeRemaining);
13183     StartClockTimer(intendedTickLength);
13184 }
13185         
13186
13187 /* Stop both clocks */
13188 void
13189 StopClocks()
13190 {       
13191     long lastTickLength;
13192     TimeMark now;
13193
13194     if (!StopClockTimer()) return;
13195     if (!appData.clockMode) return;
13196
13197     printf("Debug: in stop clocks\n");
13198
13199     GetTimeMark(&now);
13200
13201     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13202     if (WhiteOnMove(forwardMostMove)) {
13203         if(whiteNPS >= 0) lastTickLength = 0;
13204         whiteTimeRemaining -= lastTickLength;
13205         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13206     } else {
13207         if(blackNPS >= 0) lastTickLength = 0;
13208         blackTimeRemaining -= lastTickLength;
13209         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13210     }
13211     CheckFlags();
13212     printf("Debug: end stop clocks\n");
13213 }
13214         
13215 /* Start clock of player on move.  Time may have been reset, so
13216    if clock is already running, stop and restart it. */
13217 void
13218 StartClocks()
13219 {
13220     (void) StopClockTimer(); /* in case it was running already */
13221     DisplayBothClocks();
13222     if (CheckFlags()) return;
13223
13224     if (!appData.clockMode) return;
13225     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13226
13227     GetTimeMark(&tickStartTM);
13228     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13229       whiteTimeRemaining : blackTimeRemaining);
13230
13231    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13232     whiteNPS = blackNPS = -1; 
13233     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13234        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13235         whiteNPS = first.nps;
13236     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13237        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13238         blackNPS = first.nps;
13239     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13240         whiteNPS = second.nps;
13241     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13242         blackNPS = second.nps;
13243     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13244
13245     StartClockTimer(intendedTickLength);
13246 }
13247
13248 char *
13249 TimeString(ms)
13250      long ms;
13251 {
13252     long second, minute, hour, day;
13253     char *sign = "";
13254     static char buf[32];
13255     
13256     if (ms > 0 && ms <= 9900) {
13257       /* convert milliseconds to tenths, rounding up */
13258       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13259
13260       sprintf(buf, " %03.1f ", tenths/10.0);
13261       return buf;
13262     }
13263
13264     /* convert milliseconds to seconds, rounding up */
13265     /* use floating point to avoid strangeness of integer division
13266        with negative dividends on many machines */
13267     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13268
13269     if (second < 0) {
13270         sign = "-";
13271         second = -second;
13272     }
13273     
13274     day = second / (60 * 60 * 24);
13275     second = second % (60 * 60 * 24);
13276     hour = second / (60 * 60);
13277     second = second % (60 * 60);
13278     minute = second / 60;
13279     second = second % 60;
13280     
13281     if (day > 0)
13282       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13283               sign, day, hour, minute, second);
13284     else if (hour > 0)
13285       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13286     else
13287       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13288     
13289     return buf;
13290 }
13291
13292
13293 /*
13294  * This is necessary because some C libraries aren't ANSI C compliant yet.
13295  */
13296 char *
13297 StrStr(string, match)
13298      char *string, *match;
13299 {
13300     int i, length;
13301     
13302     length = strlen(match);
13303     
13304     for (i = strlen(string) - length; i >= 0; i--, string++)
13305       if (!strncmp(match, string, length))
13306         return string;
13307     
13308     return NULL;
13309 }
13310
13311 char *
13312 StrCaseStr(string, match)
13313      char *string, *match;
13314 {
13315     int i, j, length;
13316     
13317     length = strlen(match);
13318     
13319     for (i = strlen(string) - length; i >= 0; i--, string++) {
13320         for (j = 0; j < length; j++) {
13321             if (ToLower(match[j]) != ToLower(string[j]))
13322               break;
13323         }
13324         if (j == length) return string;
13325     }
13326
13327     return NULL;
13328 }
13329
13330 #ifndef _amigados
13331 int
13332 StrCaseCmp(s1, s2)
13333      char *s1, *s2;
13334 {
13335     char c1, c2;
13336     
13337     for (;;) {
13338         c1 = ToLower(*s1++);
13339         c2 = ToLower(*s2++);
13340         if (c1 > c2) return 1;
13341         if (c1 < c2) return -1;
13342         if (c1 == NULLCHAR) return 0;
13343     }
13344 }
13345
13346
13347 int
13348 ToLower(c)
13349      int c;
13350 {
13351     return isupper(c) ? tolower(c) : c;
13352 }
13353
13354
13355 int
13356 ToUpper(c)
13357      int c;
13358 {
13359     return islower(c) ? toupper(c) : c;
13360 }
13361 #endif /* !_amigados    */
13362
13363 char *
13364 StrSave(s)
13365      char *s;
13366 {
13367     char *ret;
13368
13369     if ((ret = (char *) malloc(strlen(s) + 1))) {
13370         strcpy(ret, s);
13371     }
13372     return ret;
13373 }
13374
13375 char *
13376 StrSavePtr(s, savePtr)
13377      char *s, **savePtr;
13378 {
13379     if (*savePtr) {
13380         free(*savePtr);
13381     }
13382     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13383         strcpy(*savePtr, s);
13384     }
13385     return(*savePtr);
13386 }
13387
13388 char *
13389 PGNDate()
13390 {
13391     time_t clock;
13392     struct tm *tm;
13393     char buf[MSG_SIZ];
13394
13395     clock = time((time_t *)NULL);
13396     tm = localtime(&clock);
13397     sprintf(buf, "%04d.%02d.%02d",
13398             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13399     return StrSave(buf);
13400 }
13401
13402
13403 char *
13404 PositionToFEN(move, overrideCastling)
13405      int move;
13406      char *overrideCastling;
13407 {
13408     int i, j, fromX, fromY, toX, toY;
13409     int whiteToPlay;
13410     char buf[128];
13411     char *p, *q;
13412     int emptycount;
13413     ChessSquare piece;
13414
13415     whiteToPlay = (gameMode == EditPosition) ?
13416       !blackPlaysFirst : (move % 2 == 0);
13417     p = buf;
13418
13419     /* Piece placement data */
13420     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13421         emptycount = 0;
13422         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13423             if (boards[move][i][j] == EmptySquare) {
13424                 emptycount++;
13425             } else { ChessSquare piece = boards[move][i][j];
13426                 if (emptycount > 0) {
13427                     if(emptycount<10) /* [HGM] can be >= 10 */
13428                         *p++ = '0' + emptycount;
13429                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13430                     emptycount = 0;
13431                 }
13432                 if(PieceToChar(piece) == '+') {
13433                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13434                     *p++ = '+';
13435                     piece = (ChessSquare)(DEMOTED piece);
13436                 } 
13437                 *p++ = PieceToChar(piece);
13438                 if(p[-1] == '~') {
13439                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13440                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13441                     *p++ = '~';
13442                 }
13443             }
13444         }
13445         if (emptycount > 0) {
13446             if(emptycount<10) /* [HGM] can be >= 10 */
13447                 *p++ = '0' + emptycount;
13448             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13449             emptycount = 0;
13450         }
13451         *p++ = '/';
13452     }
13453     *(p - 1) = ' ';
13454
13455     /* [HGM] print Crazyhouse or Shogi holdings */
13456     if( gameInfo.holdingsWidth ) {
13457         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13458         q = p;
13459         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13460             piece = boards[move][i][BOARD_WIDTH-1];
13461             if( piece != EmptySquare )
13462               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13463                   *p++ = PieceToChar(piece);
13464         }
13465         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13466             piece = boards[move][BOARD_HEIGHT-i-1][0];
13467             if( piece != EmptySquare )
13468               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13469                   *p++ = PieceToChar(piece);
13470         }
13471
13472         if( q == p ) *p++ = '-';
13473         *p++ = ']';
13474         *p++ = ' ';
13475     }
13476
13477     /* Active color */
13478     *p++ = whiteToPlay ? 'w' : 'b';
13479     *p++ = ' ';
13480
13481   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13482     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13483   } else {
13484   if(nrCastlingRights) {
13485      q = p;
13486      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13487        /* [HGM] write directly from rights */
13488            if(castlingRights[move][2] >= 0 &&
13489               castlingRights[move][0] >= 0   )
13490                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13491            if(castlingRights[move][2] >= 0 &&
13492               castlingRights[move][1] >= 0   )
13493                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13494            if(castlingRights[move][5] >= 0 &&
13495               castlingRights[move][3] >= 0   )
13496                 *p++ = castlingRights[move][3] + AAA;
13497            if(castlingRights[move][5] >= 0 &&
13498               castlingRights[move][4] >= 0   )
13499                 *p++ = castlingRights[move][4] + AAA;
13500      } else {
13501
13502         /* [HGM] write true castling rights */
13503         if( nrCastlingRights == 6 ) {
13504             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13505                castlingRights[move][2] >= 0  ) *p++ = 'K';
13506             if(castlingRights[move][1] == BOARD_LEFT &&
13507                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13508             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13509                castlingRights[move][5] >= 0  ) *p++ = 'k';
13510             if(castlingRights[move][4] == BOARD_LEFT &&
13511                castlingRights[move][5] >= 0  ) *p++ = 'q';
13512         }
13513      }
13514      if (q == p) *p++ = '-'; /* No castling rights */
13515      *p++ = ' ';
13516   }
13517
13518   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13519      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13520     /* En passant target square */
13521     if (move > backwardMostMove) {
13522         fromX = moveList[move - 1][0] - AAA;
13523         fromY = moveList[move - 1][1] - ONE;
13524         toX = moveList[move - 1][2] - AAA;
13525         toY = moveList[move - 1][3] - ONE;
13526         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13527             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13528             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13529             fromX == toX) {
13530             /* 2-square pawn move just happened */
13531             *p++ = toX + AAA;
13532             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13533         } else {
13534             *p++ = '-';
13535         }
13536     } else {
13537         *p++ = '-';
13538     }
13539     *p++ = ' ';
13540   }
13541   }
13542
13543     /* [HGM] find reversible plies */
13544     {   int i = 0, j=move;
13545
13546         if (appData.debugMode) { int k;
13547             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13548             for(k=backwardMostMove; k<=forwardMostMove; k++)
13549                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13550
13551         }
13552
13553         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13554         if( j == backwardMostMove ) i += initialRulePlies;
13555         sprintf(p, "%d ", i);
13556         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13557     }
13558     /* Fullmove number */
13559     sprintf(p, "%d", (move / 2) + 1);
13560     
13561     return StrSave(buf);
13562 }
13563
13564 Boolean
13565 ParseFEN(board, blackPlaysFirst, fen)
13566     Board board;
13567      int *blackPlaysFirst;
13568      char *fen;
13569 {
13570     int i, j;
13571     char *p;
13572     int emptycount;
13573     ChessSquare piece;
13574
13575     p = fen;
13576
13577     /* [HGM] by default clear Crazyhouse holdings, if present */
13578     if(gameInfo.holdingsWidth) {
13579        for(i=0; i<BOARD_HEIGHT; i++) {
13580            board[i][0]             = EmptySquare; /* black holdings */
13581            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13582            board[i][1]             = (ChessSquare) 0; /* black counts */
13583            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13584        }
13585     }
13586
13587     /* Piece placement data */
13588     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13589         j = 0;
13590         for (;;) {
13591             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13592                 if (*p == '/') p++;
13593                 emptycount = gameInfo.boardWidth - j;
13594                 while (emptycount--)
13595                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13596                 break;
13597 #if(BOARD_SIZE >= 10)
13598             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13599                 p++; emptycount=10;
13600                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13601                 while (emptycount--)
13602                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13603 #endif
13604             } else if (isdigit(*p)) {
13605                 emptycount = *p++ - '0';
13606                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13607                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13608                 while (emptycount--)
13609                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13610             } else if (*p == '+' || isalpha(*p)) {
13611                 if (j >= gameInfo.boardWidth) return FALSE;
13612                 if(*p=='+') {
13613                     piece = CharToPiece(*++p);
13614                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13615                     piece = (ChessSquare) (PROMOTED piece ); p++;
13616                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13617                 } else piece = CharToPiece(*p++);
13618
13619                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13620                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13621                     piece = (ChessSquare) (PROMOTED piece);
13622                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13623                     p++;
13624                 }
13625                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13626             } else {
13627                 return FALSE;
13628             }
13629         }
13630     }
13631     while (*p == '/' || *p == ' ') p++;
13632
13633     /* [HGM] look for Crazyhouse holdings here */
13634     while(*p==' ') p++;
13635     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13636         if(*p == '[') p++;
13637         if(*p == '-' ) *p++; /* empty holdings */ else {
13638             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13639             /* if we would allow FEN reading to set board size, we would   */
13640             /* have to add holdings and shift the board read so far here   */
13641             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13642                 *p++;
13643                 if((int) piece >= (int) BlackPawn ) {
13644                     i = (int)piece - (int)BlackPawn;
13645                     i = PieceToNumber((ChessSquare)i);
13646                     if( i >= gameInfo.holdingsSize ) return FALSE;
13647                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13648                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13649                 } else {
13650                     i = (int)piece - (int)WhitePawn;
13651                     i = PieceToNumber((ChessSquare)i);
13652                     if( i >= gameInfo.holdingsSize ) return FALSE;
13653                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13654                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13655                 }
13656             }
13657         }
13658         if(*p == ']') *p++;
13659     }
13660
13661     while(*p == ' ') p++;
13662
13663     /* Active color */
13664     switch (*p++) {
13665       case 'w':
13666         *blackPlaysFirst = FALSE;
13667         break;
13668       case 'b': 
13669         *blackPlaysFirst = TRUE;
13670         break;
13671       default:
13672         return FALSE;
13673     }
13674
13675     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13676     /* return the extra info in global variiables             */
13677
13678     /* set defaults in case FEN is incomplete */
13679     FENepStatus = EP_UNKNOWN;
13680     for(i=0; i<nrCastlingRights; i++ ) {
13681         FENcastlingRights[i] =
13682             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13683     }   /* assume possible unless obviously impossible */
13684     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13685     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13686     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13687     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13688     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13689     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13690     FENrulePlies = 0;
13691
13692     while(*p==' ') p++;
13693     if(nrCastlingRights) {
13694       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13695           /* castling indicator present, so default becomes no castlings */
13696           for(i=0; i<nrCastlingRights; i++ ) {
13697                  FENcastlingRights[i] = -1;
13698           }
13699       }
13700       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13701              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13702              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13703              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13704         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13705
13706         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13707             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13708             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13709         }
13710         switch(c) {
13711           case'K':
13712               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13713               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13714               FENcastlingRights[2] = whiteKingFile;
13715               break;
13716           case'Q':
13717               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13718               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13719               FENcastlingRights[2] = whiteKingFile;
13720               break;
13721           case'k':
13722               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13723               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13724               FENcastlingRights[5] = blackKingFile;
13725               break;
13726           case'q':
13727               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13728               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13729               FENcastlingRights[5] = blackKingFile;
13730           case '-':
13731               break;
13732           default: /* FRC castlings */
13733               if(c >= 'a') { /* black rights */
13734                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13735                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13736                   if(i == BOARD_RGHT) break;
13737                   FENcastlingRights[5] = i;
13738                   c -= AAA;
13739                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13740                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13741                   if(c > i)
13742                       FENcastlingRights[3] = c;
13743                   else
13744                       FENcastlingRights[4] = c;
13745               } else { /* white rights */
13746                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13747                     if(board[0][i] == WhiteKing) break;
13748                   if(i == BOARD_RGHT) break;
13749                   FENcastlingRights[2] = i;
13750                   c -= AAA - 'a' + 'A';
13751                   if(board[0][c] >= WhiteKing) break;
13752                   if(c > i)
13753                       FENcastlingRights[0] = c;
13754                   else
13755                       FENcastlingRights[1] = c;
13756               }
13757         }
13758       }
13759     if (appData.debugMode) {
13760         fprintf(debugFP, "FEN castling rights:");
13761         for(i=0; i<nrCastlingRights; i++)
13762         fprintf(debugFP, " %d", FENcastlingRights[i]);
13763         fprintf(debugFP, "\n");
13764     }
13765
13766       while(*p==' ') p++;
13767     }
13768
13769     /* read e.p. field in games that know e.p. capture */
13770     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13771        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13772       if(*p=='-') {
13773         p++; FENepStatus = EP_NONE;
13774       } else {
13775          char c = *p++ - AAA;
13776
13777          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13778          if(*p >= '0' && *p <='9') *p++;
13779          FENepStatus = c;
13780       }
13781     }
13782
13783
13784     if(sscanf(p, "%d", &i) == 1) {
13785         FENrulePlies = i; /* 50-move ply counter */
13786         /* (The move number is still ignored)    */
13787     }
13788
13789     return TRUE;
13790 }
13791       
13792 void
13793 EditPositionPasteFEN(char *fen)
13794 {
13795   if (fen != NULL) {
13796     Board initial_position;
13797
13798     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13799       DisplayError(_("Bad FEN position in clipboard"), 0);
13800       return ;
13801     } else {
13802       int savedBlackPlaysFirst = blackPlaysFirst;
13803       EditPositionEvent();
13804       blackPlaysFirst = savedBlackPlaysFirst;
13805       CopyBoard(boards[0], initial_position);
13806           /* [HGM] copy FEN attributes as well */
13807           {   int i;
13808               initialRulePlies = FENrulePlies;
13809               epStatus[0] = FENepStatus;
13810               for( i=0; i<nrCastlingRights; i++ )
13811                   castlingRights[0][i] = FENcastlingRights[i];
13812           }
13813       EditPositionDone();
13814       DisplayBothClocks();
13815       DrawPosition(FALSE, boards[currentMove]);
13816     }
13817   }
13818 }