Merge branch 'master' into gtk
[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 int lastSavedGame; /* [HGM] save: ID of game */
246
247 /* States for ics_getting_history */
248 #define H_FALSE 0
249 #define H_REQUESTED 1
250 #define H_GOT_REQ_HEADER 2
251 #define H_GOT_UNREQ_HEADER 3
252 #define H_GETTING_MOVES 4
253 #define H_GOT_UNWANTED_HEADER 5
254
255 /* whosays values for GameEnds */
256 #define GE_ICS 0
257 #define GE_ENGINE 1
258 #define GE_PLAYER 2
259 #define GE_FILE 3
260 #define GE_XBOARD 4
261 #define GE_ENGINE1 5
262 #define GE_ENGINE2 6
263
264 /* Maximum number of games in a cmail message */
265 #define CMAIL_MAX_GAMES 20
266
267 /* Different types of move when calling RegisterMove */
268 #define CMAIL_MOVE   0
269 #define CMAIL_RESIGN 1
270 #define CMAIL_DRAW   2
271 #define CMAIL_ACCEPT 3
272
273 /* Different types of result to remember for each game */
274 #define CMAIL_NOT_RESULT 0
275 #define CMAIL_OLD_RESULT 1
276 #define CMAIL_NEW_RESULT 2
277
278 /* Telnet protocol constants */
279 #define TN_WILL 0373
280 #define TN_WONT 0374
281 #define TN_DO   0375
282 #define TN_DONT 0376
283 #define TN_IAC  0377
284 #define TN_ECHO 0001
285 #define TN_SGA  0003
286 #define TN_PORT 23
287
288 /* [AS] */
289 static char * safeStrCpy( char * dst, const char * src, size_t count )
290 {
291     assert( dst != NULL );
292     assert( src != NULL );
293     assert( count > 0 );
294
295     strncpy( dst, src, count );
296     dst[ count-1 ] = '\0';
297     return dst;
298 }
299
300 #if 0
301 //[HGM] for future use? Conditioned out for now to suppress warning.
302 static char * safeStrCat( char * dst, const char * src, size_t count )
303 {
304     size_t  dst_len;
305
306     assert( dst != NULL );
307     assert( src != NULL );
308     assert( count > 0 );
309
310     dst_len = strlen(dst);
311
312     assert( count > dst_len ); /* Buffer size must be greater than current length */
313
314     safeStrCpy( dst + dst_len, src, count - dst_len );
315
316     return dst;
317 }
318 #endif
319
320 /* Some compiler can't cast u64 to double
321  * This function do the job for us:
322
323  * We use the highest bit for cast, this only
324  * works if the highest bit is not
325  * in use (This should not happen)
326  *
327  * We used this for all compiler
328  */
329 double
330 u64ToDouble(u64 value)
331 {
332   double r;
333   u64 tmp = value & u64Const(0x7fffffffffffffff);
334   r = (double)(s64)tmp;
335   if (value & u64Const(0x8000000000000000))
336        r +=  9.2233720368547758080e18; /* 2^63 */
337  return r;
338 }
339
340 /* Fake up flags for now, as we aren't keeping track of castling
341    availability yet. [HGM] Change of logic: the flag now only
342    indicates the type of castlings allowed by the rule of the game.
343    The actual rights themselves are maintained in the array
344    castlingRights, as part of the game history, and are not probed
345    by this function.
346  */
347 int
348 PosFlags(index)
349 {
350   int flags = F_ALL_CASTLE_OK;
351   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
352   switch (gameInfo.variant) {
353   case VariantSuicide:
354     flags &= ~F_ALL_CASTLE_OK;
355   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
356     flags |= F_IGNORE_CHECK;
357   case VariantLosers:
358     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
359     break;
360   case VariantAtomic:
361     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
362     break;
363   case VariantKriegspiel:
364     flags |= F_KRIEGSPIEL_CAPTURE;
365     break;
366   case VariantCapaRandom:
367   case VariantFischeRandom:
368     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
369   case VariantNoCastle:
370   case VariantShatranj:
371   case VariantCourier:
372     flags &= ~F_ALL_CASTLE_OK;
373     break;
374   default:
375     break;
376   }
377   return flags;
378 }
379
380 FILE *gameFileFP, *debugFP;
381
382 /*
383     [AS] Note: sometimes, the sscanf() function is used to parse the input
384     into a fixed-size buffer. Because of this, we must be prepared to
385     receive strings as long as the size of the input buffer, which is currently
386     set to 4K for Windows and 8K for the rest.
387     So, we must either allocate sufficiently large buffers here, or
388     reduce the size of the input buffer in the input reading part.
389 */
390
391 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
392 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
393 char thinkOutput1[MSG_SIZ*10];
394
395 ChessProgramState first, second;
396
397 /* premove variables */
398 int premoveToX = 0;
399 int premoveToY = 0;
400 int premoveFromX = 0;
401 int premoveFromY = 0;
402 int premovePromoChar = 0;
403 int gotPremove = 0;
404 Boolean alarmSounded;
405 /* end premove variables */
406
407 char *ics_prefix = "$";
408 int ics_type = ICS_GENERIC;
409
410 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
411 int pauseExamForwardMostMove = 0;
412 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
413 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
414 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
415 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
416 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
417 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
418 int whiteFlag = FALSE, blackFlag = FALSE;
419 int userOfferedDraw = FALSE;
420 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
421 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
422 int cmailMoveType[CMAIL_MAX_GAMES];
423 long ics_clock_paused = 0;
424 ProcRef icsPR = NoProc, cmailPR = NoProc;
425 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
426 GameMode gameMode = BeginningOfGame;
427 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
428 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
429 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
430 int hiddenThinkOutputState = 0; /* [AS] */
431 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
432 int adjudicateLossPlies = 6;
433 char white_holding[64], black_holding[64];
434 TimeMark lastNodeCountTime;
435 long lastNodeCount=0;
436 int have_sent_ICS_logon = 0;
437 int movesPerSession;
438 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
439 long timeControl_2; /* [AS] Allow separate time controls */
440 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
441 long timeRemaining[2][MAX_MOVES];
442 int matchGame = 0;
443 TimeMark programStartTime;
444 char ics_handle[MSG_SIZ];
445 int have_set_title = 0;
446
447 /* animateTraining preserves the state of appData.animate
448  * when Training mode is activated. This allows the
449  * response to be animated when appData.animate == TRUE and
450  * appData.animateDragging == TRUE.
451  */
452 Boolean animateTraining;
453
454 GameInfo gameInfo;
455
456 AppData appData;
457
458 Board boards[MAX_MOVES];
459 /* [HGM] Following 7 needed for accurate legality tests: */
460 char  epStatus[MAX_MOVES];
461 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
462 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
463 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
464 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
465 int   initialRulePlies, FENrulePlies;
466 char  FENepStatus;
467 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
468 int loadFlag = 0;
469 int shuffleOpenings;
470
471 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475         BlackKing, BlackBishop, BlackKnight, BlackRook }
476 };
477
478 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
479     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482         BlackKing, BlackKing, BlackKnight, BlackRook }
483 };
484
485 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
486     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488     { BlackRook, BlackMan, BlackBishop, BlackQueen,
489         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
490 };
491
492 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
493     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
494         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
496         BlackKing, BlackBishop, BlackKnight, BlackRook }
497 };
498
499 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 };
505
506
507 #if (BOARD_SIZE>=10)
508 ChessSquare ShogiArray[2][BOARD_SIZE] = {
509     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
510         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
511     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
512         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
513 };
514
515 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
516     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
517         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
519         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
520 };
521
522 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
526         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
527 };
528
529 ChessSquare GreatArray[2][BOARD_SIZE] = {
530     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
531         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
532     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
533         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
534 };
535
536 ChessSquare JanusArray[2][BOARD_SIZE] = {
537     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
538         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
539     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
540         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
541 };
542
543 #ifdef GOTHIC
544 ChessSquare GothicArray[2][BOARD_SIZE] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
546         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
548         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
549 };
550 #else // !GOTHIC
551 #define GothicArray CapablancaArray
552 #endif // !GOTHIC
553
554 #ifdef FALCON
555 ChessSquare FalconArray[2][BOARD_SIZE] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
557         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
559         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
560 };
561 #else // !FALCON
562 #define FalconArray CapablancaArray
563 #endif // !FALCON
564
565 #else // !(BOARD_SIZE>=10)
566 #define XiangqiPosition FIDEArray
567 #define CapablancaArray FIDEArray
568 #define GothicArray FIDEArray
569 #define GreatArray FIDEArray
570 #endif // !(BOARD_SIZE>=10)
571
572 #if (BOARD_SIZE>=12)
573 ChessSquare CourierArray[2][BOARD_SIZE] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
575         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
577         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 };
579 #else // !(BOARD_SIZE>=12)
580 #define CourierArray CapablancaArray
581 #endif // !(BOARD_SIZE>=12)
582
583
584 Board initialPosition;
585
586
587 /* Convert str to a rating. Checks for special cases of "----",
588
589    "++++", etc. Also strips ()'s */
590 int
591 string_to_rating(str)
592   char *str;
593 {
594   while(*str && !isdigit(*str)) ++str;
595   if (!*str)
596     return 0;   /* One of the special "no rating" cases */
597   else
598     return atoi(str);
599 }
600
601 void
602 ClearProgramStats()
603 {
604     /* Init programStats */
605     programStats.movelist[0] = 0;
606     programStats.depth = 0;
607     programStats.nr_moves = 0;
608     programStats.moves_left = 0;
609     programStats.nodes = 0;
610     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
611     programStats.score = 0;
612     programStats.got_only_move = 0;
613     programStats.got_fail = 0;
614     programStats.line_is_book = 0;
615 }
616
617 void
618 InitBackEnd1()
619 {
620     int matched, min, sec;
621
622     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623
624     GetTimeMark(&programStartTime);
625     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
626
627     ClearProgramStats();
628     programStats.ok_to_send = 1;
629     programStats.seen_stat = 0;
630
631     /*
632      * Initialize game list
633      */
634     ListNew(&gameList);
635
636
637     /*
638      * Internet chess server status
639      */
640     if (appData.icsActive) {
641         appData.matchMode = FALSE;
642         appData.matchGames = 0;
643 #if ZIPPY
644         appData.noChessProgram = !appData.zippyPlay;
645 #else
646         appData.zippyPlay = FALSE;
647         appData.zippyTalk = FALSE;
648         appData.noChessProgram = TRUE;
649 #endif
650         if (*appData.icsHelper != NULLCHAR) {
651             appData.useTelnet = TRUE;
652             appData.telnetProgram = appData.icsHelper;
653         }
654     } else {
655         appData.zippyTalk = appData.zippyPlay = FALSE;
656     }
657
658     /* [AS] Initialize pv info list [HGM] and game state */
659     {
660         int i, j;
661
662         for( i=0; i<MAX_MOVES; i++ ) {
663             pvInfoList[i].depth = -1;
664             epStatus[i]=EP_NONE;
665             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
666         }
667     }
668
669     /*
670      * Parse timeControl resource
671      */
672     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
673                           appData.movesPerSession)) {
674         char buf[MSG_SIZ];
675         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
676         DisplayFatalError(buf, 0, 2);
677     }
678
679     /*
680      * Parse searchTime resource
681      */
682     if (*appData.searchTime != NULLCHAR) {
683         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684         if (matched == 1) {
685             searchTime = min * 60;
686         } else if (matched == 2) {
687             searchTime = min * 60 + sec;
688         } else {
689             char buf[MSG_SIZ];
690             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
691             DisplayFatalError(buf, 0, 2);
692         }
693     }
694
695     /* [AS] Adjudication threshold */
696     adjudicateLossThreshold = appData.adjudicateLossThreshold;
697
698     first.which = "first";
699     second.which = "second";
700     first.maybeThinking = second.maybeThinking = FALSE;
701     first.pr = second.pr = NoProc;
702     first.isr = second.isr = NULL;
703     first.sendTime = second.sendTime = 2;
704     first.sendDrawOffers = 1;
705     if (appData.firstPlaysBlack) {
706         first.twoMachinesColor = "black\n";
707         second.twoMachinesColor = "white\n";
708     } else {
709         first.twoMachinesColor = "white\n";
710         second.twoMachinesColor = "black\n";
711     }
712     first.program = appData.firstChessProgram;
713     second.program = appData.secondChessProgram;
714     first.host = appData.firstHost;
715     second.host = appData.secondHost;
716     first.dir = appData.firstDirectory;
717     second.dir = appData.secondDirectory;
718     first.other = &second;
719     second.other = &first;
720     first.initString = appData.initString;
721     second.initString = appData.secondInitString;
722     first.computerString = appData.firstComputerString;
723     second.computerString = appData.secondComputerString;
724     first.useSigint = second.useSigint = TRUE;
725     first.useSigterm = second.useSigterm = TRUE;
726     first.reuse = appData.reuseFirst;
727     second.reuse = appData.reuseSecond;
728     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
729     second.nps = appData.secondNPS;
730     first.useSetboard = second.useSetboard = FALSE;
731     first.useSAN = second.useSAN = FALSE;
732     first.usePing = second.usePing = FALSE;
733     first.lastPing = second.lastPing = 0;
734     first.lastPong = second.lastPong = 0;
735     first.usePlayother = second.usePlayother = FALSE;
736     first.useColors = second.useColors = TRUE;
737     first.useUsermove = second.useUsermove = FALSE;
738     first.sendICS = second.sendICS = FALSE;
739     first.sendName = second.sendName = appData.icsActive;
740     first.sdKludge = second.sdKludge = FALSE;
741     first.stKludge = second.stKludge = FALSE;
742     TidyProgramName(first.program, first.host, first.tidy);
743     TidyProgramName(second.program, second.host, second.tidy);
744     first.matchWins = second.matchWins = 0;
745     strcpy(first.variants, appData.variant);
746     strcpy(second.variants, appData.variant);
747     first.analysisSupport = second.analysisSupport = 2; /* detect */
748     first.analyzing = second.analyzing = FALSE;
749     first.initDone = second.initDone = FALSE;
750
751     /* New features added by Tord: */
752     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
753     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
754     /* End of new features added by Tord. */
755     first.fenOverride  = appData.fenOverride1;
756     second.fenOverride = appData.fenOverride2;
757
758     /* [HGM] time odds: set factor for each machine */
759     first.timeOdds  = appData.firstTimeOdds;
760     second.timeOdds = appData.secondTimeOdds;
761     { int norm = 1;
762         if(appData.timeOddsMode) {
763             norm = first.timeOdds;
764             if(norm > second.timeOdds) norm = second.timeOdds;
765         }
766         first.timeOdds /= norm;
767         second.timeOdds /= norm;
768     }
769
770     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
771     first.accumulateTC = appData.firstAccumulateTC;
772     second.accumulateTC = appData.secondAccumulateTC;
773     first.maxNrOfSessions = second.maxNrOfSessions = 1;
774
775     /* [HGM] debug */
776     first.debug = second.debug = FALSE;
777     first.supportsNPS = second.supportsNPS = UNKNOWN;
778
779     /* [HGM] options */
780     first.optionSettings  = appData.firstOptions;
781     second.optionSettings = appData.secondOptions;
782
783     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
784     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
785     first.isUCI = appData.firstIsUCI; /* [AS] */
786     second.isUCI = appData.secondIsUCI; /* [AS] */
787     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
788     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789
790     if (appData.firstProtocolVersion > PROTOVER ||
791         appData.firstProtocolVersion < 1) {
792       char buf[MSG_SIZ];
793       sprintf(buf, _("protocol version %d not supported"),
794               appData.firstProtocolVersion);
795       DisplayFatalError(buf, 0, 2);
796     } else {
797       first.protocolVersion = appData.firstProtocolVersion;
798     }
799
800     if (appData.secondProtocolVersion > PROTOVER ||
801         appData.secondProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.secondProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       second.protocolVersion = appData.secondProtocolVersion;
808     }
809
810     if (appData.icsActive) {
811         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
812     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
813         appData.clockMode = FALSE;
814         first.sendTime = second.sendTime = 0;
815     }
816
817 #if ZIPPY
818     /* Override some settings from environment variables, for backward
819        compatibility.  Unfortunately it's not feasible to have the env
820        vars just set defaults, at least in xboard.  Ugh.
821     */
822     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
823       ZippyInit();
824     }
825 #endif
826
827     if (appData.noChessProgram) {
828         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829         sprintf(programVersion, "%s", PACKAGE_STRING);
830     } else {
831 #if 0
832         char *p, *q;
833         q = first.program;
834         while (*q != ' ' && *q != NULLCHAR) q++;
835         p = q;
836         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
837         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
838         sprintf(programVersion, "%s + ", PACKAGE_STRING);
839         strncat(programVersion, p, q - p);
840 #else
841         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
844 #endif
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantBerolina:   /* might work if TestLegality is off */
903       case VariantCapaRandom: /* should work */
904       case VariantJanus:      /* should work */
905       case VariantSuper:      /* experimental */
906       case VariantGreat:      /* experimental, requires legality testing to be off */
907         break;
908       }
909     }
910
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
912     InitEngineUCI( installDir, &second );
913 }
914
915 int NextIntegerFromString( char ** str, long * value )
916 {
917     int result = -1;
918     char * s = *str;
919
920     while( *s == ' ' || *s == '\t' ) {
921         s++;
922     }
923
924     *value = 0;
925
926     if( *s >= '0' && *s <= '9' ) {
927         while( *s >= '0' && *s <= '9' ) {
928             *value = *value * 10 + (*s - '0');
929             s++;
930         }
931
932         result = 0;
933     }
934
935     *str = s;
936
937     return result;
938 }
939
940 int NextTimeControlFromString( char ** str, long * value )
941 {
942     long temp;
943     int result = NextIntegerFromString( str, &temp );
944
945     if( result == 0 ) {
946         *value = temp * 60; /* Minutes */
947         if( **str == ':' ) {
948             (*str)++;
949             result = NextIntegerFromString( str, &temp );
950             *value += temp; /* Seconds */
951         }
952     }
953
954     return result;
955 }
956
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
959     int result = -1; long temp, temp2;
960
961     if(**str != '+') return -1; // old params remain in force!
962     (*str)++;
963     if( NextTimeControlFromString( str, &temp ) ) return -1;
964
965     if(**str != '/') {
966         /* time only: incremental or sudden-death time control */
967         if(**str == '+') { /* increment follows; read it */
968             (*str)++;
969             if(result = NextIntegerFromString( str, &temp2)) return -1;
970             *inc = temp2 * 1000;
971         } else *inc = 0;
972         *moves = 0; *tc = temp * 1000;
973         return 0;
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
975
976     (*str)++; /* classical time control */
977     result = NextTimeControlFromString( str, &temp2);
978     if(result == 0) {
979         *moves = temp/60;
980         *tc    = temp2 * 1000;
981         *inc   = 0;
982     }
983     return result;
984 }
985
986 int GetTimeQuota(int movenr)
987 {   /* [HGM] get time to add from the multi-session time-control string */
988     int moves=1; /* kludge to force reading of first session */
989     long time, increment;
990     char *s = fullTimeControlString;
991
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993     do {
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996         if(movenr == -1) return time;    /* last move before new session     */
997         if(!moves) return increment;     /* current session is incremental   */
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */
999     } while(movenr >= -1);               /* try again for next session       */
1000
1001     return 0; // no new time quota on this move
1002 }
1003
1004 int
1005 ParseTimeControl(tc, ti, mps)
1006      char *tc;
1007      int ti;
1008      int mps;
1009 {
1010 #if 0
1011     int matched, min, sec;
1012
1013     matched = sscanf(tc, "%d:%d", &min, &sec);
1014     if (matched == 1) {
1015         timeControl = min * 60 * 1000;
1016     } else if (matched == 2) {
1017         timeControl = (min * 60 + sec) * 1000;
1018     } else {
1019         return FALSE;
1020     }
1021 #else
1022     long tc1;
1023     long tc2;
1024     char buf[MSG_SIZ];
1025
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1027     if(ti > 0) {
1028         if(mps)
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030         else sprintf(buf, "+%s+%d", tc, ti);
1031     } else {
1032         if(mps)
1033              sprintf(buf, "+%d/%s", mps, tc);
1034         else sprintf(buf, "+%s", tc);
1035     }
1036     fullTimeControlString = StrSave(buf);
1037
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1039         return FALSE;
1040     }
1041
1042     if( *tc == '/' ) {
1043         /* Parse second time control */
1044         tc++;
1045
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1047             return FALSE;
1048         }
1049
1050         if( tc2 == 0 ) {
1051             return FALSE;
1052         }
1053
1054         timeControl_2 = tc2 * 1000;
1055     }
1056     else {
1057         timeControl_2 = 0;
1058     }
1059
1060     if( tc1 == 0 ) {
1061         return FALSE;
1062     }
1063
1064     timeControl = tc1 * 1000;
1065 #endif
1066
1067     if (ti >= 0) {
1068         timeIncrement = ti * 1000;  /* convert to ms */
1069         movesPerSession = 0;
1070     } else {
1071         timeIncrement = 0;
1072         movesPerSession = mps;
1073     }
1074     return TRUE;
1075 }
1076
1077 void
1078 InitBackEnd2()
1079 {
1080   if (appData.debugMode) {
1081     fprintf(debugFP, "%s\n", programVersion);
1082   }
1083
1084   if (appData.matchGames > 0) {
1085     appData.matchMode = TRUE;
1086   } else if (appData.matchMode) {
1087     appData.matchGames = 1;
1088   }
1089   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090     appData.matchGames = appData.sameColorGames;
1091   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1094   }
1095   Reset(TRUE, FALSE);
1096   if (appData.noChessProgram || first.protocolVersion == 1) {
1097     InitBackEnd3();
1098   } else {
1099     /* kludge: allow timeout for initial "feature" commands */
1100     FreezeUI();
1101     DisplayMessage("", _("Starting chess program"));
1102     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1103   }
1104 }
1105
1106 void
1107 InitBackEnd3 P((void))
1108 {
1109     GameMode initialMode;
1110     char buf[MSG_SIZ];
1111     int err;
1112
1113     InitChessProgram(&first, startedFromSetupPosition);
1114
1115
1116     if (appData.icsActive) {
1117 #ifdef WIN32
1118         /* [DM] Make a console window if needed [HGM] merged ifs */
1119         ConsoleCreate();
1120 #endif
1121         err = establish();
1122         if (err != 0) {
1123             if (*appData.icsCommPort != NULLCHAR) {
1124                 sprintf(buf, _("Could not open comm port %s"),
1125                         appData.icsCommPort);
1126             } else {
1127                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1128                         appData.icsHost, appData.icsPort);
1129             }
1130             DisplayFatalError(buf, err, 1);
1131             return;
1132         }
1133         SetICSMode();
1134         telnetISR =
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136         fromUserISR =
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138     } else if (appData.noChessProgram) {
1139         SetNCPMode();
1140     } else {
1141         SetGNUMode();
1142     }
1143
1144     if (*appData.cmailGameName != NULLCHAR) {
1145         SetCmailMode();
1146         OpenLoopback(&cmailPR);
1147         cmailISR =
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1149     }
1150
1151     ThawUI();
1152     DisplayMessage("", "");
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {
1154       initialMode = BeginningOfGame;
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156       initialMode = TwoMachinesPlay;
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158       initialMode = AnalyzeFile;
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160       initialMode = AnalyzeMode;
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162       initialMode = MachinePlaysWhite;
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164       initialMode = MachinePlaysBlack;
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166       initialMode = EditGame;
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168       initialMode = EditPosition;
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170       initialMode = Training;
1171     } else {
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173       DisplayFatalError(buf, 0, 2);
1174       return;
1175     }
1176
1177     if (appData.matchMode) {
1178         /* Set up machine vs. machine match */
1179         if (appData.noChessProgram) {
1180             DisplayFatalError(_("Can't have a match with no chess programs"),
1181                               0, 2);
1182             return;
1183         }
1184         matchMode = TRUE;
1185         matchGame = 1;
1186         if (*appData.loadGameFile != NULLCHAR) {
1187             int index = appData.loadGameIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadGameFromFile(appData.loadGameFile,
1190                                   index,
1191                                   appData.loadGameFile, FALSE)) {
1192                 DisplayFatalError(_("Bad game file"), 0, 1);
1193                 return;
1194             }
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             int index = appData.loadPositionIndex; // [HGM] autoinc
1197             if(index<0) lastIndex = index = 1;
1198             if (!LoadPositionFromFile(appData.loadPositionFile,
1199                                       index,
1200                                       appData.loadPositionFile)) {
1201                 DisplayFatalError(_("Bad position file"), 0, 1);
1202                 return;
1203             }
1204         }
1205         TwoMachinesEvent();
1206     } else if (*appData.cmailGameName != NULLCHAR) {
1207         /* Set up cmail mode */
1208         ReloadCmailMsgEvent(TRUE);
1209     } else {
1210         /* Set up other modes */
1211         if (initialMode == AnalyzeFile) {
1212           if (*appData.loadGameFile == NULLCHAR) {
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1214             return;
1215           }
1216         }
1217         if (*appData.loadGameFile != NULLCHAR) {
1218             (void) LoadGameFromFile(appData.loadGameFile,
1219                                     appData.loadGameIndex,
1220                                     appData.loadGameFile, TRUE);
1221         } else if (*appData.loadPositionFile != NULLCHAR) {
1222             (void) LoadPositionFromFile(appData.loadPositionFile,
1223                                         appData.loadPositionIndex,
1224                                         appData.loadPositionFile);
1225             /* [HGM] try to make self-starting even after FEN load */
1226             /* to allow automatic setup of fairy variants with wtm */
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228                 gameMode = BeginningOfGame;
1229                 setboardSpoiledMachineBlack = 1;
1230             }
1231             /* [HGM] loadPos: make that every new game uses the setup */
1232             /* from file as long as we do not switch variant          */
1233             if(!blackPlaysFirst) { int i;
1234                 startedFromPositionFile = TRUE;
1235                 CopyBoard(filePosition, boards[0]);
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1237             }
1238         }
1239         if (initialMode == AnalyzeMode) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1242             return;
1243           }
1244           if (appData.icsActive) {
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1246             return;
1247           }
1248           AnalyzeModeEvent();
1249         } else if (initialMode == AnalyzeFile) {
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251           ShowThinkingEvent();
1252           AnalyzeFileEvent();
1253           AnalysisPeriodicEvent(1);
1254         } else if (initialMode == MachinePlaysWhite) {
1255           if (appData.noChessProgram) {
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1257                               0, 2);
1258             return;
1259           }
1260           if (appData.icsActive) {
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1262                               0, 2);
1263             return;
1264           }
1265           MachineWhiteEvent();
1266         } else if (initialMode == MachinePlaysBlack) {
1267           if (appData.noChessProgram) {
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1269                               0, 2);
1270             return;
1271           }
1272           if (appData.icsActive) {
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1274                               0, 2);
1275             return;
1276           }
1277           MachineBlackEvent();
1278         } else if (initialMode == TwoMachinesPlay) {
1279           if (appData.noChessProgram) {
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1281                               0, 2);
1282             return;
1283           }
1284           if (appData.icsActive) {
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1286                               0, 2);
1287             return;
1288           }
1289           TwoMachinesEvent();
1290         } else if (initialMode == EditGame) {
1291           EditGameEvent();
1292         } else if (initialMode == EditPosition) {
1293           EditPositionEvent();
1294         } else if (initialMode == Training) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1297             return;
1298           }
1299           TrainingEvent();
1300         }
1301     }
1302 }
1303
1304 /*
1305  * Establish will establish a contact to a remote host.port.
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307  *  used to talk to the host.
1308  * Returns 0 if okay, error code if not.
1309  */
1310 int
1311 establish()
1312 {
1313     char buf[MSG_SIZ];
1314
1315     if (*appData.icsCommPort != NULLCHAR) {
1316         /* Talk to the host through a serial comm port */
1317         return OpenCommPort(appData.icsCommPort, &icsPR);
1318
1319     } else if (*appData.gateway != NULLCHAR) {
1320         if (*appData.remoteShell == NULLCHAR) {
1321             /* Use the rcmd protocol to run telnet program on a gateway host */
1322             snprintf(buf, sizeof(buf), "%s %s %s",
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1325
1326         } else {
1327             /* Use the rsh program to run telnet program on a gateway host */
1328             if (*appData.remoteUser == NULLCHAR) {
1329                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330                         appData.gateway, appData.telnetProgram,
1331                         appData.icsHost, appData.icsPort);
1332             } else {
1333                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334                         appData.remoteShell, appData.gateway,
1335                         appData.remoteUser, appData.telnetProgram,
1336                         appData.icsHost, appData.icsPort);
1337             }
1338             return StartChildProcess(buf, "", &icsPR);
1339
1340         }
1341     } else if (appData.useTelnet) {
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1343
1344     } else {
1345         /* TCP socket interface differs somewhat between
1346            Unix and NT; handle details in the front end.
1347            */
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1349     }
1350 }
1351
1352 void
1353 show_bytes(fp, buf, count)
1354      FILE *fp;
1355      char *buf;
1356      int count;
1357 {
1358     while (count--) {
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360             fprintf(fp, "\\%03o", *buf & 0xff);
1361         } else {
1362             putc(*buf, fp);
1363         }
1364         buf++;
1365     }
1366     fflush(fp);
1367 }
1368
1369 /* Returns an errno value */
1370 int
1371 OutputMaybeTelnet(pr, message, count, outError)
1372      ProcRef pr;
1373      char *message;
1374      int count;
1375      int *outError;
1376 {
1377     char buf[8192], *p, *q, *buflim;
1378     int left, newcount, outcount;
1379
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381         *appData.gateway != NULLCHAR) {
1382         if (appData.debugMode) {
1383             fprintf(debugFP, ">ICS: ");
1384             show_bytes(debugFP, message, count);
1385             fprintf(debugFP, "\n");
1386         }
1387         return OutputToProcess(pr, message, count, outError);
1388     }
1389
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1391     p = message;
1392     q = buf;
1393     left = count;
1394     newcount = 0;
1395     while (left) {
1396         if (q >= buflim) {
1397             if (appData.debugMode) {
1398                 fprintf(debugFP, ">ICS: ");
1399                 show_bytes(debugFP, buf, newcount);
1400                 fprintf(debugFP, "\n");
1401             }
1402             outcount = OutputToProcess(pr, buf, newcount, outError);
1403             if (outcount < newcount) return -1; /* to be sure */
1404             q = buf;
1405             newcount = 0;
1406         }
1407         if (*p == '\n') {
1408             *q++ = '\r';
1409             newcount++;
1410         } else if (((unsigned char) *p) == TN_IAC) {
1411             *q++ = (char) TN_IAC;
1412             newcount ++;
1413         }
1414         *q++ = *p++;
1415         newcount++;
1416         left--;
1417     }
1418     if (appData.debugMode) {
1419         fprintf(debugFP, ">ICS: ");
1420         show_bytes(debugFP, buf, newcount);
1421         fprintf(debugFP, "\n");
1422     }
1423     outcount = OutputToProcess(pr, buf, newcount, outError);
1424     if (outcount < newcount) return -1; /* to be sure */
1425     return count;
1426 }
1427
1428 void
1429 read_from_player(isr, closure, message, count, error)
1430      InputSourceRef isr;
1431      VOIDSTAR closure;
1432      char *message;
1433      int count;
1434      int error;
1435 {
1436     int outError, outCount;
1437     static int gotEof = 0;
1438
1439     /* Pass data read from player on to ICS */
1440     if (count > 0) {
1441         gotEof = 0;
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443         if (outCount < count) {
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445         }
1446     } else if (count < 0) {
1447         RemoveInputSource(isr);
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449     } else if (gotEof++ > 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1452     }
1453 }
1454
1455 void
1456 SendToICS(s)
1457      char *s;
1458 {
1459     int count, outCount, outError;
1460
1461     if (icsPR == NULL) return;
1462
1463     count = strlen(s);
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465     if (outCount < count) {
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1467     }
1468 }
1469
1470 /* This is used for sending logon scripts to the ICS. Sending
1471    without a delay causes problems when using timestamp on ICC
1472    (at least on my machine). */
1473 void
1474 SendToICSDelayed(s,msdelay)
1475      char *s;
1476      long msdelay;
1477 {
1478     int count, outCount, outError;
1479
1480     if (icsPR == NULL) return;
1481
1482     count = strlen(s);
1483     if (appData.debugMode) {
1484         fprintf(debugFP, ">ICS: ");
1485         show_bytes(debugFP, s, count);
1486         fprintf(debugFP, "\n");
1487     }
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489                                       msdelay);
1490     if (outCount < count) {
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492     }
1493 }
1494
1495
1496 /* Remove all highlighting escape sequences in s
1497    Also deletes any suffix starting with '('
1498    */
1499 char *
1500 StripHighlightAndTitle(s)
1501      char *s;
1502 {
1503     static char retbuf[MSG_SIZ];
1504     char *p = retbuf;
1505
1506     while (*s != NULLCHAR) {
1507         while (*s == '\033') {
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;
1509             if (*s != NULLCHAR) s++;
1510         }
1511         while (*s != NULLCHAR && *s != '\033') {
1512             if (*s == '(' || *s == '[') {
1513                 *p = NULLCHAR;
1514                 return retbuf;
1515             }
1516             *p++ = *s++;
1517         }
1518     }
1519     *p = NULLCHAR;
1520     return retbuf;
1521 }
1522
1523 /* Remove all highlighting escape sequences in s */
1524 char *
1525 StripHighlight(s)
1526      char *s;
1527 {
1528     static char retbuf[MSG_SIZ];
1529     char *p = retbuf;
1530
1531     while (*s != NULLCHAR) {
1532         while (*s == '\033') {
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;
1534             if (*s != NULLCHAR) s++;
1535         }
1536         while (*s != NULLCHAR && *s != '\033') {
1537             *p++ = *s++;
1538         }
1539     }
1540     *p = NULLCHAR;
1541     return retbuf;
1542 }
1543
1544 char *variantNames[] = VARIANT_NAMES;
1545 char *
1546 VariantName(v)
1547      VariantClass v;
1548 {
1549     return variantNames[v];
1550 }
1551
1552
1553 /* Identify a variant from the strings the chess servers use or the
1554    PGN Variant tag names we use. */
1555 VariantClass
1556 StringToVariant(e)
1557      char *e;
1558 {
1559     char *p;
1560     int wnum = -1;
1561     VariantClass v = VariantNormal;
1562     int i, found = FALSE;
1563     char buf[MSG_SIZ];
1564
1565     if (!e) return v;
1566
1567     /* [HGM] skip over optional board-size prefixes */
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570         while( *e++ != '_');
1571     }
1572
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574       if (StrCaseStr(e, variantNames[i])) {
1575         v = (VariantClass) i;
1576         found = TRUE;
1577         break;
1578       }
1579     }
1580
1581     if (!found) {
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583           || StrCaseStr(e, "wild/fr")
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585         v = VariantFischeRandom;
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587                  (i = 1, p = StrCaseStr(e, "w"))) {
1588         p += i;
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1590         if (isdigit(*p)) {
1591           wnum = atoi(p);
1592         } else {
1593           wnum = -1;
1594         }
1595         switch (wnum) {
1596         case 0: /* FICS only, actually */
1597         case 1:
1598           /* Castling legal even if K starts on d-file */
1599           v = VariantWildCastle;
1600           break;
1601         case 2:
1602         case 3:
1603         case 4:
1604           /* Castling illegal even if K & R happen to start in
1605              normal positions. */
1606           v = VariantNoCastle;
1607           break;
1608         case 5:
1609         case 7:
1610         case 8:
1611         case 10:
1612         case 11:
1613         case 12:
1614         case 13:
1615         case 14:
1616         case 15:
1617         case 18:
1618         case 19:
1619           /* Castling legal iff K & R start in normal positions */
1620           v = VariantNormal;
1621           break;
1622         case 6:
1623         case 20:
1624         case 21:
1625           /* Special wilds for position setup; unclear what to do here */
1626           v = VariantLoadable;
1627           break;
1628         case 9:
1629           /* Bizarre ICC game */
1630           v = VariantTwoKings;
1631           break;
1632         case 16:
1633           v = VariantKriegspiel;
1634           break;
1635         case 17:
1636           v = VariantLosers;
1637           break;
1638         case 22:
1639           v = VariantFischeRandom;
1640           break;
1641         case 23:
1642           v = VariantCrazyhouse;
1643           break;
1644         case 24:
1645           v = VariantBughouse;
1646           break;
1647         case 25:
1648           v = Variant3Check;
1649           break;
1650         case 26:
1651           /* Not quite the same as FICS suicide! */
1652           v = VariantGiveaway;
1653           break;
1654         case 27:
1655           v = VariantAtomic;
1656           break;
1657         case 28:
1658           v = VariantShatranj;
1659           break;
1660
1661         /* Temporary names for future ICC types.  The name *will* change in
1662            the next xboard/WinBoard release after ICC defines it. */
1663         case 29:
1664           v = Variant29;
1665           break;
1666         case 30:
1667           v = Variant30;
1668           break;
1669         case 31:
1670           v = Variant31;
1671           break;
1672         case 32:
1673           v = Variant32;
1674           break;
1675         case 33:
1676           v = Variant33;
1677           break;
1678         case 34:
1679           v = Variant34;
1680           break;
1681         case 35:
1682           v = Variant35;
1683           break;
1684         case 36:
1685           v = Variant36;
1686           break;
1687         case 37:
1688           v = VariantShogi;
1689           break;
1690         case 38:
1691           v = VariantXiangqi;
1692           break;
1693         case 39:
1694           v = VariantCourier;
1695           break;
1696         case 40:
1697           v = VariantGothic;
1698           break;
1699         case 41:
1700           v = VariantCapablanca;
1701           break;
1702         case 42:
1703           v = VariantKnightmate;
1704           break;
1705         case 43:
1706           v = VariantFairy;
1707           break;
1708         case 44:
1709           v = VariantCylinder;
1710           break;
1711         case 45:
1712           v = VariantFalcon;
1713           break;
1714         case 46:
1715           v = VariantCapaRandom;
1716           break;
1717         case 47:
1718           v = VariantBerolina;
1719           break;
1720         case 48:
1721           v = VariantJanus;
1722           break;
1723         case 49:
1724           v = VariantSuper;
1725           break;
1726         case 50:
1727           v = VariantGreat;
1728           break;
1729         case -1:
1730           /* Found "wild" or "w" in the string but no number;
1731              must assume it's normal chess. */
1732           v = VariantNormal;
1733           break;
1734         default:
1735           sprintf(buf, _("Unknown wild type %d"), wnum);
1736           DisplayError(buf, 0);
1737           v = VariantUnknown;
1738           break;
1739         }
1740       }
1741     }
1742     if (appData.debugMode) {
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744               e, wnum, VariantName(v));
1745     }
1746     return v;
1747 }
1748
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1751
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753    advance *index beyond it, and set leftover_start to the new value of
1754    *index; else return FALSE.  If pattern contains the character '*', it
1755    matches any sequence of characters not containing '\r', '\n', or the
1756    character following the '*' (if any), and the matched sequence(s) are
1757    copied into star_match.
1758    */
1759 int
1760 looking_at(buf, index, pattern)
1761      char *buf;
1762      int *index;
1763      char *pattern;
1764 {
1765     char *bufp = &buf[*index], *patternp = pattern;
1766     int star_count = 0;
1767     char *matchp = star_match[0];
1768
1769     for (;;) {
1770         if (*patternp == NULLCHAR) {
1771             *index = leftover_start = bufp - buf;
1772             *matchp = NULLCHAR;
1773             return TRUE;
1774         }
1775         if (*bufp == NULLCHAR) return FALSE;
1776         if (*patternp == '*') {
1777             if (*bufp == *(patternp + 1)) {
1778                 *matchp = NULLCHAR;
1779                 matchp = star_match[++star_count];
1780                 patternp += 2;
1781                 bufp++;
1782                 continue;
1783             } else if (*bufp == '\n' || *bufp == '\r') {
1784                 patternp++;
1785                 if (*patternp == NULLCHAR)
1786                   continue;
1787                 else
1788                   return FALSE;
1789             } else {
1790                 *matchp++ = *bufp++;
1791                 continue;
1792             }
1793         }
1794         if (*patternp != *bufp) return FALSE;
1795         patternp++;
1796         bufp++;
1797     }
1798 }
1799
1800 void
1801 SendToPlayer(data, length)
1802      char *data;
1803      int length;
1804 {
1805     int error, outCount;
1806     outCount = OutputToProcess(NoProc, data, length, &error);
1807     if (outCount < length) {
1808         DisplayFatalError(_("Error writing to display"), error, 1);
1809     }
1810 }
1811
1812 void
1813 PackHolding(packed, holding)
1814      char packed[];
1815      char *holding;
1816 {
1817     char *p = holding;
1818     char *q = packed;
1819     int runlength = 0;
1820     int curr = 9999;
1821     do {
1822         if (*p == curr) {
1823             runlength++;
1824         } else {
1825             switch (runlength) {
1826               case 0:
1827                 break;
1828               case 1:
1829                 *q++ = curr;
1830                 break;
1831               case 2:
1832                 *q++ = curr;
1833                 *q++ = curr;
1834                 break;
1835               default:
1836                 sprintf(q, "%d", runlength);
1837                 while (*q) q++;
1838                 *q++ = curr;
1839                 break;
1840             }
1841             runlength = 1;
1842             curr = *p;
1843         }
1844     } while (*p++);
1845     *q = NULLCHAR;
1846 }
1847
1848 /* Telnet protocol requests from the front end */
1849 void
1850 TelnetRequest(ddww, option)
1851      unsigned char ddww, option;
1852 {
1853     unsigned char msg[3];
1854     int outCount, outError;
1855
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857
1858     if (appData.debugMode) {
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860         switch (ddww) {
1861           case TN_DO:
1862             ddwwStr = "DO";
1863             break;
1864           case TN_DONT:
1865             ddwwStr = "DONT";
1866             break;
1867           case TN_WILL:
1868             ddwwStr = "WILL";
1869             break;
1870           case TN_WONT:
1871             ddwwStr = "WONT";
1872             break;
1873           default:
1874             ddwwStr = buf1;
1875             sprintf(buf1, "%d", ddww);
1876             break;
1877         }
1878         switch (option) {
1879           case TN_ECHO:
1880             optionStr = "ECHO";
1881             break;
1882           default:
1883             optionStr = buf2;
1884             sprintf(buf2, "%d", option);
1885             break;
1886         }
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888     }
1889     msg[0] = TN_IAC;
1890     msg[1] = ddww;
1891     msg[2] = option;
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893     if (outCount < 3) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 void
1899 DoEcho()
1900 {
1901     if (!appData.icsActive) return;
1902     TelnetRequest(TN_DO, TN_ECHO);
1903 }
1904
1905 void
1906 DontEcho()
1907 {
1908     if (!appData.icsActive) return;
1909     TelnetRequest(TN_DONT, TN_ECHO);
1910 }
1911
1912 void
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 {
1915     /* put the holdings sent to us by the server on the board holdings area */
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917     char p;
1918     ChessSquare piece;
1919
1920     if(gameInfo.holdingsWidth < 2)  return;
1921
1922     if( (int)lowestPiece >= BlackPawn ) {
1923         holdingsColumn = 0;
1924         countsColumn = 1;
1925         holdingsStartRow = BOARD_HEIGHT-1;
1926         direction = -1;
1927     } else {
1928         holdingsColumn = BOARD_WIDTH-1;
1929         countsColumn = BOARD_WIDTH-2;
1930         holdingsStartRow = 0;
1931         direction = 1;
1932     }
1933
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935         board[i][holdingsColumn] = EmptySquare;
1936         board[i][countsColumn]   = (ChessSquare) 0;
1937     }
1938     while( (p=*holdings++) != NULLCHAR ) {
1939         piece = CharToPiece( ToUpper(p) );
1940         if(piece == EmptySquare) continue;
1941         /*j = (int) piece - (int) WhitePawn;*/
1942         j = PieceToNumber(piece);
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944         if(j < 0) continue;               /* should not happen */
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947         board[holdingsStartRow+j*direction][countsColumn]++;
1948     }
1949
1950 }
1951
1952
1953 void
1954 VariantSwitch(Board board, VariantClass newVariant)
1955 {
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959
1960    startedFromPositionFile = FALSE;
1961    if(gameInfo.variant == newVariant) return;
1962
1963    /* [HGM] This routine is called each time an assignment is made to
1964     * gameInfo.variant during a game, to make sure the board sizes
1965     * are set to match the new variant. If that means adding or deleting
1966     * holdings, we shift the playing board accordingly
1967     * This kludge is needed because in ICS observe mode, we get boards
1968     * of an ongoing game without knowing the variant, and learn about the
1969     * latter only later. This can be because of the move list we requested,
1970     * in which case the game history is refilled from the beginning anyway,
1971     * but also when receiving holdings of a crazyhouse game. In the latter
1972     * case we want to add those holdings to the already received position.
1973     */
1974
1975
1976   if (appData.debugMode) {
1977     fprintf(debugFP, "Switch board from %s to %s\n",
1978                VariantName(gameInfo.variant), VariantName(newVariant));
1979     setbuf(debugFP, NULL);
1980   }
1981     shuffleOpenings = 0;       /* [HGM] shuffle */
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983     switch(newVariant) {
1984             case VariantShogi:
1985               newWidth = 9;  newHeight = 9;
1986               gameInfo.holdingsSize = 7;
1987             case VariantBughouse:
1988             case VariantCrazyhouse:
1989               newHoldingsWidth = 2; break;
1990             default:
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;
1992     }
1993
1994     if(newWidth  != gameInfo.boardWidth  ||
1995        newHeight != gameInfo.boardHeight ||
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {
1997
1998         /* shift position to new playing area, if needed */
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000            for(i=0; i<BOARD_HEIGHT; i++)
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003                                                      board[i][j];
2004            for(i=0; i<newHeight; i++) {
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007            }
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009            for(i=0; i<BOARD_HEIGHT; i++)
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012                                                  board[i][j];
2013         }
2014
2015         gameInfo.boardWidth  = newWidth;
2016         gameInfo.boardHeight = newHeight;
2017         gameInfo.holdingsWidth = newHoldingsWidth;
2018         gameInfo.variant = newVariant;
2019         InitDrawingSizes(-2, 0);
2020
2021         /* [HGM] The following should definitely be solved in a better way */
2022 #if 0
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025         saveEP = epStatus[0];
2026 #endif
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2028 #if 0
2029         epStatus[0] = saveEP;
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2032 #endif
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034
2035     forwardMostMove = oldForwardMostMove;
2036     backwardMostMove = oldBackwardMostMove;
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2038 }
2039
2040 static int loggedOn = FALSE;
2041
2042 /*-- Game start info cache: --*/
2043 int gs_gamenum;
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2050
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2053
2054 void
2055 read_from_ics(isr, closure, data, count, error)
2056      InputSourceRef isr;
2057      VOIDSTAR closure;
2058      char *data;
2059      int count;
2060      int error;
2061 {
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2071
2072     static int started = STARTED_NONE;
2073     static char parse[20000];
2074     static int parse_pos = 0;
2075     static char buf[BUF_SIZE + 1];
2076     static int firstTime = TRUE, intfSet = FALSE;
2077     static ColorClass prevColor = ColorNormal;
2078     static int savingComment = FALSE;
2079     char str[500];
2080     int i, oldi;
2081     int buf_len;
2082     int next_out;
2083     int tkind;
2084     int backup;    /* [DM] For zippy color lines */
2085     char *p;
2086
2087     if (appData.debugMode) {
2088       if (!error) {
2089         fprintf(debugFP, "<ICS: ");
2090         show_bytes(debugFP, data, count);
2091         fprintf(debugFP, "\n");
2092       }
2093     }
2094
2095     if (appData.debugMode) { int f = forwardMostMove;
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2098     }
2099     if (count > 0) {
2100         /* If last read ended with a partial line that we couldn't parse,
2101            prepend it to the new read and try again. */
2102         if (leftover_len > 0) {
2103             for (i=0; i<leftover_len; i++)
2104               buf[i] = buf[leftover_start + i];
2105         }
2106
2107         /* Copy in new characters, removing nulls and \r's */
2108         buf_len = leftover_len;
2109         for (i = 0; i < count; i++) {
2110             if (data[i] != NULLCHAR && data[i] != '\r')
2111               buf[buf_len++] = data[i];
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2115                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2116             }
2117         }
2118
2119         buf[buf_len] = NULLCHAR;
2120         next_out = leftover_len;
2121         leftover_start = 0;
2122
2123         i = 0;
2124         while (i < buf_len) {
2125             /* Deal with part of the TELNET option negotiation
2126                protocol.  We refuse to do anything beyond the
2127                defaults, except that we allow the WILL ECHO option,
2128                which ICS uses to turn off password echoing when we are
2129                directly connected to it.  We reject this option
2130                if localLineEditing mode is on (always on in xboard)
2131                and we are talking to port 23, which might be a real
2132                telnet server that will try to keep WILL ECHO on permanently.
2133              */
2134             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2135                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2136                 unsigned char option;
2137                 oldi = i;
2138                 switch ((unsigned char) buf[++i]) {
2139                   case TN_WILL:
2140                     if (appData.debugMode)
2141                       fprintf(debugFP, "\n<WILL ");
2142                     switch (option = (unsigned char) buf[++i]) {
2143                       case TN_ECHO:
2144                         if (appData.debugMode)
2145                           fprintf(debugFP, "ECHO ");
2146                         /* Reply only if this is a change, according
2147                            to the protocol rules. */
2148                         if (remoteEchoOption) break;
2149                         if (appData.localLineEditing &&
2150                             atoi(appData.icsPort) == TN_PORT) {
2151                             TelnetRequest(TN_DONT, TN_ECHO);
2152                         } else {
2153                             EchoOff();
2154                             TelnetRequest(TN_DO, TN_ECHO);
2155                             remoteEchoOption = TRUE;
2156                         }
2157                         break;
2158                       default:
2159                         if (appData.debugMode)
2160                           fprintf(debugFP, "%d ", option);
2161                         /* Whatever this is, we don't want it. */
2162                         TelnetRequest(TN_DONT, option);
2163                         break;
2164                     }
2165                     break;
2166                   case TN_WONT:
2167                     if (appData.debugMode)
2168                       fprintf(debugFP, "\n<WONT ");
2169                     switch (option = (unsigned char) buf[++i]) {
2170                       case TN_ECHO:
2171                         if (appData.debugMode)
2172                           fprintf(debugFP, "ECHO ");
2173                         /* Reply only if this is a change, according
2174                            to the protocol rules. */
2175                         if (!remoteEchoOption) break;
2176                         EchoOn();
2177                         TelnetRequest(TN_DONT, TN_ECHO);
2178                         remoteEchoOption = FALSE;
2179                         break;
2180                       default:
2181                         if (appData.debugMode)
2182                           fprintf(debugFP, "%d ", (unsigned char) option);
2183                         /* Whatever this is, it must already be turned
2184                            off, because we never agree to turn on
2185                            anything non-default, so according to the
2186                            protocol rules, we don't reply. */
2187                         break;
2188                     }
2189                     break;
2190                   case TN_DO:
2191                     if (appData.debugMode)
2192                       fprintf(debugFP, "\n<DO ");
2193                     switch (option = (unsigned char) buf[++i]) {
2194                       default:
2195                         /* Whatever this is, we refuse to do it. */
2196                         if (appData.debugMode)
2197                           fprintf(debugFP, "%d ", option);
2198                         TelnetRequest(TN_WONT, option);
2199                         break;
2200                     }
2201                     break;
2202                   case TN_DONT:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<DONT ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       default:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         /* Whatever this is, we are already not doing
2210                            it, because we never agree to do anything
2211                            non-default, so according to the protocol
2212                            rules, we don't reply. */
2213                         break;
2214                     }
2215                     break;
2216                   case TN_IAC:
2217                     if (appData.debugMode)
2218                       fprintf(debugFP, "\n<IAC ");
2219                     /* Doubled IAC; pass it through */
2220                     i--;
2221                     break;
2222                   default:
2223                     if (appData.debugMode)
2224                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2225                     /* Drop all other telnet commands on the floor */
2226                     break;
2227                 }
2228                 if (oldi > next_out)
2229                   SendToPlayer(&buf[next_out], oldi - next_out);
2230                 if (++i > next_out)
2231                   next_out = i;
2232                 continue;
2233             }
2234
2235             /* OK, this at least will *usually* work */
2236             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2237                 loggedOn = TRUE;
2238             }
2239
2240             if (loggedOn && !intfSet) {
2241                 if (ics_type == ICS_ICC) {
2242                   sprintf(str,
2243                           "/set-quietly interface %s\n/set-quietly style 12\n",
2244                           programVersion);
2245
2246                 } else if (ics_type == ICS_CHESSNET) {
2247                   sprintf(str, "/style 12\n");
2248                 } else {
2249                   strcpy(str, "alias $ @\n$set interface ");
2250                   strcat(str, programVersion);
2251                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2252 #ifdef WIN32
2253                   strcat(str, "$iset nohighlight 1\n");
2254 #endif
2255                   strcat(str, "$iset lock 1\n$style 12\n");
2256                 }
2257                 SendToICS(str);
2258                 intfSet = TRUE;
2259             }
2260
2261             if (started == STARTED_COMMENT) {
2262                 /* Accumulate characters in comment */
2263                 parse[parse_pos++] = buf[i];
2264                 if (buf[i] == '\n') {
2265                     parse[parse_pos] = NULLCHAR;
2266                     if(!suppressKibitz) // [HGM] kibitz
2267                         AppendComment(forwardMostMove, StripHighlight(parse));
2268                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2269                         int nrDigit = 0, nrAlph = 0, i;
2270                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2271                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2272                         parse[parse_pos] = NULLCHAR;
2273                         // try to be smart: if it does not look like search info, it should go to
2274                         // ICS interaction window after all, not to engine-output window.
2275                         for(i=0; i<parse_pos; i++) { // count letters and digits
2276                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2277                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2278                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2279                         }
2280                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2281                             int depth=0; float score;
2282                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2283                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2284                                 pvInfoList[forwardMostMove-1].depth = depth;
2285                                 pvInfoList[forwardMostMove-1].score = 100*score;
2286                             }
2287                             OutputKibitz(suppressKibitz, parse);
2288                         } else {
2289                             char tmp[MSG_SIZ];
2290                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2291                             SendToPlayer(tmp, strlen(tmp));
2292                         }
2293                     }
2294                     started = STARTED_NONE;
2295                 } else {
2296                     /* Don't match patterns against characters in chatter */
2297                     i++;
2298                     continue;
2299                 }
2300             }
2301             if (started == STARTED_CHATTER) {
2302                 if (buf[i] != '\n') {
2303                     /* Don't match patterns against characters in chatter */
2304                     i++;
2305                     continue;
2306                 }
2307                 started = STARTED_NONE;
2308             }
2309
2310             /* Kludge to deal with rcmd protocol */
2311             if (firstTime && looking_at(buf, &i, "\001*")) {
2312                 DisplayFatalError(&buf[1], 0, 1);
2313                 continue;
2314             } else {
2315                 firstTime = FALSE;
2316             }
2317
2318             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2319                 ics_type = ICS_ICC;
2320                 ics_prefix = "/";
2321                 if (appData.debugMode)
2322                   fprintf(debugFP, "ics_type %d\n", ics_type);
2323                 continue;
2324             }
2325             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2326                 ics_type = ICS_FICS;
2327                 ics_prefix = "$";
2328                 if (appData.debugMode)
2329                   fprintf(debugFP, "ics_type %d\n", ics_type);
2330                 continue;
2331             }
2332             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2333                 ics_type = ICS_CHESSNET;
2334                 ics_prefix = "/";
2335                 if (appData.debugMode)
2336                   fprintf(debugFP, "ics_type %d\n", ics_type);
2337                 continue;
2338             }
2339
2340             if (!loggedOn &&
2341                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2342                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2343                  looking_at(buf, &i, "will be \"*\""))) {
2344               strcpy(ics_handle, star_match[0]);
2345               continue;
2346             }
2347
2348             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2349               char buf[MSG_SIZ];
2350               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2351               DisplayIcsInteractionTitle(buf);
2352               have_set_title = TRUE;
2353             }
2354
2355             /* skip finger notes */
2356             if (started == STARTED_NONE &&
2357                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2358                  (buf[i] == '1' && buf[i+1] == '0')) &&
2359                 buf[i+2] == ':' && buf[i+3] == ' ') {
2360               started = STARTED_CHATTER;
2361               i += 3;
2362               continue;
2363             }
2364
2365             /* skip formula vars */
2366             if (started == STARTED_NONE &&
2367                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2368               started = STARTED_CHATTER;
2369               i += 3;
2370               continue;
2371             }
2372
2373             oldi = i;
2374             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2375             if (appData.autoKibitz && started == STARTED_NONE &&
2376                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2377                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2378                 if(looking_at(buf, &i, "* kibitzes: ") &&
2379                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2380                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2381                         suppressKibitz = TRUE;
2382                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2383                                 && (gameMode == IcsPlayingWhite)) ||
2384                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2385                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2386                             started = STARTED_CHATTER; // own kibitz we simply discard
2387                         else {
2388                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2389                             parse_pos = 0; parse[0] = NULLCHAR;
2390                             savingComment = TRUE;
2391                             suppressKibitz = gameMode != IcsObserving ? 2 :
2392                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2393                         }
2394                         continue;
2395                 } else
2396                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2397                     started = STARTED_CHATTER;
2398                     suppressKibitz = TRUE;
2399                 }
2400             } // [HGM] kibitz: end of patch
2401
2402             if (appData.zippyTalk || appData.zippyPlay) {
2403                 /* [DM] Backup address for color zippy lines */
2404                 backup = i;
2405 #if ZIPPY
2406        #ifdef WIN32
2407                if (loggedOn == TRUE)
2408                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2409                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2410        #else
2411                 if (ZippyControl(buf, &i) ||
2412                     ZippyConverse(buf, &i) ||
2413                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2414                       loggedOn = TRUE;
2415                       if (!appData.colorize) continue;
2416                 }
2417        #endif
2418 #endif
2419             } // [DM] 'else { ' deleted
2420                 if (/* Don't color "message" or "messages" output */
2421                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2422                     looking_at(buf, &i, "*. * at *:*: ") ||
2423                     looking_at(buf, &i, "--* (*:*): ") ||
2424                     /* Regular tells and says */
2425                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2426                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2427                     looking_at(buf, &i, "* says: ") ||
2428                     /* Message notifications (same color as tells) */
2429                     looking_at(buf, &i, "* has left a message ") ||
2430                     looking_at(buf, &i, "* just sent you a message:\n") ||
2431                     /* Whispers and kibitzes */
2432                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2433                     looking_at(buf, &i, "* kibitzes: ") ||
2434                     /* Channel tells */
2435                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2436
2437                   if (tkind == 1 && strchr(star_match[0], ':')) {
2438                       /* Avoid "tells you:" spoofs in channels */
2439                      tkind = 3;
2440                   }
2441                   if (star_match[0][0] == NULLCHAR ||
2442                       strchr(star_match[0], ' ') ||
2443                       (tkind == 3 && strchr(star_match[1], ' '))) {
2444                     /* Reject bogus matches */
2445                     i = oldi;
2446                   } else {
2447                     if (appData.colorize) {
2448                       if (oldi > next_out) {
2449                         SendToPlayer(&buf[next_out], oldi - next_out);
2450                         next_out = oldi;
2451                       }
2452                       switch (tkind) {
2453                       case 1:
2454                         Colorize(ColorTell, FALSE);
2455                         curColor = ColorTell;
2456                         break;
2457                       case 2:
2458                         Colorize(ColorKibitz, FALSE);
2459                         curColor = ColorKibitz;
2460                         break;
2461                       case 3:
2462                         p = strrchr(star_match[1], '(');
2463                         if (p == NULL) {
2464                           p = star_match[1];
2465                         } else {
2466                           p++;
2467                         }
2468                         if (atoi(p) == 1) {
2469                           Colorize(ColorChannel1, FALSE);
2470                           curColor = ColorChannel1;
2471                         } else {
2472                           Colorize(ColorChannel, FALSE);
2473                           curColor = ColorChannel;
2474                         }
2475                         break;
2476                       case 5:
2477                         curColor = ColorNormal;
2478                         break;
2479                       }
2480                     }
2481                     if (started == STARTED_NONE && appData.autoComment &&
2482                         (gameMode == IcsObserving ||
2483                          gameMode == IcsPlayingWhite ||
2484                          gameMode == IcsPlayingBlack)) {
2485                       parse_pos = i - oldi;
2486                       memcpy(parse, &buf[oldi], parse_pos);
2487                       parse[parse_pos] = NULLCHAR;
2488                       started = STARTED_COMMENT;
2489                       savingComment = TRUE;
2490                     } else {
2491                       started = STARTED_CHATTER;
2492                       savingComment = FALSE;
2493                     }
2494                     loggedOn = TRUE;
2495                     continue;
2496                   }
2497                 }
2498
2499                 if (looking_at(buf, &i, "* s-shouts: ") ||
2500                     looking_at(buf, &i, "* c-shouts: ")) {
2501                     if (appData.colorize) {
2502                         if (oldi > next_out) {
2503                             SendToPlayer(&buf[next_out], oldi - next_out);
2504                             next_out = oldi;
2505                         }
2506                         Colorize(ColorSShout, FALSE);
2507                         curColor = ColorSShout;
2508                     }
2509                     loggedOn = TRUE;
2510                     started = STARTED_CHATTER;
2511                     continue;
2512                 }
2513
2514                 if (looking_at(buf, &i, "--->")) {
2515                     loggedOn = TRUE;
2516                     continue;
2517                 }
2518
2519                 if (looking_at(buf, &i, "* shouts: ") ||
2520                     looking_at(buf, &i, "--> ")) {
2521                     if (appData.colorize) {
2522                         if (oldi > next_out) {
2523                             SendToPlayer(&buf[next_out], oldi - next_out);
2524                             next_out = oldi;
2525                         }
2526                         Colorize(ColorShout, FALSE);
2527                         curColor = ColorShout;
2528                     }
2529                     loggedOn = TRUE;
2530                     started = STARTED_CHATTER;
2531                     continue;
2532                 }
2533
2534                 if (looking_at( buf, &i, "Challenge:")) {
2535                     if (appData.colorize) {
2536                         if (oldi > next_out) {
2537                             SendToPlayer(&buf[next_out], oldi - next_out);
2538                             next_out = oldi;
2539                         }
2540                         Colorize(ColorChallenge, FALSE);
2541                         curColor = ColorChallenge;
2542                     }
2543                     loggedOn = TRUE;
2544                     continue;
2545                 }
2546
2547                 if (looking_at(buf, &i, "* offers you") ||
2548                     looking_at(buf, &i, "* offers to be") ||
2549                     looking_at(buf, &i, "* would like to") ||
2550                     looking_at(buf, &i, "* requests to") ||
2551                     looking_at(buf, &i, "Your opponent offers") ||
2552                     looking_at(buf, &i, "Your opponent requests")) {
2553
2554                     if (appData.colorize) {
2555                         if (oldi > next_out) {
2556                             SendToPlayer(&buf[next_out], oldi - next_out);
2557                             next_out = oldi;
2558                         }
2559                         Colorize(ColorRequest, FALSE);
2560                         curColor = ColorRequest;
2561                     }
2562                     continue;
2563                 }
2564
2565                 if (looking_at(buf, &i, "* (*) seeking")) {
2566                     if (appData.colorize) {
2567                         if (oldi > next_out) {
2568                             SendToPlayer(&buf[next_out], oldi - next_out);
2569                             next_out = oldi;
2570                         }
2571                         Colorize(ColorSeek, FALSE);
2572                         curColor = ColorSeek;
2573                     }
2574                     continue;
2575             }
2576
2577             if (looking_at(buf, &i, "\\   ")) {
2578                 if (prevColor != ColorNormal) {
2579                     if (oldi > next_out) {
2580                         SendToPlayer(&buf[next_out], oldi - next_out);
2581                         next_out = oldi;
2582                     }
2583                     Colorize(prevColor, TRUE);
2584                     curColor = prevColor;
2585                 }
2586                 if (savingComment) {
2587                     parse_pos = i - oldi;
2588                     memcpy(parse, &buf[oldi], parse_pos);
2589                     parse[parse_pos] = NULLCHAR;
2590                     started = STARTED_COMMENT;
2591                 } else {
2592                     started = STARTED_CHATTER;
2593                 }
2594                 continue;
2595             }
2596
2597             if (looking_at(buf, &i, "Black Strength :") ||
2598                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2599                 looking_at(buf, &i, "<10>") ||
2600                 looking_at(buf, &i, "#@#")) {
2601                 /* Wrong board style */
2602                 loggedOn = TRUE;
2603                 SendToICS(ics_prefix);
2604                 SendToICS("set style 12\n");
2605                 SendToICS(ics_prefix);
2606                 SendToICS("refresh\n");
2607                 continue;
2608             }
2609
2610             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2611                 ICSInitScript();
2612                 have_sent_ICS_logon = 1;
2613                 continue;
2614             }
2615
2616             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2617                 (looking_at(buf, &i, "\n<12> ") ||
2618                  looking_at(buf, &i, "<12> "))) {
2619                 loggedOn = TRUE;
2620                 if (oldi > next_out) {
2621                     SendToPlayer(&buf[next_out], oldi - next_out);
2622                 }
2623                 next_out = i;
2624                 started = STARTED_BOARD;
2625                 parse_pos = 0;
2626                 continue;
2627             }
2628
2629             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2630                 looking_at(buf, &i, "<b1> ")) {
2631                 if (oldi > next_out) {
2632                     SendToPlayer(&buf[next_out], oldi - next_out);
2633                 }
2634                 next_out = i;
2635                 started = STARTED_HOLDINGS;
2636                 parse_pos = 0;
2637                 continue;
2638             }
2639
2640             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2641                 loggedOn = TRUE;
2642                 /* Header for a move list -- first line */
2643
2644                 switch (ics_getting_history) {
2645                   case H_FALSE:
2646                     switch (gameMode) {
2647                       case IcsIdle:
2648                       case BeginningOfGame:
2649                         /* User typed "moves" or "oldmoves" while we
2650                            were idle.  Pretend we asked for these
2651                            moves and soak them up so user can step
2652                            through them and/or save them.
2653                            */
2654                         Reset(FALSE, TRUE);
2655                         gameMode = IcsObserving;
2656                         ModeHighlight();
2657                         ics_gamenum = -1;
2658                         ics_getting_history = H_GOT_UNREQ_HEADER;
2659                         break;
2660                       case EditGame: /*?*/
2661                       case EditPosition: /*?*/
2662                         /* Should above feature work in these modes too? */
2663                         /* For now it doesn't */
2664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2665                         break;
2666                       default:
2667                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2668                         break;
2669                     }
2670                     break;
2671                   case H_REQUESTED:
2672                     /* Is this the right one? */
2673                     if (gameInfo.white && gameInfo.black &&
2674                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2675                         strcmp(gameInfo.black, star_match[2]) == 0) {
2676                         /* All is well */
2677                         ics_getting_history = H_GOT_REQ_HEADER;
2678                     }
2679                     break;
2680                   case H_GOT_REQ_HEADER:
2681                   case H_GOT_UNREQ_HEADER:
2682                   case H_GOT_UNWANTED_HEADER:
2683                   case H_GETTING_MOVES:
2684                     /* Should not happen */
2685                     DisplayError(_("Error gathering move list: two headers"), 0);
2686                     ics_getting_history = H_FALSE;
2687                     break;
2688                 }
2689
2690                 /* Save player ratings into gameInfo if needed */
2691                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2692                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2693                     (gameInfo.whiteRating == -1 ||
2694                      gameInfo.blackRating == -1)) {
2695
2696                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2697                     gameInfo.blackRating = string_to_rating(star_match[3]);
2698                     if (appData.debugMode)
2699                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2700                               gameInfo.whiteRating, gameInfo.blackRating);
2701                 }
2702                 continue;
2703             }
2704
2705             if (looking_at(buf, &i,
2706               "* * match, initial time: * minute*, increment: * second")) {
2707                 /* Header for a move list -- second line */
2708                 /* Initial board will follow if this is a wild game */
2709                 if (gameInfo.event != NULL) free(gameInfo.event);
2710                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2711                 gameInfo.event = StrSave(str);
2712                 /* [HGM] we switched variant. Translate boards if needed. */
2713                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2714                 continue;
2715             }
2716
2717             if (looking_at(buf, &i, "Move  ")) {
2718                 /* Beginning of a move list */
2719                 switch (ics_getting_history) {
2720                   case H_FALSE:
2721                     /* Normally should not happen */
2722                     /* Maybe user hit reset while we were parsing */
2723                     break;
2724                   case H_REQUESTED:
2725                     /* Happens if we are ignoring a move list that is not
2726                      * the one we just requested.  Common if the user
2727                      * tries to observe two games without turning off
2728                      * getMoveList */
2729                     break;
2730                   case H_GETTING_MOVES:
2731                     /* Should not happen */
2732                     DisplayError(_("Error gathering move list: nested"), 0);
2733                     ics_getting_history = H_FALSE;
2734                     break;
2735                   case H_GOT_REQ_HEADER:
2736                     ics_getting_history = H_GETTING_MOVES;
2737                     started = STARTED_MOVES;
2738                     parse_pos = 0;
2739                     if (oldi > next_out) {
2740                         SendToPlayer(&buf[next_out], oldi - next_out);
2741                     }
2742                     break;
2743                   case H_GOT_UNREQ_HEADER:
2744                     ics_getting_history = H_GETTING_MOVES;
2745                     started = STARTED_MOVES_NOHIDE;
2746                     parse_pos = 0;
2747                     break;
2748                   case H_GOT_UNWANTED_HEADER:
2749                     ics_getting_history = H_FALSE;
2750                     break;
2751                 }
2752                 continue;
2753             }
2754
2755             if (looking_at(buf, &i, "% ") ||
2756                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2757                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2758                 savingComment = FALSE;
2759                 switch (started) {
2760                   case STARTED_MOVES:
2761                   case STARTED_MOVES_NOHIDE:
2762                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2763                     parse[parse_pos + i - oldi] = NULLCHAR;
2764                     ParseGameHistory(parse);
2765 #if ZIPPY
2766                     if (appData.zippyPlay && first.initDone) {
2767                         FeedMovesToProgram(&first, forwardMostMove);
2768                         if (gameMode == IcsPlayingWhite) {
2769                             if (WhiteOnMove(forwardMostMove)) {
2770                                 if (first.sendTime) {
2771                                   if (first.useColors) {
2772                                     SendToProgram("black\n", &first);
2773                                   }
2774                                   SendTimeRemaining(&first, TRUE);
2775                                 }
2776 #if 0
2777                                 if (first.useColors) {
2778                                   SendToProgram("white\ngo\n", &first);
2779                                 } else {
2780                                   SendToProgram("go\n", &first);
2781                                 }
2782 #else
2783                                 if (first.useColors) {
2784                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2785                                 }
2786                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2787 #endif
2788                                 first.maybeThinking = TRUE;
2789                             } else {
2790                                 if (first.usePlayother) {
2791                                   if (first.sendTime) {
2792                                     SendTimeRemaining(&first, TRUE);
2793                                   }
2794                                   SendToProgram("playother\n", &first);
2795                                   firstMove = FALSE;
2796                                 } else {
2797                                   firstMove = TRUE;
2798                                 }
2799                             }
2800                         } else if (gameMode == IcsPlayingBlack) {
2801                             if (!WhiteOnMove(forwardMostMove)) {
2802                                 if (first.sendTime) {
2803                                   if (first.useColors) {
2804                                     SendToProgram("white\n", &first);
2805                                   }
2806                                   SendTimeRemaining(&first, FALSE);
2807                                 }
2808 #if 0
2809                                 if (first.useColors) {
2810                                   SendToProgram("black\ngo\n", &first);
2811                                 } else {
2812                                   SendToProgram("go\n", &first);
2813                                 }
2814 #else
2815                                 if (first.useColors) {
2816                                   SendToProgram("black\n", &first);
2817                                 }
2818                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2819 #endif
2820                                 first.maybeThinking = TRUE;
2821                             } else {
2822                                 if (first.usePlayother) {
2823                                   if (first.sendTime) {
2824                                     SendTimeRemaining(&first, FALSE);
2825                                   }
2826                                   SendToProgram("playother\n", &first);
2827                                   firstMove = FALSE;
2828                                 } else {
2829                                   firstMove = TRUE;
2830                                 }
2831                             }
2832                         }
2833                     }
2834 #endif
2835                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2836                         /* Moves came from oldmoves or moves command
2837                            while we weren't doing anything else.
2838                            */
2839                         currentMove = forwardMostMove;
2840                         ClearHighlights();/*!!could figure this out*/
2841                         flipView = appData.flipView;
2842                         DrawPosition(FALSE, boards[currentMove]);
2843                         DisplayBothClocks();
2844                         sprintf(str, "%s vs. %s",
2845                                 gameInfo.white, gameInfo.black);
2846                         DisplayTitle(str);
2847                         gameMode = IcsIdle;
2848                     } else {
2849                         /* Moves were history of an active game */
2850                         if (gameInfo.resultDetails != NULL) {
2851                             free(gameInfo.resultDetails);
2852                             gameInfo.resultDetails = NULL;
2853                         }
2854                     }
2855                     HistorySet(parseList, backwardMostMove,
2856                                forwardMostMove, currentMove-1);
2857                     DisplayMove(currentMove - 1);
2858                     if (started == STARTED_MOVES) next_out = i;
2859                     started = STARTED_NONE;
2860                     ics_getting_history = H_FALSE;
2861                     break;
2862
2863                   case STARTED_OBSERVE:
2864                     started = STARTED_NONE;
2865                     SendToICS(ics_prefix);
2866                     SendToICS("refresh\n");
2867                     break;
2868
2869                   default:
2870                     break;
2871                 }
2872                 if(bookHit) { // [HGM] book: simulate book reply
2873                     static char bookMove[MSG_SIZ]; // a bit generous?
2874
2875                     programStats.nodes = programStats.depth = programStats.time =
2876                     programStats.score = programStats.got_only_move = 0;
2877                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2878
2879                     strcpy(bookMove, "move ");
2880                     strcat(bookMove, bookHit);
2881                     HandleMachineMove(bookMove, &first);
2882                 }
2883                 continue;
2884             }
2885
2886             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2887                  started == STARTED_HOLDINGS ||
2888                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2889                 /* Accumulate characters in move list or board */
2890                 parse[parse_pos++] = buf[i];
2891             }
2892
2893             /* Start of game messages.  Mostly we detect start of game
2894                when the first board image arrives.  On some versions
2895                of the ICS, though, we need to do a "refresh" after starting
2896                to observe in order to get the current board right away. */
2897             if (looking_at(buf, &i, "Adding game * to observation list")) {
2898                 started = STARTED_OBSERVE;
2899                 continue;
2900             }
2901
2902             /* Handle auto-observe */
2903             if (appData.autoObserve &&
2904                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2905                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2906                 char *player;
2907                 /* Choose the player that was highlighted, if any. */
2908                 if (star_match[0][0] == '\033' ||
2909                     star_match[1][0] != '\033') {
2910                     player = star_match[0];
2911                 } else {
2912                     player = star_match[2];
2913                 }
2914                 sprintf(str, "%sobserve %s\n",
2915                         ics_prefix, StripHighlightAndTitle(player));
2916                 SendToICS(str);
2917
2918                 /* Save ratings from notify string */
2919                 strcpy(player1Name, star_match[0]);
2920                 player1Rating = string_to_rating(star_match[1]);
2921                 strcpy(player2Name, star_match[2]);
2922                 player2Rating = string_to_rating(star_match[3]);
2923
2924                 if (appData.debugMode)
2925                   fprintf(debugFP,
2926                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2927                           player1Name, player1Rating,
2928                           player2Name, player2Rating);
2929
2930                 continue;
2931             }
2932
2933             /* Deal with automatic examine mode after a game,
2934                and with IcsObserving -> IcsExamining transition */
2935             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2936                 looking_at(buf, &i, "has made you an examiner of game *")) {
2937
2938                 int gamenum = atoi(star_match[0]);
2939                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2940                     gamenum == ics_gamenum) {
2941                     /* We were already playing or observing this game;
2942                        no need to refetch history */
2943                     gameMode = IcsExamining;
2944                     if (pausing) {
2945                         pauseExamForwardMostMove = forwardMostMove;
2946                     } else if (currentMove < forwardMostMove) {
2947                         ForwardInner(forwardMostMove);
2948                     }
2949                 } else {
2950                     /* I don't think this case really can happen */
2951                     SendToICS(ics_prefix);
2952                     SendToICS("refresh\n");
2953                 }
2954                 continue;
2955             }
2956
2957             /* Error messages */
2958 //          if (ics_user_moved) {
2959             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2960                 if (looking_at(buf, &i, "Illegal move") ||
2961                     looking_at(buf, &i, "Not a legal move") ||
2962                     looking_at(buf, &i, "Your king is in check") ||
2963                     looking_at(buf, &i, "It isn't your turn") ||
2964                     looking_at(buf, &i, "It is not your move")) {
2965                     /* Illegal move */
2966                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2967                         currentMove = --forwardMostMove;
2968                         DisplayMove(currentMove - 1); /* before DMError */
2969                         DrawPosition(FALSE, boards[currentMove]);
2970                         SwitchClocks();
2971                         DisplayBothClocks();
2972                     }
2973                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2974                     ics_user_moved = 0;
2975                     continue;
2976                 }
2977             }
2978
2979             if (looking_at(buf, &i, "still have time") ||
2980                 looking_at(buf, &i, "not out of time") ||
2981                 looking_at(buf, &i, "either player is out of time") ||
2982                 looking_at(buf, &i, "has timeseal; checking")) {
2983                 /* We must have called his flag a little too soon */
2984                 whiteFlag = blackFlag = FALSE;
2985                 continue;
2986             }
2987
2988             if (looking_at(buf, &i, "added * seconds to") ||
2989                 looking_at(buf, &i, "seconds were added to")) {
2990                 /* Update the clocks */
2991                 SendToICS(ics_prefix);
2992                 SendToICS("refresh\n");
2993                 continue;
2994             }
2995
2996             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2997                 ics_clock_paused = TRUE;
2998                 StopClocks();
2999                 continue;
3000             }
3001
3002             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3003                 ics_clock_paused = FALSE;
3004                 StartClocks();
3005                 continue;
3006             }
3007
3008             /* Grab player ratings from the Creating: message.
3009                Note we have to check for the special case when
3010                the ICS inserts things like [white] or [black]. */
3011             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3012                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3013                 /* star_matches:
3014                    0    player 1 name (not necessarily white)
3015                    1    player 1 rating
3016                    2    empty, white, or black (IGNORED)
3017                    3    player 2 name (not necessarily black)
3018                    4    player 2 rating
3019
3020                    The names/ratings are sorted out when the game
3021                    actually starts (below).
3022                 */
3023                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3024                 player1Rating = string_to_rating(star_match[1]);
3025                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3026                 player2Rating = string_to_rating(star_match[4]);
3027
3028                 if (appData.debugMode)
3029                   fprintf(debugFP,
3030                           "Ratings from 'Creating:' %s %d, %s %d\n",
3031                           player1Name, player1Rating,
3032                           player2Name, player2Rating);
3033
3034                 continue;
3035             }
3036
3037             /* Improved generic start/end-of-game messages */
3038             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3039                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3040                 /* If tkind == 0: */
3041                 /* star_match[0] is the game number */
3042                 /*           [1] is the white player's name */
3043                 /*           [2] is the black player's name */
3044                 /* For end-of-game: */
3045                 /*           [3] is the reason for the game end */
3046                 /*           [4] is a PGN end game-token, preceded by " " */
3047                 /* For start-of-game: */
3048                 /*           [3] begins with "Creating" or "Continuing" */
3049                 /*           [4] is " *" or empty (don't care). */
3050                 int gamenum = atoi(star_match[0]);
3051                 char *whitename, *blackname, *why, *endtoken;
3052                 ChessMove endtype = (ChessMove) 0;
3053
3054                 if (tkind == 0) {
3055                   whitename = star_match[1];
3056                   blackname = star_match[2];
3057                   why = star_match[3];
3058                   endtoken = star_match[4];
3059                 } else {
3060                   whitename = star_match[1];
3061                   blackname = star_match[3];
3062                   why = star_match[5];
3063                   endtoken = star_match[6];
3064                 }
3065
3066                 /* Game start messages */
3067                 if (strncmp(why, "Creating ", 9) == 0 ||
3068                     strncmp(why, "Continuing ", 11) == 0) {
3069                     gs_gamenum = gamenum;
3070                     strcpy(gs_kind, strchr(why, ' ') + 1);
3071 #if ZIPPY
3072                     if (appData.zippyPlay) {
3073                         ZippyGameStart(whitename, blackname);
3074                     }
3075 #endif /*ZIPPY*/
3076                     continue;
3077                 }
3078
3079                 /* Game end messages */
3080                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3081                     ics_gamenum != gamenum) {
3082                     continue;
3083                 }
3084                 while (endtoken[0] == ' ') endtoken++;
3085                 switch (endtoken[0]) {
3086                   case '*':
3087                   default:
3088                     endtype = GameUnfinished;
3089                     break;
3090                   case '0':
3091                     endtype = BlackWins;
3092                     break;
3093                   case '1':
3094                     if (endtoken[1] == '/')
3095                       endtype = GameIsDrawn;
3096                     else
3097                       endtype = WhiteWins;
3098                     break;
3099                 }
3100                 GameEnds(endtype, why, GE_ICS);
3101 #if ZIPPY
3102                 if (appData.zippyPlay && first.initDone) {
3103                     ZippyGameEnd(endtype, why);
3104                     if (first.pr == NULL) {
3105                       /* Start the next process early so that we'll
3106                          be ready for the next challenge */
3107                       StartChessProgram(&first);
3108                     }
3109                     /* Send "new" early, in case this command takes
3110                        a long time to finish, so that we'll be ready
3111                        for the next challenge. */
3112                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3113                     Reset(TRUE, TRUE);
3114                 }
3115 #endif /*ZIPPY*/
3116                 continue;
3117             }
3118
3119             if (looking_at(buf, &i, "Removing game * from observation") ||
3120                 looking_at(buf, &i, "no longer observing game *") ||
3121                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3122                 if (gameMode == IcsObserving &&
3123                     atoi(star_match[0]) == ics_gamenum)
3124                   {
3125                       /* icsEngineAnalyze */
3126                       if (appData.icsEngineAnalyze) {
3127                             ExitAnalyzeMode();
3128                             ModeHighlight();
3129                       }
3130                       StopClocks();
3131                       gameMode = IcsIdle;
3132                       ics_gamenum = -1;
3133                       ics_user_moved = FALSE;
3134                   }
3135                 continue;
3136             }
3137
3138             if (looking_at(buf, &i, "no longer examining game *")) {
3139                 if (gameMode == IcsExamining &&
3140                     atoi(star_match[0]) == ics_gamenum)
3141                   {
3142                       gameMode = IcsIdle;
3143                       ics_gamenum = -1;
3144                       ics_user_moved = FALSE;
3145                   }
3146                 continue;
3147             }
3148
3149             /* Advance leftover_start past any newlines we find,
3150                so only partial lines can get reparsed */
3151             if (looking_at(buf, &i, "\n")) {
3152                 prevColor = curColor;
3153                 if (curColor != ColorNormal) {
3154                     if (oldi > next_out) {
3155                         SendToPlayer(&buf[next_out], oldi - next_out);
3156                         next_out = oldi;
3157                     }
3158                     Colorize(ColorNormal, FALSE);
3159                     curColor = ColorNormal;
3160                 }
3161                 if (started == STARTED_BOARD) {
3162                     started = STARTED_NONE;
3163                     parse[parse_pos] = NULLCHAR;
3164                     ParseBoard12(parse);
3165                     ics_user_moved = 0;
3166
3167                     /* Send premove here */
3168                     if (appData.premove) {
3169                       char str[MSG_SIZ];
3170                       if (currentMove == 0 &&
3171                           gameMode == IcsPlayingWhite &&
3172                           appData.premoveWhite) {
3173                         sprintf(str, "%s%s\n", ics_prefix,
3174                                 appData.premoveWhiteText);
3175                         if (appData.debugMode)
3176                           fprintf(debugFP, "Sending premove:\n");
3177                         SendToICS(str);
3178                       } else if (currentMove == 1 &&
3179                                  gameMode == IcsPlayingBlack &&
3180                                  appData.premoveBlack) {
3181                         sprintf(str, "%s%s\n", ics_prefix,
3182                                 appData.premoveBlackText);
3183                         if (appData.debugMode)
3184                           fprintf(debugFP, "Sending premove:\n");
3185                         SendToICS(str);
3186                       } else if (gotPremove) {
3187                         gotPremove = 0;
3188                         ClearPremoveHighlights();
3189                         if (appData.debugMode)
3190                           fprintf(debugFP, "Sending premove:\n");
3191                           UserMoveEvent(premoveFromX, premoveFromY,
3192                                         premoveToX, premoveToY,
3193                                         premovePromoChar);
3194                       }
3195                     }
3196
3197                     /* Usually suppress following prompt */
3198                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3199                         if (looking_at(buf, &i, "*% ")) {
3200                             savingComment = FALSE;
3201                         }
3202                     }
3203                     next_out = i;
3204                 } else if (started == STARTED_HOLDINGS) {
3205                     int gamenum;
3206                     char new_piece[MSG_SIZ];
3207                     started = STARTED_NONE;
3208                     parse[parse_pos] = NULLCHAR;
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3211                                                         parse, currentMove);
3212                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3213                         gamenum == ics_gamenum) {
3214                         if (gameInfo.variant == VariantNormal) {
3215                           /* [HGM] We seem to switch variant during a game!
3216                            * Presumably no holdings were displayed, so we have
3217                            * to move the position two files to the right to
3218                            * create room for them!
3219                            */
3220                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3221                           /* Get a move list just to see the header, which
3222                              will tell us whether this is really bug or zh */
3223                           if (ics_getting_history == H_FALSE) {
3224                             ics_getting_history = H_REQUESTED;
3225                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3226                             SendToICS(str);
3227                           }
3228                         }
3229                         new_piece[0] = NULLCHAR;
3230                         sscanf(parse, "game %d white [%s black [%s <- %s",
3231                                &gamenum, white_holding, black_holding,
3232                                new_piece);
3233                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3234                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3235                         /* [HGM] copy holdings to board holdings area */
3236                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3237                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3238 #if ZIPPY
3239                         if (appData.zippyPlay && first.initDone) {
3240                             ZippyHoldings(white_holding, black_holding,
3241                                           new_piece);
3242                         }
3243 #endif /*ZIPPY*/
3244                         if (tinyLayout || smallLayout) {
3245                             char wh[16], bh[16];
3246                             PackHolding(wh, white_holding);
3247                             PackHolding(bh, black_holding);
3248                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3249                                     gameInfo.white, gameInfo.black);
3250                         } else {
3251                             sprintf(str, "%s [%s] vs. %s [%s]",
3252                                     gameInfo.white, white_holding,
3253                                     gameInfo.black, black_holding);
3254                         }
3255
3256                         DrawPosition(FALSE, boards[currentMove]);
3257                         DisplayTitle(str);
3258                     }
3259                     /* Suppress following prompt */
3260                     if (looking_at(buf, &i, "*% ")) {
3261                         savingComment = FALSE;
3262                     }
3263                     next_out = i;
3264                 }
3265                 continue;
3266             }
3267
3268             i++;                /* skip unparsed character and loop back */
3269         }
3270
3271         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3272             started != STARTED_HOLDINGS && i > next_out) {
3273             SendToPlayer(&buf[next_out], i - next_out);
3274             next_out = i;
3275         }
3276         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3277
3278         leftover_len = buf_len - leftover_start;
3279         /* if buffer ends with something we couldn't parse,
3280            reparse it after appending the next read */
3281
3282     } else if (count == 0) {
3283         RemoveInputSource(isr);
3284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3285     } else {
3286         DisplayFatalError(_("Error reading from ICS"), error, 1);
3287     }
3288 }
3289
3290
3291 /* Board style 12 looks like this:
3292
3293    <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
3294
3295  * The "<12> " is stripped before it gets to this routine.  The two
3296  * trailing 0's (flip state and clock ticking) are later addition, and
3297  * some chess servers may not have them, or may have only the first.
3298  * Additional trailing fields may be added in the future.
3299  */
3300
3301 #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"
3302
3303 #define RELATION_OBSERVING_PLAYED    0
3304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3305 #define RELATION_PLAYING_MYMOVE      1
3306 #define RELATION_PLAYING_NOTMYMOVE  -1
3307 #define RELATION_EXAMINING           2
3308 #define RELATION_ISOLATED_BOARD     -3
3309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3310
3311 void
3312 ParseBoard12(string)
3313      char *string;
3314 {
3315     GameMode newGameMode;
3316     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3317     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3318     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3319     char to_play, board_chars[200];
3320     char move_str[500], str[500], elapsed_time[500];
3321     char black[32], white[32];
3322     Board board;
3323     int prevMove = currentMove;
3324     int ticking = 2;
3325     ChessMove moveType;
3326     int fromX, fromY, toX, toY;
3327     char promoChar;
3328     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3329     char *bookHit = NULL; // [HGM] book
3330
3331     fromX = fromY = toX = toY = -1;
3332
3333     newGame = FALSE;
3334
3335     if (appData.debugMode)
3336       fprintf(debugFP, _("Parsing board: %s\n"), string);
3337
3338     move_str[0] = NULLCHAR;
3339     elapsed_time[0] = NULLCHAR;
3340     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3341         int  i = 0, j;
3342         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3343             if(string[i] == ' ') { ranks++; files = 0; }
3344             else files++;
3345             i++;
3346         }
3347         for(j = 0; j <i; j++) board_chars[j] = string[j];
3348         board_chars[i] = '\0';
3349         string += i + 1;
3350     }
3351     n = sscanf(string, PATTERN, &to_play, &double_push,
3352                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3353                &gamenum, white, black, &relation, &basetime, &increment,
3354                &white_stren, &black_stren, &white_time, &black_time,
3355                &moveNum, str, elapsed_time, move_str, &ics_flip,
3356                &ticking);
3357
3358     if (n < 21) {
3359         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3360         DisplayError(str, 0);
3361         return;
3362     }
3363
3364     /* Convert the move number to internal form */
3365     moveNum = (moveNum - 1) * 2;
3366     if (to_play == 'B') moveNum++;
3367     if (moveNum >= MAX_MOVES) {
3368       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3369                         0, 1);
3370       return;
3371     }
3372
3373     switch (relation) {
3374       case RELATION_OBSERVING_PLAYED:
3375       case RELATION_OBSERVING_STATIC:
3376         if (gamenum == -1) {
3377             /* Old ICC buglet */
3378             relation = RELATION_OBSERVING_STATIC;
3379         }
3380         newGameMode = IcsObserving;
3381         break;
3382       case RELATION_PLAYING_MYMOVE:
3383       case RELATION_PLAYING_NOTMYMOVE:
3384         newGameMode =
3385           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3386             IcsPlayingWhite : IcsPlayingBlack;
3387         break;
3388       case RELATION_EXAMINING:
3389         newGameMode = IcsExamining;
3390         break;
3391       case RELATION_ISOLATED_BOARD:
3392       default:
3393         /* Just display this board.  If user was doing something else,
3394            we will forget about it until the next board comes. */
3395         newGameMode = IcsIdle;
3396         break;
3397       case RELATION_STARTING_POSITION:
3398         newGameMode = gameMode;
3399         break;
3400     }
3401
3402     /* Modify behavior for initial board display on move listing
3403        of wild games.
3404        */
3405     switch (ics_getting_history) {
3406       case H_FALSE:
3407       case H_REQUESTED:
3408         break;
3409       case H_GOT_REQ_HEADER:
3410       case H_GOT_UNREQ_HEADER:
3411         /* This is the initial position of the current game */
3412         gamenum = ics_gamenum;
3413         moveNum = 0;            /* old ICS bug workaround */
3414         if (to_play == 'B') {
3415           startedFromSetupPosition = TRUE;
3416           blackPlaysFirst = TRUE;
3417           moveNum = 1;
3418           if (forwardMostMove == 0) forwardMostMove = 1;
3419           if (backwardMostMove == 0) backwardMostMove = 1;
3420           if (currentMove == 0) currentMove = 1;
3421         }
3422         newGameMode = gameMode;
3423         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3424         break;
3425       case H_GOT_UNWANTED_HEADER:
3426         /* This is an initial board that we don't want */
3427         return;
3428       case H_GETTING_MOVES:
3429         /* Should not happen */
3430         DisplayError(_("Error gathering move list: extra board"), 0);
3431         ics_getting_history = H_FALSE;
3432         return;
3433     }
3434
3435     /* Take action if this is the first board of a new game, or of a
3436        different game than is currently being displayed.  */
3437     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3438         relation == RELATION_ISOLATED_BOARD) {
3439
3440         /* Forget the old game and get the history (if any) of the new one */
3441         if (gameMode != BeginningOfGame) {
3442           Reset(FALSE, TRUE);
3443         }
3444         newGame = TRUE;
3445         if (appData.autoRaiseBoard) BoardToTop();
3446         prevMove = -3;
3447         if (gamenum == -1) {
3448             newGameMode = IcsIdle;
3449         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3450                    appData.getMoveList) {
3451             /* Need to get game history */
3452             ics_getting_history = H_REQUESTED;
3453             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3454             SendToICS(str);
3455         }
3456
3457         /* Initially flip the board to have black on the bottom if playing
3458            black or if the ICS flip flag is set, but let the user change
3459            it with the Flip View button. */
3460         flipView = appData.autoFlipView ?
3461           (newGameMode == IcsPlayingBlack) || ics_flip :
3462           appData.flipView;
3463
3464         /* Done with values from previous mode; copy in new ones */
3465         gameMode = newGameMode;
3466         ModeHighlight();
3467         ics_gamenum = gamenum;
3468         if (gamenum == gs_gamenum) {
3469             int klen = strlen(gs_kind);
3470             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3471             sprintf(str, "ICS %s", gs_kind);
3472             gameInfo.event = StrSave(str);
3473         } else {
3474             gameInfo.event = StrSave("ICS game");
3475         }
3476         gameInfo.site = StrSave(appData.icsHost);
3477         gameInfo.date = PGNDate();
3478         gameInfo.round = StrSave("-");
3479         gameInfo.white = StrSave(white);
3480         gameInfo.black = StrSave(black);
3481         timeControl = basetime * 60 * 1000;
3482         timeControl_2 = 0;
3483         timeIncrement = increment * 1000;
3484         movesPerSession = 0;
3485         gameInfo.timeControl = TimeControlTagValue();
3486         VariantSwitch(board, StringToVariant(gameInfo.event) );
3487   if (appData.debugMode) {
3488     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3489     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3490     setbuf(debugFP, NULL);
3491   }
3492
3493         gameInfo.outOfBook = NULL;
3494
3495         /* Do we have the ratings? */
3496         if (strcmp(player1Name, white) == 0 &&
3497             strcmp(player2Name, black) == 0) {
3498             if (appData.debugMode)
3499               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3500                       player1Rating, player2Rating);
3501             gameInfo.whiteRating = player1Rating;
3502             gameInfo.blackRating = player2Rating;
3503         } else if (strcmp(player2Name, white) == 0 &&
3504                    strcmp(player1Name, black) == 0) {
3505             if (appData.debugMode)
3506               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3507                       player2Rating, player1Rating);
3508             gameInfo.whiteRating = player2Rating;
3509             gameInfo.blackRating = player1Rating;
3510         }
3511         player1Name[0] = player2Name[0] = NULLCHAR;
3512
3513         /* Silence shouts if requested */
3514         if (appData.quietPlay &&
3515             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3516             SendToICS(ics_prefix);
3517             SendToICS("set shout 0\n");
3518         }
3519     }
3520
3521     /* Deal with midgame name changes */
3522     if (!newGame) {
3523         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3524             if (gameInfo.white) free(gameInfo.white);
3525             gameInfo.white = StrSave(white);
3526         }
3527         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3528             if (gameInfo.black) free(gameInfo.black);
3529             gameInfo.black = StrSave(black);
3530         }
3531     }
3532
3533     /* Throw away game result if anything actually changes in examine mode */
3534     if (gameMode == IcsExamining && !newGame) {
3535         gameInfo.result = GameUnfinished;
3536         if (gameInfo.resultDetails != NULL) {
3537             free(gameInfo.resultDetails);
3538             gameInfo.resultDetails = NULL;
3539         }
3540     }
3541
3542     /* In pausing && IcsExamining mode, we ignore boards coming
3543        in if they are in a different variation than we are. */
3544     if (pauseExamInvalid) return;
3545     if (pausing && gameMode == IcsExamining) {
3546         if (moveNum <= pauseExamForwardMostMove) {
3547             pauseExamInvalid = TRUE;
3548             forwardMostMove = pauseExamForwardMostMove;
3549             return;
3550         }
3551     }
3552
3553   if (appData.debugMode) {
3554     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3555   }
3556     /* Parse the board */
3557     for (k = 0; k < ranks; k++) {
3558       for (j = 0; j < files; j++)
3559         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3560       if(gameInfo.holdingsWidth > 1) {
3561            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3562            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3563       }
3564     }
3565     CopyBoard(boards[moveNum], board);
3566     if (moveNum == 0) {
3567         startedFromSetupPosition =
3568           !CompareBoards(board, initialPosition);
3569         if(startedFromSetupPosition)
3570             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3571     }
3572
3573     /* [HGM] Set castling rights. Take the outermost Rooks,
3574        to make it also work for FRC opening positions. Note that board12
3575        is really defective for later FRC positions, as it has no way to
3576        indicate which Rook can castle if they are on the same side of King.
3577        For the initial position we grant rights to the outermost Rooks,
3578        and remember thos rights, and we then copy them on positions
3579        later in an FRC game. This means WB might not recognize castlings with
3580        Rooks that have moved back to their original position as illegal,
3581        but in ICS mode that is not its job anyway.
3582     */
3583     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3584     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3585
3586         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590             if(board[0][i] == WhiteRook) j = i;
3591         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598
3599         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3600         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3602         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603             if(board[BOARD_HEIGHT-1][k] == bKing)
3604                 initialRights[5] = castlingRights[moveNum][5] = k;
3605     } else { int r;
3606         r = castlingRights[moveNum][0] = initialRights[0];
3607         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3608         r = castlingRights[moveNum][1] = initialRights[1];
3609         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3610         r = castlingRights[moveNum][3] = initialRights[3];
3611         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3612         r = castlingRights[moveNum][4] = initialRights[4];
3613         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3614         /* wildcastle kludge: always assume King has rights */
3615         r = castlingRights[moveNum][2] = initialRights[2];
3616         r = castlingRights[moveNum][5] = initialRights[5];
3617     }
3618     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3619     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3620
3621
3622     if (ics_getting_history == H_GOT_REQ_HEADER ||
3623         ics_getting_history == H_GOT_UNREQ_HEADER) {
3624         /* This was an initial position from a move list, not
3625            the current position */
3626         return;
3627     }
3628
3629     /* Update currentMove and known move number limits */
3630     newMove = newGame || moveNum > forwardMostMove;
3631
3632     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3633     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3634         takeback = forwardMostMove - moveNum;
3635         for (i = 0; i < takeback; i++) {
3636              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3637              SendToProgram("undo\n", &first);
3638         }
3639     }
3640
3641     if (newGame) {
3642         forwardMostMove = backwardMostMove = currentMove = moveNum;
3643         if (gameMode == IcsExamining && moveNum == 0) {
3644           /* Workaround for ICS limitation: we are not told the wild
3645              type when starting to examine a game.  But if we ask for
3646              the move list, the move list header will tell us */
3647             ics_getting_history = H_REQUESTED;
3648             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3649             SendToICS(str);
3650         }
3651     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3652                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3653         forwardMostMove = moveNum;
3654         if (!pausing || currentMove > forwardMostMove)
3655           currentMove = forwardMostMove;
3656     } else {
3657         /* New part of history that is not contiguous with old part */
3658         if (pausing && gameMode == IcsExamining) {
3659             pauseExamInvalid = TRUE;
3660             forwardMostMove = pauseExamForwardMostMove;
3661             return;
3662         }
3663         forwardMostMove = backwardMostMove = currentMove = moveNum;
3664         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3665             ics_getting_history = H_REQUESTED;
3666             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3667             SendToICS(str);
3668         }
3669     }
3670
3671     /* Update the clocks */
3672     if (strchr(elapsed_time, '.')) {
3673       /* Time is in ms */
3674       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3675       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3676     } else {
3677       /* Time is in seconds */
3678       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3679       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3680     }
3681
3682
3683 #if ZIPPY
3684     if (appData.zippyPlay && newGame &&
3685         gameMode != IcsObserving && gameMode != IcsIdle &&
3686         gameMode != IcsExamining)
3687       ZippyFirstBoard(moveNum, basetime, increment);
3688 #endif
3689
3690     /* Put the move on the move list, first converting
3691        to canonical algebraic form. */
3692     if (moveNum > 0) {
3693   if (appData.debugMode) {
3694     if (appData.debugMode) { int f = forwardMostMove;
3695         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3696                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3697     }
3698     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3699     fprintf(debugFP, "moveNum = %d\n", moveNum);
3700     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3701     setbuf(debugFP, NULL);
3702   }
3703         if (moveNum <= backwardMostMove) {
3704             /* We don't know what the board looked like before
3705                this move.  Punt. */
3706             strcpy(parseList[moveNum - 1], move_str);
3707             strcat(parseList[moveNum - 1], " ");
3708             strcat(parseList[moveNum - 1], elapsed_time);
3709             moveList[moveNum - 1][0] = NULLCHAR;
3710         } else if (strcmp(move_str, "none") == 0) {
3711             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3712             /* Again, we don't know what the board looked like;
3713                this is really the start of the game. */
3714             parseList[moveNum - 1][0] = NULLCHAR;
3715             moveList[moveNum - 1][0] = NULLCHAR;
3716             backwardMostMove = moveNum;
3717             startedFromSetupPosition = TRUE;
3718             fromX = fromY = toX = toY = -1;
3719         } else {
3720           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3721           //                 So we parse the long-algebraic move string in stead of the SAN move
3722           int valid; char buf[MSG_SIZ], *prom;
3723
3724           // str looks something like "Q/a1-a2"; kill the slash
3725           if(str[1] == '/')
3726                 sprintf(buf, "%c%s", str[0], str+2);
3727           else  strcpy(buf, str); // might be castling
3728           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3729                 strcat(buf, prom); // long move lacks promo specification!
3730           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3731                 if(appData.debugMode)
3732                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3733                 strcpy(move_str, buf);
3734           }
3735           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3736                                 &fromX, &fromY, &toX, &toY, &promoChar)
3737                || ParseOneMove(buf, moveNum - 1, &moveType,
3738                                 &fromX, &fromY, &toX, &toY, &promoChar);
3739           // end of long SAN patch
3740           if (valid) {
3741             (void) CoordsToAlgebraic(boards[moveNum - 1],
3742                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3743                                      fromY, fromX, toY, toX, promoChar,
3744                                      parseList[moveNum-1]);
3745             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3746                              castlingRights[moveNum]) ) {
3747               case MT_NONE:
3748               case MT_STALEMATE:
3749               default:
3750                 break;
3751               case MT_CHECK:
3752                 if(gameInfo.variant != VariantShogi)
3753                     strcat(parseList[moveNum - 1], "+");
3754                 break;
3755               case MT_CHECKMATE:
3756               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3757                 strcat(parseList[moveNum - 1], "#");
3758                 break;
3759             }
3760             strcat(parseList[moveNum - 1], " ");
3761             strcat(parseList[moveNum - 1], elapsed_time);
3762             /* currentMoveString is set as a side-effect of ParseOneMove */
3763             strcpy(moveList[moveNum - 1], currentMoveString);
3764             strcat(moveList[moveNum - 1], "\n");
3765           } else {
3766             /* Move from ICS was illegal!?  Punt. */
3767   if (appData.debugMode) {
3768     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3769     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3770   }
3771 #if 0
3772             if (appData.testLegality && appData.debugMode) {
3773                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3774                 DisplayError(str, 0);
3775             }
3776 #endif
3777             strcpy(parseList[moveNum - 1], move_str);
3778             strcat(parseList[moveNum - 1], " ");
3779             strcat(parseList[moveNum - 1], elapsed_time);
3780             moveList[moveNum - 1][0] = NULLCHAR;
3781             fromX = fromY = toX = toY = -1;
3782           }
3783         }
3784   if (appData.debugMode) {
3785     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3786     setbuf(debugFP, NULL);
3787   }
3788
3789 #if ZIPPY
3790         /* Send move to chess program (BEFORE animating it). */
3791         if (appData.zippyPlay && !newGame && newMove &&
3792            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3793
3794             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3795                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3796                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3797                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3798                             move_str);
3799                     DisplayError(str, 0);
3800                 } else {
3801                     if (first.sendTime) {
3802                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3803                     }
3804                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3805                     if (firstMove && !bookHit) {
3806                         firstMove = FALSE;
3807                         if (first.useColors) {
3808                           SendToProgram(gameMode == IcsPlayingWhite ?
3809                                         "white\ngo\n" :
3810                                         "black\ngo\n", &first);
3811                         } else {
3812                           SendToProgram("go\n", &first);
3813                         }
3814                         first.maybeThinking = TRUE;
3815                     }
3816                 }
3817             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3818               if (moveList[moveNum - 1][0] == NULLCHAR) {
3819                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3820                 DisplayError(str, 0);
3821               } else {
3822                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3823                 SendMoveToProgram(moveNum - 1, &first);
3824               }
3825             }
3826         }
3827 #endif
3828     }
3829
3830     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3831         /* If move comes from a remote source, animate it.  If it
3832            isn't remote, it will have already been animated. */
3833         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3834             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3835         }
3836         if (!pausing && appData.highlightLastMove) {
3837             SetHighlights(fromX, fromY, toX, toY);
3838         }
3839     }
3840
3841     /* Start the clocks */
3842     whiteFlag = blackFlag = FALSE;
3843     appData.clockMode = !(basetime == 0 && increment == 0);
3844     if (ticking == 0) {
3845       ics_clock_paused = TRUE;
3846       StopClocks();
3847     } else if (ticking == 1) {
3848       ics_clock_paused = FALSE;
3849     }
3850     if (gameMode == IcsIdle ||
3851         relation == RELATION_OBSERVING_STATIC ||
3852         relation == RELATION_EXAMINING ||
3853         ics_clock_paused)
3854       DisplayBothClocks();
3855     else
3856       StartClocks();
3857
3858     /* Display opponents and material strengths */
3859     if (gameInfo.variant != VariantBughouse &&
3860         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3861         if (tinyLayout || smallLayout) {
3862             if(gameInfo.variant == VariantNormal)
3863                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3865                     basetime, increment);
3866             else
3867                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3868                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3869                     basetime, increment, (int) gameInfo.variant);
3870         } else {
3871             if(gameInfo.variant == VariantNormal)
3872                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3873                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3874                     basetime, increment);
3875             else
3876                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3877                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3878                     basetime, increment, VariantName(gameInfo.variant));
3879         }
3880         DisplayTitle(str);
3881   if (appData.debugMode) {
3882     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3883   }
3884     }
3885
3886
3887     /* Display the board */
3888     if (!pausing && !appData.noGUI) {
3889       if (appData.premove)
3890           if (!gotPremove ||
3891              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893               ClearPremoveHighlights();
3894
3895       DrawPosition(FALSE, boards[currentMove]);
3896       DisplayMove(moveNum - 1);
3897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3901       }
3902     }
3903
3904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3905 #if ZIPPY
3906     if(bookHit) { // [HGM] book: simulate book reply
3907         static char bookMove[MSG_SIZ]; // a bit generous?
3908
3909         programStats.nodes = programStats.depth = programStats.time =
3910         programStats.score = programStats.got_only_move = 0;
3911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3912
3913         strcpy(bookMove, "move ");
3914         strcat(bookMove, bookHit);
3915         HandleMachineMove(bookMove, &first);
3916     }
3917 #endif
3918 }
3919
3920 void
3921 GetMoveListEvent()
3922 {
3923     char buf[MSG_SIZ];
3924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925         ics_getting_history = H_REQUESTED;
3926         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3927         SendToICS(buf);
3928     }
3929 }
3930
3931 void
3932 AnalysisPeriodicEvent(force)
3933      int force;
3934 {
3935     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936          && !force) || !appData.periodicUpdates)
3937       return;
3938
3939     /* Send . command to Crafty to collect stats */
3940     SendToProgram(".\n", &first);
3941
3942     /* Don't send another until we get a response (this makes
3943        us stop sending to old Crafty's which don't understand
3944        the "." command (sending illegal cmds resets node count & time,
3945        which looks bad)) */
3946     programStats.ok_to_send = 0;
3947 }
3948
3949 void
3950 SendMoveToProgram(moveNum, cps)
3951      int moveNum;
3952      ChessProgramState *cps;
3953 {
3954     char buf[MSG_SIZ];
3955
3956     if (cps->useUsermove) {
3957       SendToProgram("usermove ", cps);
3958     }
3959     if (cps->useSAN) {
3960       char *space;
3961       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3962         int len = space - parseList[moveNum];
3963         memcpy(buf, parseList[moveNum], len);
3964         buf[len++] = '\n';
3965         buf[len] = NULLCHAR;
3966       } else {
3967         sprintf(buf, "%s\n", parseList[moveNum]);
3968       }
3969       SendToProgram(buf, cps);
3970     } else {
3971       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3972         AlphaRank(moveList[moveNum], 4);
3973         SendToProgram(moveList[moveNum], cps);
3974         AlphaRank(moveList[moveNum], 4); // and back
3975       } else
3976       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3977        * the engine. It would be nice to have a better way to identify castle
3978        * moves here. */
3979       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3980                                                                          && cps->useOOCastle) {
3981         int fromX = moveList[moveNum][0] - AAA;
3982         int fromY = moveList[moveNum][1] - ONE;
3983         int toX = moveList[moveNum][2] - AAA;
3984         int toY = moveList[moveNum][3] - ONE;
3985         if((boards[moveNum][fromY][fromX] == WhiteKing
3986             && boards[moveNum][toY][toX] == WhiteRook)
3987            || (boards[moveNum][fromY][fromX] == BlackKing
3988                && boards[moveNum][toY][toX] == BlackRook)) {
3989           if(toX > fromX) SendToProgram("O-O\n", cps);
3990           else SendToProgram("O-O-O\n", cps);
3991         }
3992         else SendToProgram(moveList[moveNum], cps);
3993       }
3994       else SendToProgram(moveList[moveNum], cps);
3995       /* End of additions by Tord */
3996     }
3997
3998     /* [HGM] setting up the opening has brought engine in force mode! */
3999     /*       Send 'go' if we are in a mode where machine should play. */
4000     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4001         (gameMode == TwoMachinesPlay   ||
4002 #ifdef ZIPPY
4003          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4004 #endif
4005          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4006         SendToProgram("go\n", cps);
4007   if (appData.debugMode) {
4008     fprintf(debugFP, "(extra)\n");
4009   }
4010     }
4011     setboardSpoiledMachineBlack = 0;
4012 }
4013
4014 void
4015 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4016      ChessMove moveType;
4017      int fromX, fromY, toX, toY;
4018 {
4019     char user_move[MSG_SIZ];
4020
4021     switch (moveType) {
4022       default:
4023         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4024                 (int)moveType, fromX, fromY, toX, toY);
4025         DisplayError(user_move + strlen("say "), 0);
4026         break;
4027       case WhiteKingSideCastle:
4028       case BlackKingSideCastle:
4029       case WhiteQueenSideCastleWild:
4030       case BlackQueenSideCastleWild:
4031       /* PUSH Fabien */
4032       case WhiteHSideCastleFR:
4033       case BlackHSideCastleFR:
4034       /* POP Fabien */
4035         sprintf(user_move, "o-o\n");
4036         break;
4037       case WhiteQueenSideCastle:
4038       case BlackQueenSideCastle:
4039       case WhiteKingSideCastleWild:
4040       case BlackKingSideCastleWild:
4041       /* PUSH Fabien */
4042       case WhiteASideCastleFR:
4043       case BlackASideCastleFR:
4044       /* POP Fabien */
4045         sprintf(user_move, "o-o-o\n");
4046         break;
4047       case WhitePromotionQueen:
4048       case BlackPromotionQueen:
4049       case WhitePromotionRook:
4050       case BlackPromotionRook:
4051       case WhitePromotionBishop:
4052       case BlackPromotionBishop:
4053       case WhitePromotionKnight:
4054       case BlackPromotionKnight:
4055       case WhitePromotionKing:
4056       case BlackPromotionKing:
4057       case WhitePromotionChancellor:
4058       case BlackPromotionChancellor:
4059       case WhitePromotionArchbishop:
4060       case BlackPromotionArchbishop:
4061         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4062             sprintf(user_move, "%c%c%c%c=%c\n",
4063                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4064                 PieceToChar(WhiteFerz));
4065         else if(gameInfo.variant == VariantGreat)
4066             sprintf(user_move, "%c%c%c%c=%c\n",
4067                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068                 PieceToChar(WhiteMan));
4069         else
4070             sprintf(user_move, "%c%c%c%c=%c\n",
4071                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072                 PieceToChar(PromoPiece(moveType)));
4073         break;
4074       case WhiteDrop:
4075       case BlackDrop:
4076         sprintf(user_move, "%c@%c%c\n",
4077                 ToUpper(PieceToChar((ChessSquare) fromX)),
4078                 AAA + toX, ONE + toY);
4079         break;
4080       case NormalMove:
4081       case WhiteCapturesEnPassant:
4082       case BlackCapturesEnPassant:
4083       case IllegalMove:  /* could be a variant we don't quite understand */
4084         sprintf(user_move, "%c%c%c%c\n",
4085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4086         break;
4087     }
4088     SendToICS(user_move);
4089 }
4090
4091 void
4092 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4093      int rf, ff, rt, ft;
4094      char promoChar;
4095      char move[7];
4096 {
4097     if (rf == DROP_RANK) {
4098         sprintf(move, "%c@%c%c\n",
4099                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4100     } else {
4101         if (promoChar == 'x' || promoChar == NULLCHAR) {
4102             sprintf(move, "%c%c%c%c\n",
4103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4104         } else {
4105             sprintf(move, "%c%c%c%c%c\n",
4106                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4107         }
4108     }
4109 }
4110
4111 void
4112 ProcessICSInitScript(f)
4113      FILE *f;
4114 {
4115     char buf[MSG_SIZ];
4116
4117     while (fgets(buf, MSG_SIZ, f)) {
4118         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4119     }
4120
4121     fclose(f);
4122 }
4123
4124
4125 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4126 void
4127 AlphaRank(char *move, int n)
4128 {
4129 //    char *p = move, c; int x, y;
4130
4131     if (appData.debugMode) {
4132         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4133     }
4134
4135     if(move[1]=='*' &&
4136        move[2]>='0' && move[2]<='9' &&
4137        move[3]>='a' && move[3]<='x'    ) {
4138         move[1] = '@';
4139         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4140         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4141     } else
4142     if(move[0]>='0' && move[0]<='9' &&
4143        move[1]>='a' && move[1]<='x' &&
4144        move[2]>='0' && move[2]<='9' &&
4145        move[3]>='a' && move[3]<='x'    ) {
4146         /* input move, Shogi -> normal */
4147         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4148         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4149         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4150         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4151     } else
4152     if(move[1]=='@' &&
4153        move[3]>='0' && move[3]<='9' &&
4154        move[2]>='a' && move[2]<='x'    ) {
4155         move[1] = '*';
4156         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4157         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4158     } else
4159     if(
4160        move[0]>='a' && move[0]<='x' &&
4161        move[3]>='0' && move[3]<='9' &&
4162        move[2]>='a' && move[2]<='x'    ) {
4163          /* output move, normal -> Shogi */
4164         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4165         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4166         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4167         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4168         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4169     }
4170     if (appData.debugMode) {
4171         fprintf(debugFP, "   out = '%s'\n", move);
4172     }
4173 }
4174
4175 /* Parser for moves from gnuchess, ICS, or user typein box */
4176 Boolean
4177 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4178      char *move;
4179      int moveNum;
4180      ChessMove *moveType;
4181      int *fromX, *fromY, *toX, *toY;
4182      char *promoChar;
4183 {
4184     if (appData.debugMode) {
4185         fprintf(debugFP, "move to parse: %s\n", move);
4186     }
4187     *moveType = yylexstr(moveNum, move);
4188
4189     switch (*moveType) {
4190       case WhitePromotionChancellor:
4191       case BlackPromotionChancellor:
4192       case WhitePromotionArchbishop:
4193       case BlackPromotionArchbishop:
4194       case WhitePromotionQueen:
4195       case BlackPromotionQueen:
4196       case WhitePromotionRook:
4197       case BlackPromotionRook:
4198       case WhitePromotionBishop:
4199       case BlackPromotionBishop:
4200       case WhitePromotionKnight:
4201       case BlackPromotionKnight:
4202       case WhitePromotionKing:
4203       case BlackPromotionKing:
4204       case NormalMove:
4205       case WhiteCapturesEnPassant:
4206       case BlackCapturesEnPassant:
4207       case WhiteKingSideCastle:
4208       case WhiteQueenSideCastle:
4209       case BlackKingSideCastle:
4210       case BlackQueenSideCastle:
4211       case WhiteKingSideCastleWild:
4212       case WhiteQueenSideCastleWild:
4213       case BlackKingSideCastleWild:
4214       case BlackQueenSideCastleWild:
4215       /* Code added by Tord: */
4216       case WhiteHSideCastleFR:
4217       case WhiteASideCastleFR:
4218       case BlackHSideCastleFR:
4219       case BlackASideCastleFR:
4220       /* End of code added by Tord */
4221       case IllegalMove:         /* bug or odd chess variant */
4222         *fromX = currentMoveString[0] - AAA;
4223         *fromY = currentMoveString[1] - ONE;
4224         *toX = currentMoveString[2] - AAA;
4225         *toY = currentMoveString[3] - ONE;
4226         *promoChar = currentMoveString[4];
4227         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4228             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4229     if (appData.debugMode) {
4230         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4231     }
4232             *fromX = *fromY = *toX = *toY = 0;
4233             return FALSE;
4234         }
4235         if (appData.testLegality) {
4236           return (*moveType != IllegalMove);
4237         } else {
4238           return !(fromX == fromY && toX == toY);
4239         }
4240
4241       case WhiteDrop:
4242       case BlackDrop:
4243         *fromX = *moveType == WhiteDrop ?
4244           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4245           (int) CharToPiece(ToLower(currentMoveString[0]));
4246         *fromY = DROP_RANK;
4247         *toX = currentMoveString[2] - AAA;
4248         *toY = currentMoveString[3] - ONE;
4249         *promoChar = NULLCHAR;
4250         return TRUE;
4251
4252       case AmbiguousMove:
4253       case ImpossibleMove:
4254       case (ChessMove) 0:       /* end of file */
4255       case ElapsedTime:
4256       case Comment:
4257       case PGNTag:
4258       case NAG:
4259       case WhiteWins:
4260       case BlackWins:
4261       case GameIsDrawn:
4262       default:
4263     if (appData.debugMode) {
4264         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4265     }
4266         /* bug? */
4267         *fromX = *fromY = *toX = *toY = 0;
4268         *promoChar = NULLCHAR;
4269         return FALSE;
4270     }
4271 }
4272
4273 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4274 // All positions will have equal probability, but the current method will not provide a unique
4275 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4276 #define DARK 1
4277 #define LITE 2
4278 #define ANY 3
4279
4280 int squaresLeft[4];
4281 int piecesLeft[(int)BlackPawn];
4282 int seed, nrOfShuffles;
4283
4284 void GetPositionNumber()
4285 {       // sets global variable seed
4286         int i;
4287
4288         seed = appData.defaultFrcPosition;
4289         if(seed < 0) { // randomize based on time for negative FRC position numbers
4290                 for(i=0; i<50; i++) seed += random();
4291                 seed = random() ^ random() >> 8 ^ random() << 8;
4292                 if(seed<0) seed = -seed;
4293         }
4294 }
4295
4296 int put(Board board, int pieceType, int rank, int n, int shade)
4297 // put the piece on the (n-1)-th empty squares of the given shade
4298 {
4299         int i;
4300
4301         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4302                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4303                         board[rank][i] = (ChessSquare) pieceType;
4304                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4305                         squaresLeft[ANY]--;
4306                         piecesLeft[pieceType]--;
4307                         return i;
4308                 }
4309         }
4310         return -1;
4311 }
4312
4313
4314 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4315 // calculate where the next piece goes, (any empty square), and put it there
4316 {
4317         int i;
4318
4319         i = seed % squaresLeft[shade];
4320         nrOfShuffles *= squaresLeft[shade];
4321         seed /= squaresLeft[shade];
4322         put(board, pieceType, rank, i, shade);
4323 }
4324
4325 void AddTwoPieces(Board board, int pieceType, int rank)
4326 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4327 {
4328         int i, n=squaresLeft[ANY], j=n-1, k;
4329
4330         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4331         i = seed % k;  // pick one
4332         nrOfShuffles *= k;
4333         seed /= k;
4334         while(i >= j) i -= j--;
4335         j = n - 1 - j; i += j;
4336         put(board, pieceType, rank, j, ANY);
4337         put(board, pieceType, rank, i, ANY);
4338 }
4339
4340 void SetUpShuffle(Board board, int number)
4341 {
4342         int i, p, first=1;
4343
4344         GetPositionNumber(); nrOfShuffles = 1;
4345
4346         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4347         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4348         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4349
4350         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4351
4352         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4353             p = (int) board[0][i];
4354             if(p < (int) BlackPawn) piecesLeft[p] ++;
4355             board[0][i] = EmptySquare;
4356         }
4357
4358         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4359             // shuffles restricted to allow normal castling put KRR first
4360             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4361                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4363                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4365                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4366             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4367                 put(board, WhiteRook, 0, 0, ANY);
4368             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4369         }
4370
4371         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4372             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4373             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4374                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4375                 while(piecesLeft[p] >= 2) {
4376                     AddOnePiece(board, p, 0, LITE);
4377                     AddOnePiece(board, p, 0, DARK);
4378                 }
4379                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4380             }
4381
4382         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4383             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4384             // but we leave King and Rooks for last, to possibly obey FRC restriction
4385             if(p == (int)WhiteRook) continue;
4386             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4387             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4388         }
4389
4390         // now everything is placed, except perhaps King (Unicorn) and Rooks
4391
4392         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4393             // Last King gets castling rights
4394             while(piecesLeft[(int)WhiteUnicorn]) {
4395                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4397             }
4398
4399             while(piecesLeft[(int)WhiteKing]) {
4400                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4402             }
4403
4404
4405         } else {
4406             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4407             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4408         }
4409
4410         // Only Rooks can be left; simply place them all
4411         while(piecesLeft[(int)WhiteRook]) {
4412                 i = put(board, WhiteRook, 0, 0, ANY);
4413                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4414                         if(first) {
4415                                 first=0;
4416                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4417                         }
4418                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4419                 }
4420         }
4421         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4422             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4423         }
4424
4425         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4426 }
4427
4428 int SetCharTable( char *table, const char * map )
4429 /* [HGM] moved here from winboard.c because of its general usefulness */
4430 /*       Basically a safe strcpy that uses the last character as King */
4431 {
4432     int result = FALSE; int NrPieces;
4433
4434     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4435                     && NrPieces >= 12 && !(NrPieces&1)) {
4436         int i; /* [HGM] Accept even length from 12 to 34 */
4437
4438         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4439         for( i=0; i<NrPieces/2-1; i++ ) {
4440             table[i] = map[i];
4441             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4442         }
4443         table[(int) WhiteKing]  = map[NrPieces/2-1];
4444         table[(int) BlackKing]  = map[NrPieces-1];
4445
4446         result = TRUE;
4447     }
4448
4449     return result;
4450 }
4451
4452 void Prelude(Board board)
4453 {       // [HGM] superchess: random selection of exo-pieces
4454         int i, j, k; ChessSquare p;
4455         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4456
4457         GetPositionNumber(); // use FRC position number
4458
4459         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4460             SetCharTable(pieceToChar, appData.pieceToCharTable);
4461             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4462                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4463         }
4464
4465         j = seed%4;                 seed /= 4;
4466         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4467         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4468         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4469         j = seed%3 + (seed%3 >= j); seed /= 3;
4470         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4471         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4472         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4473         j = seed%3;                 seed /= 3;
4474         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4475         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4476         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4477         j = seed%2 + (seed%2 >= j); seed /= 2;
4478         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4479         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4480         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4481         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4482         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4483         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4484         put(board, exoPieces[0],    0, 0, ANY);
4485         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4486 }
4487
4488 void
4489 InitPosition(redraw)
4490      int redraw;
4491 {
4492     ChessSquare (* pieces)[BOARD_SIZE];
4493     int i, j, pawnRow, overrule,
4494     oldx = gameInfo.boardWidth,
4495     oldy = gameInfo.boardHeight,
4496     oldh = gameInfo.holdingsWidth,
4497     oldv = gameInfo.variant;
4498
4499     currentMove = forwardMostMove = backwardMostMove = 0;
4500     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4501
4502     /* [AS] Initialize pv info list [HGM] and game status */
4503     {
4504         for( i=0; i<MAX_MOVES; i++ ) {
4505             pvInfoList[i].depth = 0;
4506             epStatus[i]=EP_NONE;
4507             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4508         }
4509
4510         initialRulePlies = 0; /* 50-move counter start */
4511
4512         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4513         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4514     }
4515
4516
4517     /* [HGM] logic here is completely changed. In stead of full positions */
4518     /* the initialized data only consist of the two backranks. The switch */
4519     /* selects which one we will use, which is than copied to the Board   */
4520     /* initialPosition, which for the rest is initialized by Pawns and    */
4521     /* empty squares. This initial position is then copied to boards[0],  */
4522     /* possibly after shuffling, so that it remains available.            */
4523
4524     gameInfo.holdingsWidth = 0; /* default board sizes */
4525     gameInfo.boardWidth    = 8;
4526     gameInfo.boardHeight   = 8;
4527     gameInfo.holdingsSize  = 0;
4528     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4529     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4530     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4531
4532     switch (gameInfo.variant) {
4533     case VariantFischeRandom:
4534       shuffleOpenings = TRUE;
4535     default:
4536       pieces = FIDEArray;
4537       break;
4538     case VariantShatranj:
4539       pieces = ShatranjArray;
4540       nrCastlingRights = 0;
4541       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4542       break;
4543     case VariantTwoKings:
4544       pieces = twoKingsArray;
4545       break;
4546     case VariantCapaRandom:
4547       shuffleOpenings = TRUE;
4548     case VariantCapablanca:
4549       pieces = CapablancaArray;
4550       gameInfo.boardWidth = 10;
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4552       break;
4553     case VariantGothic:
4554       pieces = GothicArray;
4555       gameInfo.boardWidth = 10;
4556       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4557       break;
4558     case VariantJanus:
4559       pieces = JanusArray;
4560       gameInfo.boardWidth = 10;
4561       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4562       nrCastlingRights = 6;
4563         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4564         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4565         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4566         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4567         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4568         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4569       break;
4570     case VariantFalcon:
4571       pieces = FalconArray;
4572       gameInfo.boardWidth = 10;
4573       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4574       break;
4575     case VariantXiangqi:
4576       pieces = XiangqiArray;
4577       gameInfo.boardWidth  = 9;
4578       gameInfo.boardHeight = 10;
4579       nrCastlingRights = 0;
4580       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4581       break;
4582     case VariantShogi:
4583       pieces = ShogiArray;
4584       gameInfo.boardWidth  = 9;
4585       gameInfo.boardHeight = 9;
4586       gameInfo.holdingsSize = 7;
4587       nrCastlingRights = 0;
4588       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4589       break;
4590     case VariantCourier:
4591       pieces = CourierArray;
4592       gameInfo.boardWidth  = 12;
4593       nrCastlingRights = 0;
4594       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4595       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4596       break;
4597     case VariantKnightmate:
4598       pieces = KnightmateArray;
4599       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4600       break;
4601     case VariantFairy:
4602       pieces = fairyArray;
4603       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4604       break;
4605     case VariantGreat:
4606       pieces = GreatArray;
4607       gameInfo.boardWidth = 10;
4608       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4609       gameInfo.holdingsSize = 8;
4610       break;
4611     case VariantSuper:
4612       pieces = FIDEArray;
4613       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4614       gameInfo.holdingsSize = 8;
4615       startedFromSetupPosition = TRUE;
4616       break;
4617     case VariantCrazyhouse:
4618     case VariantBughouse:
4619       pieces = FIDEArray;
4620       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4621       gameInfo.holdingsSize = 5;
4622       break;
4623     case VariantWildCastle:
4624       pieces = FIDEArray;
4625       /* !!?shuffle with kings guaranteed to be on d or e file */
4626       shuffleOpenings = 1;
4627       break;
4628     case VariantNoCastle:
4629       pieces = FIDEArray;
4630       nrCastlingRights = 0;
4631       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4632       /* !!?unconstrained back-rank shuffle */
4633       shuffleOpenings = 1;
4634       break;
4635     }
4636
4637     overrule = 0;
4638     if(appData.NrFiles >= 0) {
4639         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4640         gameInfo.boardWidth = appData.NrFiles;
4641     }
4642     if(appData.NrRanks >= 0) {
4643         gameInfo.boardHeight = appData.NrRanks;
4644     }
4645     if(appData.holdingsSize >= 0) {
4646         i = appData.holdingsSize;
4647         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4648         gameInfo.holdingsSize = i;
4649     }
4650     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4651     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4652         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4653
4654     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4655     if(pawnRow < 1) pawnRow = 1;
4656
4657     /* User pieceToChar list overrules defaults */
4658     if(appData.pieceToCharTable != NULL)
4659         SetCharTable(pieceToChar, appData.pieceToCharTable);
4660
4661     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4662
4663         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4664             s = (ChessSquare) 0; /* account holding counts in guard band */
4665         for( i=0; i<BOARD_HEIGHT; i++ )
4666             initialPosition[i][j] = s;
4667
4668         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4669         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4670         initialPosition[pawnRow][j] = WhitePawn;
4671         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4672         if(gameInfo.variant == VariantXiangqi) {
4673             if(j&1) {
4674                 initialPosition[pawnRow][j] =
4675                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4676                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4677                    initialPosition[2][j] = WhiteCannon;
4678                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4679                 }
4680             }
4681         }
4682         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4683     }
4684     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4685
4686             j=BOARD_LEFT+1;
4687             initialPosition[1][j] = WhiteBishop;
4688             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4689             j=BOARD_RGHT-2;
4690             initialPosition[1][j] = WhiteRook;
4691             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4692     }
4693
4694     if( nrCastlingRights == -1) {
4695         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4696         /*       This sets default castling rights from none to normal corners   */
4697         /* Variants with other castling rights must set them themselves above    */
4698         nrCastlingRights = 6;
4699
4700         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4701         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4702         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4703         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4704         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4705         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4706      }
4707
4708      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4709      if(gameInfo.variant == VariantGreat) { // promotion commoners
4710         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4711         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4712         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4713         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4714      }
4715 #if 0
4716     if(gameInfo.variant == VariantFischeRandom) {
4717       if( appData.defaultFrcPosition < 0 ) {
4718         ShuffleFRC( initialPosition );
4719       }
4720       else {
4721         SetupFRC( initialPosition, appData.defaultFrcPosition );
4722       }
4723       startedFromSetupPosition = TRUE;
4724     } else
4725 #else
4726   if (appData.debugMode) {
4727     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4728   }
4729     if(shuffleOpenings) {
4730         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731         startedFromSetupPosition = TRUE;
4732     }
4733 #endif
4734     if(startedFromPositionFile) {
4735       /* [HGM] loadPos: use PositionFile for every new game */
4736       CopyBoard(initialPosition, filePosition);
4737       for(i=0; i<nrCastlingRights; i++)
4738           castlingRights[0][i] = initialRights[i] = fileRights[i];
4739       startedFromSetupPosition = TRUE;
4740     }
4741
4742     CopyBoard(boards[0], initialPosition);
4743     if(oldx != gameInfo.boardWidth ||
4744        oldy != gameInfo.boardHeight ||
4745        oldh != gameInfo.holdingsWidth
4746 #ifdef GOTHIC
4747        || oldv == VariantGothic ||        // For licensing popups
4748        gameInfo.variant == VariantGothic
4749 #endif
4750 #ifdef FALCON
4751        || oldv == VariantFalcon ||
4752        gameInfo.variant == VariantFalcon
4753 #endif
4754                                          )
4755       {
4756             InitDrawingSizes(-2 ,0);
4757       }
4758
4759     if (redraw)
4760       DrawPosition(TRUE, boards[currentMove]);
4761
4762 }
4763
4764 void
4765 SendBoard(cps, moveNum)
4766      ChessProgramState *cps;
4767      int moveNum;
4768 {
4769     char message[MSG_SIZ];
4770
4771     if (cps->useSetboard) {
4772       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4773       sprintf(message, "setboard %s\n", fen);
4774       SendToProgram(message, cps);
4775       free(fen);
4776
4777     } else {
4778       ChessSquare *bp;
4779       int i, j;
4780       /* Kludge to set black to move, avoiding the troublesome and now
4781        * deprecated "black" command.
4782        */
4783       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4784
4785       SendToProgram("edit\n", cps);
4786       SendToProgram("#\n", cps);
4787       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4788         bp = &boards[moveNum][i][BOARD_LEFT];
4789         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4790           if ((int) *bp < (int) BlackPawn) {
4791             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4792                     AAA + j, ONE + i);
4793             if(message[0] == '+' || message[0] == '~') {
4794                 sprintf(message, "%c%c%c+\n",
4795                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4796                         AAA + j, ONE + i);
4797             }
4798             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4799                 message[1] = BOARD_RGHT   - 1 - j + '1';
4800                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4801             }
4802             SendToProgram(message, cps);
4803           }
4804         }
4805       }
4806
4807       SendToProgram("c\n", cps);
4808       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4809         bp = &boards[moveNum][i][BOARD_LEFT];
4810         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4811           if (((int) *bp != (int) EmptySquare)
4812               && ((int) *bp >= (int) BlackPawn)) {
4813             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4814                     AAA + j, ONE + i);
4815             if(message[0] == '+' || message[0] == '~') {
4816                 sprintf(message, "%c%c%c+\n",
4817                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4818                         AAA + j, ONE + i);
4819             }
4820             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4821                 message[1] = BOARD_RGHT   - 1 - j + '1';
4822                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4823             }
4824             SendToProgram(message, cps);
4825           }
4826         }
4827       }
4828
4829       SendToProgram(".\n", cps);
4830     }
4831     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4832 }
4833
4834 int
4835 IsPromotion(fromX, fromY, toX, toY)
4836      int fromX, fromY, toX, toY;
4837 {
4838     /* [HGM] add Shogi promotions */
4839     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4840     ChessSquare piece;
4841
4842     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4843       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4844    /* [HGM] Note to self: line above also weeds out drops */
4845     piece = boards[currentMove][fromY][fromX];
4846     if(gameInfo.variant == VariantShogi) {
4847         promotionZoneSize = 3;
4848         highestPromotingPiece = (int)WhiteKing;
4849         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4850            and if in normal chess we then allow promotion to King, why not
4851            allow promotion of other piece in Shogi?                         */
4852     }
4853     if((int)piece >= BlackPawn) {
4854         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4855              return FALSE;
4856         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4857     } else {
4858         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4859            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4860     }
4861     return ( (int)piece <= highestPromotingPiece );
4862 }
4863
4864 int
4865 InPalace(row, column)
4866      int row, column;
4867 {   /* [HGM] for Xiangqi */
4868     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4869          column < (BOARD_WIDTH + 4)/2 &&
4870          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4871     return FALSE;
4872 }
4873
4874 int
4875 PieceForSquare (x, y)
4876      int x;
4877      int y;
4878 {
4879   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4880      return -1;
4881   else
4882      return boards[currentMove][y][x];
4883 }
4884
4885 int
4886 OKToStartUserMove(x, y)
4887      int x, y;
4888 {
4889     ChessSquare from_piece;
4890     int white_piece;
4891
4892     if (matchMode) return FALSE;
4893     if (gameMode == EditPosition) return TRUE;
4894
4895     if (x >= 0 && y >= 0)
4896       from_piece = boards[currentMove][y][x];
4897     else
4898       from_piece = EmptySquare;
4899
4900     if (from_piece == EmptySquare) return FALSE;
4901
4902     white_piece = (int)from_piece >= (int)WhitePawn &&
4903       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4904
4905     switch (gameMode) {
4906       case PlayFromGameFile:
4907       case AnalyzeFile:
4908       case TwoMachinesPlay:
4909       case EndOfGame:
4910         return FALSE;
4911
4912       case IcsObserving:
4913       case IcsIdle:
4914         return FALSE;
4915
4916       case MachinePlaysWhite:
4917       case IcsPlayingBlack:
4918         if (appData.zippyPlay) return FALSE;
4919         if (white_piece) {
4920             DisplayMoveError(_("You are playing Black"));
4921             return FALSE;
4922         }
4923         break;
4924
4925       case MachinePlaysBlack:
4926       case IcsPlayingWhite:
4927         if (appData.zippyPlay) return FALSE;
4928         if (!white_piece) {
4929             DisplayMoveError(_("You are playing White"));
4930             return FALSE;
4931         }
4932         break;
4933
4934       case EditGame:
4935         if (!white_piece && WhiteOnMove(currentMove)) {
4936             DisplayMoveError(_("It is White's turn"));
4937             return FALSE;
4938         }
4939         if (white_piece && !WhiteOnMove(currentMove)) {
4940             DisplayMoveError(_("It is Black's turn"));
4941             return FALSE;
4942         }
4943         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4944             /* Editing correspondence game history */
4945             /* Could disallow this or prompt for confirmation */
4946             cmailOldMove = -1;
4947         }
4948         if (currentMove < forwardMostMove) {
4949             /* Discarding moves */
4950             /* Could prompt for confirmation here,
4951                but I don't think that's such a good idea */
4952             forwardMostMove = currentMove;
4953         }
4954         break;
4955
4956       case BeginningOfGame:
4957         if (appData.icsActive) return FALSE;
4958         if (!appData.noChessProgram) {
4959             if (!white_piece) {
4960                 DisplayMoveError(_("You are playing White"));
4961                 return FALSE;
4962             }
4963         }
4964         break;
4965
4966       case Training:
4967         if (!white_piece && WhiteOnMove(currentMove)) {
4968             DisplayMoveError(_("It is White's turn"));
4969             return FALSE;
4970         }
4971         if (white_piece && !WhiteOnMove(currentMove)) {
4972             DisplayMoveError(_("It is Black's turn"));
4973             return FALSE;
4974         }
4975         break;
4976
4977       default:
4978       case IcsExamining:
4979         break;
4980     }
4981     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4982         && gameMode != AnalyzeFile && gameMode != Training) {
4983         DisplayMoveError(_("Displayed position is not current"));
4984         return FALSE;
4985     }
4986     return TRUE;
4987 }
4988
4989 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4990 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4991 int lastLoadGameUseList = FALSE;
4992 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4993 ChessMove lastLoadGameStart = (ChessMove) 0;
4994
4995
4996 ChessMove
4997 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4998      int fromX, fromY, toX, toY;
4999      int promoChar;
5000 {
5001     ChessMove moveType;
5002     ChessSquare pdown, pup;
5003
5004     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5005     if ((fromX == toX) && (fromY == toY)) {
5006         return ImpossibleMove;
5007     }
5008
5009     /* [HGM] suppress all moves into holdings area and guard band */
5010     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5011             return ImpossibleMove;
5012
5013     /* [HGM] <sameColor> moved to here from winboard.c */
5014     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5015     pdown = boards[currentMove][fromY][fromX];
5016     pup = boards[currentMove][toY][toX];
5017     if (    gameMode != EditPosition &&
5018             (WhitePawn <= pdown && pdown < BlackPawn &&
5019              WhitePawn <= pup && pup < BlackPawn  ||
5020              BlackPawn <= pdown && pdown < EmptySquare &&
5021              BlackPawn <= pup && pup < EmptySquare
5022             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5023                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5024                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  )
5025         )           )
5026          return ImpossibleMove;
5027
5028     /* Check if the user is playing in turn.  This is complicated because we
5029        let the user "pick up" a piece before it is his turn.  So the piece he
5030        tried to pick up may have been captured by the time he puts it down!
5031        Therefore we use the color the user is supposed to be playing in this
5032        test, not the color of the piece that is currently on the starting
5033        square---except in EditGame mode, where the user is playing both
5034        sides; fortunately there the capture race can't happen.  (It can
5035        now happen in IcsExamining mode, but that's just too bad.  The user
5036        will get a somewhat confusing message in that case.)
5037        */
5038
5039     switch (gameMode) {
5040       case PlayFromGameFile:
5041       case AnalyzeFile:
5042       case TwoMachinesPlay:
5043       case EndOfGame:
5044       case IcsObserving:
5045       case IcsIdle:
5046         /* We switched into a game mode where moves are not accepted,
5047            perhaps while the mouse button was down. */
5048         return ImpossibleMove;
5049
5050       case MachinePlaysWhite:
5051         /* User is moving for Black */
5052         if (WhiteOnMove(currentMove)) {
5053             DisplayMoveError(_("It is White's turn"));
5054             return ImpossibleMove;
5055         }
5056         break;
5057
5058       case MachinePlaysBlack:
5059         /* User is moving for White */
5060         if (!WhiteOnMove(currentMove)) {
5061             DisplayMoveError(_("It is Black's turn"));
5062             return ImpossibleMove;
5063         }
5064         break;
5065
5066       case EditGame:
5067       case IcsExamining:
5068       case BeginningOfGame:
5069       case AnalyzeMode:
5070       case Training:
5071         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5072             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5073             /* User is moving for Black */
5074             if (WhiteOnMove(currentMove)) {
5075                 DisplayMoveError(_("It is White's turn"));
5076                 return ImpossibleMove;
5077             }
5078         } else {
5079             /* User is moving for White */
5080             if (!WhiteOnMove(currentMove)) {
5081                 DisplayMoveError(_("It is Black's turn"));
5082                 return ImpossibleMove;
5083             }
5084         }
5085         break;
5086
5087       case IcsPlayingBlack:
5088         /* User is moving for Black */
5089         if (WhiteOnMove(currentMove)) {
5090             if (!appData.premove) {
5091                 DisplayMoveError(_("It is White's turn"));
5092             } else if (toX >= 0 && toY >= 0) {
5093                 premoveToX = toX;
5094                 premoveToY = toY;
5095                 premoveFromX = fromX;
5096                 premoveFromY = fromY;
5097                 premovePromoChar = promoChar;
5098                 gotPremove = 1;
5099                 if (appData.debugMode)
5100                     fprintf(debugFP, "Got premove: fromX %d,"
5101                             "fromY %d, toX %d, toY %d\n",
5102                             fromX, fromY, toX, toY);
5103             }
5104             return ImpossibleMove;
5105         }
5106         break;
5107
5108       case IcsPlayingWhite:
5109         /* User is moving for White */
5110         if (!WhiteOnMove(currentMove)) {
5111             if (!appData.premove) {
5112                 DisplayMoveError(_("It is Black's turn"));
5113             } else if (toX >= 0 && toY >= 0) {
5114                 premoveToX = toX;
5115                 premoveToY = toY;
5116                 premoveFromX = fromX;
5117                 premoveFromY = fromY;
5118                 premovePromoChar = promoChar;
5119                 gotPremove = 1;
5120                 if (appData.debugMode)
5121                     fprintf(debugFP, "Got premove: fromX %d,"
5122                             "fromY %d, toX %d, toY %d\n",
5123                             fromX, fromY, toX, toY);
5124             }
5125             return ImpossibleMove;
5126         }
5127         break;
5128
5129       default:
5130         break;
5131
5132       case EditPosition:
5133         /* EditPosition, empty square, or different color piece;
5134            click-click move is possible */
5135         if (toX == -2 || toY == -2) {
5136             boards[0][fromY][fromX] = EmptySquare;
5137             return AmbiguousMove;
5138         } else if (toX >= 0 && toY >= 0) {
5139             boards[0][toY][toX] = boards[0][fromY][fromX];
5140             boards[0][fromY][fromX] = EmptySquare;
5141             return AmbiguousMove;
5142         }
5143         return ImpossibleMove;
5144     }
5145
5146     /* [HGM] If move started in holdings, it means a drop */
5147     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5148          if( pup != EmptySquare ) return ImpossibleMove;
5149          if(appData.testLegality) {
5150              /* it would be more logical if LegalityTest() also figured out
5151               * which drops are legal. For now we forbid pawns on back rank.
5152               * Shogi is on its own here...
5153               */
5154              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5155                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5156                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5157          }
5158          return WhiteDrop; /* Not needed to specify white or black yet */
5159     }
5160
5161     userOfferedDraw = FALSE;
5162
5163     /* [HGM] always test for legality, to get promotion info */
5164     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5165                           epStatus[currentMove], castlingRights[currentMove],
5166                                          fromY, fromX, toY, toX, promoChar);
5167
5168     /* [HGM] but possibly ignore an IllegalMove result */
5169     if (appData.testLegality) {
5170         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5171             DisplayMoveError(_("Illegal move"));
5172             return ImpossibleMove;
5173         }
5174     }
5175     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5176     return moveType;
5177     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5178        function is made into one that returns an OK move type if FinishMove
5179        should be called. This to give the calling driver routine the
5180        opportunity to finish the userMove input with a promotion popup,
5181        without bothering the user with this for invalid or illegal moves */
5182
5183 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5184 }
5185
5186 /* Common tail of UserMoveEvent and DropMenuEvent */
5187 int
5188 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5189      ChessMove moveType;
5190      int fromX, fromY, toX, toY;
5191      /*char*/int promoChar;
5192 {
5193   char *bookHit = 0;
5194
5195   if(appData.debugMode)
5196     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5197
5198   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5199     {
5200       // [HGM] superchess: suppress promotions to non-available piece
5201       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5202       if(WhiteOnMove(currentMove))
5203         {
5204           if(!boards[currentMove][k][BOARD_WIDTH-2])
5205             return 0;
5206         }
5207       else
5208         {
5209           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5210             return 0;
5211         }
5212     }
5213   
5214   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5215      move type in caller when we know the move is a legal promotion */
5216   if(moveType == NormalMove && promoChar)
5217     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5218
5219   if(appData.debugMode) 
5220     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5221
5222   /* [HGM] convert drag-and-drop piece drops to standard form */
5223   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5224     {
5225       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5226       if(appData.debugMode) 
5227         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5228                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5229       //         fromX = boards[currentMove][fromY][fromX];
5230       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5231       if(fromX == 0) 
5232         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5233
5234       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5235
5236       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5237         fromX++; 
5238
5239       fromY = DROP_RANK;
5240     }
5241
5242   /* [HGM] <popupFix> The following if has been moved here from
5243      UserMoveEvent(). Because it seemed to belon here (why not allow
5244      piece drops in training games?), and because it can only be
5245      performed after it is known to what we promote. */
5246   if (gameMode == Training)
5247     {
5248       /* compare the move played on the board to the next move in the
5249        * game. If they match, display the move and the opponent's response.
5250        * If they don't match, display an error message.
5251        */
5252       int saveAnimate;
5253       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5254       CopyBoard(testBoard, boards[currentMove]);
5255       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5256
5257       if (CompareBoards(testBoard, boards[currentMove+1]))
5258         {
5259           ForwardInner(currentMove+1);
5260
5261           /* Autoplay the opponent's response.
5262            * if appData.animate was TRUE when Training mode was entered,
5263            * the response will be animated.
5264            */
5265           saveAnimate = appData.animate;
5266           appData.animate = animateTraining;
5267           ForwardInner(currentMove+1);
5268           appData.animate = saveAnimate;
5269
5270           /* check for the end of the game */
5271           if (currentMove >= forwardMostMove)
5272             {
5273               gameMode = PlayFromGameFile;
5274               ModeHighlight();
5275               SetTrainingModeOff();
5276               DisplayInformation(_("End of game"));
5277             }
5278         }
5279       else
5280         {
5281           DisplayError(_("Incorrect move"), 0);
5282         }
5283       return 1;
5284     }
5285
5286   /* Ok, now we know that the move is good, so we can kill
5287      the previous line in Analysis Mode */
5288   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5289     {
5290       forwardMostMove = currentMove;
5291     }
5292
5293   /* If we need the chess program but it's dead, restart it */
5294   ResurrectChessProgram();
5295
5296   /* A user move restarts a paused game*/
5297   if (pausing)
5298     PauseEvent();
5299
5300   thinkOutput[0] = NULLCHAR;
5301
5302   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5303
5304   if (gameMode == BeginningOfGame)
5305     {
5306       if (appData.noChessProgram)
5307         {
5308           gameMode = EditGame;
5309           SetGameInfo();
5310         }
5311       else
5312         {
5313           char buf[MSG_SIZ];
5314           gameMode = MachinePlaysBlack;
5315           StartClocks();
5316           SetGameInfo();
5317           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5318           DisplayTitle(buf);
5319           if (first.sendName)
5320             {
5321               sprintf(buf, "name %s\n", gameInfo.white);
5322               SendToProgram(buf, &first);
5323             }
5324           StartClocks();
5325         }
5326       ModeHighlight();
5327     }
5328   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5329
5330   /* Relay move to ICS or chess engine */
5331   if (appData.icsActive)
5332     {
5333       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5334           gameMode == IcsExamining)
5335         {
5336           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5337           ics_user_moved = 1;
5338         }
5339     }
5340   else
5341     {
5342       if (first.sendTime && (gameMode == BeginningOfGame ||
5343                              gameMode == MachinePlaysWhite ||
5344                              gameMode == MachinePlaysBlack))
5345         {
5346           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5347         }
5348       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5349         {
5350           // [HGM] book: if program might be playing, let it use book
5351           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5352           first.maybeThinking = TRUE;
5353         }
5354       else
5355         SendMoveToProgram(forwardMostMove-1, &first);
5356       if (currentMove == cmailOldMove + 1)
5357         {
5358           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5359         }
5360     }
5361
5362   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5363
5364   switch (gameMode)
5365     {
5366     case EditGame:
5367       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5368                        EP_UNKNOWN, castlingRights[currentMove]) )
5369         {
5370         case MT_NONE:
5371         case MT_CHECK:
5372           break;
5373         case MT_CHECKMATE:
5374         case MT_STAINMATE:
5375           if (WhiteOnMove(currentMove))
5376             {
5377               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5378             }
5379           else
5380             {
5381               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5382             }
5383           break;
5384         case MT_STALEMATE:
5385           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5386           break;
5387     }
5388       break;
5389
5390     case MachinePlaysBlack:
5391     case MachinePlaysWhite:
5392       /* disable certain menu options while machine is thinking */
5393       SetMachineThinkingEnables();
5394       break;
5395
5396     default:
5397       break;
5398     }
5399
5400   if(bookHit)
5401     { // [HGM] book: simulate book reply
5402       static char bookMove[MSG_SIZ]; // a bit generous?
5403
5404       programStats.nodes = programStats.depth = programStats.time =
5405         programStats.score = programStats.got_only_move = 0;
5406       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5407
5408       strcpy(bookMove, "move ");
5409       strcat(bookMove, bookHit);
5410       HandleMachineMove(bookMove, &first);
5411     }
5412
5413   return 1;
5414 }
5415
5416 void
5417 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5418      int fromX, fromY, toX, toY;
5419      int promoChar;
5420 {
5421     /* [HGM] This routine was added to allow calling of its two logical
5422        parts from other modules in the old way. Before, UserMoveEvent()
5423        automatically called FinishMove() if the move was OK, and returned
5424        otherwise. I separated the two, in order to make it possible to
5425        slip a promotion popup in between. But that it always needs two
5426        calls, to the first part, (now called UserMoveTest() ), and to
5427        FinishMove if the first part succeeded. Calls that do not need
5428        to do anything in between, can call this routine the old way.
5429     */
5430   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5431   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5432   if(moveType != ImpossibleMove)
5433     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5434 }
5435
5436 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5437 {
5438 //    char * hint = lastHint;
5439     FrontEndProgramStats stats;
5440
5441     stats.which = cps == &first ? 0 : 1;
5442     stats.depth = cpstats->depth;
5443     stats.nodes = cpstats->nodes;
5444     stats.score = cpstats->score;
5445     stats.time = cpstats->time;
5446     stats.pv = cpstats->movelist;
5447     stats.hint = lastHint;
5448     stats.an_move_index = 0;
5449     stats.an_move_count = 0;
5450
5451     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5452         stats.hint = cpstats->move_name;
5453         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5454         stats.an_move_count = cpstats->nr_moves;
5455     }
5456
5457     SetProgramStats( &stats );
5458 }
5459
5460 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5461 {   // [HGM] book: this routine intercepts moves to simulate book replies
5462     char *bookHit = NULL;
5463
5464     //first determine if the incoming move brings opponent into his book
5465     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5466         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5467     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5468     if(bookHit != NULL && !cps->bookSuspend) {
5469         // make sure opponent is not going to reply after receiving move to book position
5470         SendToProgram("force\n", cps);
5471         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5472     }
5473     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5474     // now arrange restart after book miss
5475     if(bookHit) {
5476         // after a book hit we never send 'go', and the code after the call to this routine
5477         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5478         char buf[MSG_SIZ];
5479         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5480         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5481         SendToProgram(buf, cps);
5482         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5483     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5484         SendToProgram("go\n", cps);
5485         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5486     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5487         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5488             SendToProgram("go\n", cps);
5489         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5490     }
5491     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5492 }
5493
5494 char *savedMessage;
5495 ChessProgramState *savedState;
5496 void DeferredBookMove(void)
5497 {
5498         if(savedState->lastPing != savedState->lastPong)
5499                     ScheduleDelayedEvent(DeferredBookMove, 10);
5500         else
5501         HandleMachineMove(savedMessage, savedState);
5502 }
5503
5504 void
5505 HandleMachineMove(message, cps)
5506      char *message;
5507      ChessProgramState *cps;
5508 {
5509     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5510     char realname[MSG_SIZ];
5511     int fromX, fromY, toX, toY;
5512     ChessMove moveType;
5513     char promoChar;
5514     char *p;
5515     int machineWhite;
5516     char *bookHit;
5517
5518 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5519     /*
5520      * Kludge to ignore BEL characters
5521      */
5522     while (*message == '\007') message++;
5523
5524     /*
5525      * [HGM] engine debug message: ignore lines starting with '#' character
5526      */
5527     if(cps->debug && *message == '#') return;
5528
5529     /*
5530      * Look for book output
5531      */
5532     if (cps == &first && bookRequested) {
5533         if (message[0] == '\t' || message[0] == ' ') {
5534             /* Part of the book output is here; append it */
5535             strcat(bookOutput, message);
5536             strcat(bookOutput, "  \n");
5537             return;
5538         } else if (bookOutput[0] != NULLCHAR) {
5539             /* All of book output has arrived; display it */
5540             char *p = bookOutput;
5541             while (*p != NULLCHAR) {
5542                 if (*p == '\t') *p = ' ';
5543                 p++;
5544             }
5545             DisplayInformation(bookOutput);
5546             bookRequested = FALSE;
5547             /* Fall through to parse the current output */
5548         }
5549     }
5550
5551     /*
5552      * Look for machine move.
5553      */
5554     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5555         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5556     {
5557         /* This method is only useful on engines that support ping */
5558         if (cps->lastPing != cps->lastPong) {
5559           if (gameMode == BeginningOfGame) {
5560             /* Extra move from before last new; ignore */
5561             if (appData.debugMode) {
5562                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5563             }
5564           } else {
5565             if (appData.debugMode) {
5566                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5567                         cps->which, gameMode);
5568             }
5569
5570             SendToProgram("undo\n", cps);
5571           }
5572           return;
5573         }
5574
5575         switch (gameMode) {
5576           case BeginningOfGame:
5577             /* Extra move from before last reset; ignore */
5578             if (appData.debugMode) {
5579                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5580             }
5581             return;
5582
5583           case EndOfGame:
5584           case IcsIdle:
5585           default:
5586             /* Extra move after we tried to stop.  The mode test is
5587                not a reliable way of detecting this problem, but it's
5588                the best we can do on engines that don't support ping.
5589             */
5590             if (appData.debugMode) {
5591                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5592                         cps->which, gameMode);
5593             }
5594             SendToProgram("undo\n", cps);
5595             return;
5596
5597           case MachinePlaysWhite:
5598           case IcsPlayingWhite:
5599             machineWhite = TRUE;
5600             break;
5601
5602           case MachinePlaysBlack:
5603           case IcsPlayingBlack:
5604             machineWhite = FALSE;
5605             break;
5606
5607           case TwoMachinesPlay:
5608             machineWhite = (cps->twoMachinesColor[0] == 'w');
5609             break;
5610         }
5611         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5612             if (appData.debugMode) {
5613                 fprintf(debugFP,
5614                         "Ignoring move out of turn by %s, gameMode %d"
5615                         ", forwardMost %d\n",
5616                         cps->which, gameMode, forwardMostMove);
5617             }
5618             return;
5619         }
5620
5621     if (appData.debugMode) { int f = forwardMostMove;
5622         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5623                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5624     }
5625         if(cps->alphaRank) AlphaRank(machineMove, 4);
5626         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5627                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5628             /* Machine move could not be parsed; ignore it. */
5629             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5630                     machineMove, cps->which);
5631             DisplayError(buf1, 0);
5632             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5633                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5634             if (gameMode == TwoMachinesPlay) {
5635               GameEnds(machineWhite ? BlackWins : WhiteWins,
5636                        buf1, GE_XBOARD);
5637             }
5638             return;
5639         }
5640
5641         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5642         /* So we have to redo legality test with true e.p. status here,  */
5643         /* to make sure an illegal e.p. capture does not slip through,   */
5644         /* to cause a forfeit on a justified illegal-move complaint      */
5645         /* of the opponent.                                              */
5646         if( gameMode==TwoMachinesPlay && appData.testLegality
5647             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5648                                                               ) {
5649            ChessMove moveType;
5650            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5651                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5652                              fromY, fromX, toY, toX, promoChar);
5653             if (appData.debugMode) {
5654                 int i;
5655                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5656                     castlingRights[forwardMostMove][i], castlingRank[i]);
5657                 fprintf(debugFP, "castling rights\n");
5658             }
5659             if(moveType == IllegalMove) {
5660                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5661                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5662                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5663                            buf1, GE_XBOARD);
5664                 return;
5665            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5666            /* [HGM] Kludge to handle engines that send FRC-style castling
5667               when they shouldn't (like TSCP-Gothic) */
5668            switch(moveType) {
5669              case WhiteASideCastleFR:
5670              case BlackASideCastleFR:
5671                toX+=2;
5672                currentMoveString[2]++;
5673                break;
5674              case WhiteHSideCastleFR:
5675              case BlackHSideCastleFR:
5676                toX--;
5677                currentMoveString[2]--;
5678                break;
5679              default: ; // nothing to do, but suppresses warning of pedantic compilers
5680            }
5681         }
5682         hintRequested = FALSE;
5683         lastHint[0] = NULLCHAR;
5684         bookRequested = FALSE;
5685         /* Program may be pondering now */
5686         cps->maybeThinking = TRUE;
5687         if (cps->sendTime == 2) cps->sendTime = 1;
5688         if (cps->offeredDraw) cps->offeredDraw--;
5689
5690 #if ZIPPY
5691         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5692             first.initDone) {
5693           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5694           ics_user_moved = 1;
5695           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5696                 char buf[3*MSG_SIZ];
5697
5698                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5699                         programStats.score / 100.,
5700                         programStats.depth,
5701                         programStats.time / 100.,
5702                         (unsigned int)programStats.nodes,
5703                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5704                         programStats.movelist);
5705                 SendToICS(buf);
5706           }
5707         }
5708 #endif
5709         /* currentMoveString is set as a side-effect of ParseOneMove */
5710         strcpy(machineMove, currentMoveString);
5711         strcat(machineMove, "\n");
5712         strcpy(moveList[forwardMostMove], machineMove);
5713
5714         /* [AS] Save move info and clear stats for next move */
5715         pvInfoList[ forwardMostMove ].score = programStats.score;
5716         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5717         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5718         ClearProgramStats();
5719         thinkOutput[0] = NULLCHAR;
5720         hiddenThinkOutputState = 0;
5721
5722         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5723
5724         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5725         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5726             int count = 0;
5727
5728             while( count < adjudicateLossPlies ) {
5729                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5730
5731                 if( count & 1 ) {
5732                     score = -score; /* Flip score for winning side */
5733                 }
5734
5735                 if( score > adjudicateLossThreshold ) {
5736                     break;
5737                 }
5738
5739                 count++;
5740             }
5741
5742             if( count >= adjudicateLossPlies ) {
5743                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5744
5745                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5746                     "Xboard adjudication",
5747                     GE_XBOARD );
5748
5749                 return;
5750             }
5751         }
5752
5753         if( gameMode == TwoMachinesPlay ) {
5754           // [HGM] some adjudications useful with buggy engines
5755             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5756           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5757
5758
5759             if( appData.testLegality )
5760             {   /* [HGM] Some more adjudications for obstinate engines */
5761                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5762                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5763                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5764                 static int moveCount = 6;
5765                 ChessMove result;
5766                 char *reason = NULL;
5767
5768                 /* Count what is on board. */
5769                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5770                 {   ChessSquare p = boards[forwardMostMove][i][j];
5771                     int m=i;
5772
5773                     switch((int) p)
5774                     {   /* count B,N,R and other of each side */
5775                         case WhiteKing:
5776                         case BlackKing:
5777                              NrK++; break; // [HGM] atomic: count Kings
5778                         case WhiteKnight:
5779                              NrWN++; break;
5780                         case WhiteBishop:
5781                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5782                              bishopsColor |= 1 << ((i^j)&1);
5783                              NrWB++; break;
5784                         case BlackKnight:
5785                              NrBN++; break;
5786                         case BlackBishop:
5787                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5788                              bishopsColor |= 1 << ((i^j)&1);
5789                              NrBB++; break;
5790                         case WhiteRook:
5791                              NrWR++; break;
5792                         case BlackRook:
5793                              NrBR++; break;
5794                         case WhiteQueen:
5795                              NrWQ++; break;
5796                         case BlackQueen:
5797                              NrBQ++; break;
5798                         case EmptySquare:
5799                              break;
5800                         case BlackPawn:
5801                              m = 7-i;
5802                         case WhitePawn:
5803                              PawnAdvance += m; NrPawns++;
5804                     }
5805                     NrPieces += (p != EmptySquare);
5806                     NrW += ((int)p < (int)BlackPawn);
5807                     if(gameInfo.variant == VariantXiangqi &&
5808                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5809                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5810                         NrW -= ((int)p < (int)BlackPawn);
5811                     }
5812                 }
5813
5814                 /* Some material-based adjudications that have to be made before stalemate test */
5815                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5816                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5817                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5818                      if(appData.checkMates) {
5819                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5820                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5821                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5822                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5823                          return;
5824                      }
5825                 }
5826
5827                 /* Bare King in Shatranj (loses) or Losers (wins) */
5828                 if( NrW == 1 || NrPieces - NrW == 1) {
5829                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5830                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5831                      if(appData.checkMates) {
5832                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5833                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5834                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5835                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5836                          return;
5837                      }
5838                   } else
5839                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5840                   {    /* bare King */
5841                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5842                         if(appData.checkMates) {
5843                             /* but only adjudicate if adjudication enabled */
5844                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5845                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5847                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5848                             return;
5849                         }
5850                   }
5851                 } else bare = 1;
5852
5853
5854             // don't wait for engine to announce game end if we can judge ourselves
5855             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5856                                        castlingRights[forwardMostMove]) ) {
5857               case MT_CHECK:
5858                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5859                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5860                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5861                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5862                             checkCnt++;
5863                         if(checkCnt >= 2) {
5864                             reason = "Xboard adjudication: 3rd check";
5865                             epStatus[forwardMostMove] = EP_CHECKMATE;
5866                             break;
5867                         }
5868                     }
5869                 }
5870               case MT_NONE:
5871               default:
5872                 break;
5873               case MT_STALEMATE:
5874               case MT_STAINMATE:
5875                 reason = "Xboard adjudication: Stalemate";
5876                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5877                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5878                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5879                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5880                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5881                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5882                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5883                                                                         EP_CHECKMATE : EP_WINS);
5884                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5885                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5886                 }
5887                 break;
5888               case MT_CHECKMATE:
5889                 reason = "Xboard adjudication: Checkmate";
5890                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5891                 break;
5892             }
5893
5894                 switch(i = epStatus[forwardMostMove]) {
5895                     case EP_STALEMATE:
5896                         result = GameIsDrawn; break;
5897                     case EP_CHECKMATE:
5898                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5899                     case EP_WINS:
5900                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5901                     default:
5902                         result = (ChessMove) 0;
5903                 }
5904                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5905                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5906                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5907                     GameEnds( result, reason, GE_XBOARD );
5908                     return;
5909                 }
5910
5911                 /* Next absolutely insufficient mating material. */
5912                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5913                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5914                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5915                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5916                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5917
5918                      /* always flag draws, for judging claims */
5919                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5920
5921                      if(appData.materialDraws) {
5922                          /* but only adjudicate them if adjudication enabled */
5923                          SendToProgram("force\n", cps->other); // suppress reply
5924                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5925                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5926                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5927                          return;
5928                      }
5929                 }
5930
5931                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5932                 if(NrPieces == 4 &&
5933                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5934                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5935                    || NrWN==2 || NrBN==2     /* KNNK */
5936                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5937                   ) ) {
5938                      if(--moveCount < 0 && appData.trivialDraws)
5939                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5940                           SendToProgram("force\n", cps->other); // suppress reply
5941                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5942                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5943                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5944                           return;
5945                      }
5946                 } else moveCount = 6;
5947             }
5948           }
5949 #if 1
5950     if (appData.debugMode) { int i;
5951       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5952               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5953               appData.drawRepeats);
5954       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5955            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5956
5957     }
5958 #endif
5959                 /* Check for rep-draws */
5960                 count = 0;
5961                 for(k = forwardMostMove-2;
5962                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5963                         epStatus[k] < EP_UNKNOWN &&
5964                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5965                     k-=2)
5966                 {   int rights=0;
5967 #if 0
5968     if (appData.debugMode) {
5969       fprintf(debugFP, " loop\n");
5970     }
5971 #endif
5972                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5973 #if 0
5974     if (appData.debugMode) {
5975       fprintf(debugFP, "match\n");
5976     }
5977 #endif
5978                         /* compare castling rights */
5979                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5980                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5981                                 rights++; /* King lost rights, while rook still had them */
5982                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5983                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5984                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5985                                    rights++; /* but at least one rook lost them */
5986                         }
5987                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5988                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5989                                 rights++;
5990                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5991                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5992                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5993                                    rights++;
5994                         }
5995 #if 0
5996     if (appData.debugMode) {
5997       for(i=0; i<nrCastlingRights; i++)
5998       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5999     }
6000
6001     if (appData.debugMode) {
6002       fprintf(debugFP, " %d %d\n", rights, k);
6003     }
6004 #endif
6005                         if( rights == 0 && ++count > appData.drawRepeats-2
6006                             && appData.drawRepeats > 1) {
6007                              /* adjudicate after user-specified nr of repeats */
6008                              SendToProgram("force\n", cps->other); // suppress reply
6009                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6010                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6011                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6012                                 // [HGM] xiangqi: check for forbidden perpetuals
6013                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6014                                 for(m=forwardMostMove; m>k; m-=2) {
6015                                     if(MateTest(boards[m], PosFlags(m),
6016                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6017                                         ourPerpetual = 0; // the current mover did not always check
6018                                     if(MateTest(boards[m-1], PosFlags(m-1),
6019                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6020                                         hisPerpetual = 0; // the opponent did not always check
6021                                 }
6022                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6023                                                                         ourPerpetual, hisPerpetual);
6024                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6025                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6026                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6027                                     return;
6028                                 }
6029                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6030                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6031                                 // Now check for perpetual chases
6032                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6033                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6034                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6035                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6036                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6037                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6038                                         return;
6039                                     }
6040                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6041                                         break; // Abort repetition-checking loop.
6042                                 }
6043                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6044                              }
6045                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6046                              return;
6047                         }
6048                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6049                              epStatus[forwardMostMove] = EP_REP_DRAW;
6050                     }
6051                 }
6052
6053                 /* Now we test for 50-move draws. Determine ply count */
6054                 count = forwardMostMove;
6055                 /* look for last irreversble move */
6056                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6057                     count--;
6058                 /* if we hit starting position, add initial plies */
6059                 if( count == backwardMostMove )
6060                     count -= initialRulePlies;
6061                 count = forwardMostMove - count;
6062                 if( count >= 100)
6063                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6064                          /* this is used to judge if draw claims are legal */
6065                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6066                          SendToProgram("force\n", cps->other); // suppress reply
6067                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6068                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6070                          return;
6071                 }
6072
6073                 /* if draw offer is pending, treat it as a draw claim
6074                  * when draw condition present, to allow engines a way to
6075                  * claim draws before making their move to avoid a race
6076                  * condition occurring after their move
6077                  */
6078                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6079                          char *p = NULL;
6080                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6081                              p = "Draw claim: 50-move rule";
6082                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6083                              p = "Draw claim: 3-fold repetition";
6084                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6085                              p = "Draw claim: insufficient mating material";
6086                          if( p != NULL ) {
6087                              SendToProgram("force\n", cps->other); // suppress reply
6088                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6089                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6090                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6091                              return;
6092                          }
6093                 }
6094
6095
6096                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6097                     SendToProgram("force\n", cps->other); // suppress reply
6098                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6099                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6100
6101                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6102
6103                     return;
6104                 }
6105         }
6106
6107         bookHit = NULL;
6108         if (gameMode == TwoMachinesPlay) {
6109             /* [HGM] relaying draw offers moved to after reception of move */
6110             /* and interpreting offer as claim if it brings draw condition */
6111             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6112                 SendToProgram("draw\n", cps->other);
6113             }
6114             if (cps->other->sendTime) {
6115                 SendTimeRemaining(cps->other,
6116                                   cps->other->twoMachinesColor[0] == 'w');
6117             }
6118             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6119             if (firstMove && !bookHit) {
6120                 firstMove = FALSE;
6121                 if (cps->other->useColors) {
6122                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6123                 }
6124                 SendToProgram("go\n", cps->other);
6125             }
6126             cps->other->maybeThinking = TRUE;
6127         }
6128
6129         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6130
6131         if (!pausing && appData.ringBellAfterMoves) {
6132             RingBell();
6133         }
6134
6135         /*
6136          * Reenable menu items that were disabled while
6137          * machine was thinking
6138          */
6139         if (gameMode != TwoMachinesPlay)
6140             SetUserThinkingEnables();
6141
6142         // [HGM] book: after book hit opponent has received move and is now in force mode
6143         // force the book reply into it, and then fake that it outputted this move by jumping
6144         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6145         if(bookHit) {
6146                 static char bookMove[MSG_SIZ]; // a bit generous?
6147
6148                 strcpy(bookMove, "move ");
6149                 strcat(bookMove, bookHit);
6150                 message = bookMove;
6151                 cps = cps->other;
6152                 programStats.nodes = programStats.depth = programStats.time =
6153                 programStats.score = programStats.got_only_move = 0;
6154                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6155
6156                 if(cps->lastPing != cps->lastPong) {
6157                     savedMessage = message; // args for deferred call
6158                     savedState = cps;
6159                     ScheduleDelayedEvent(DeferredBookMove, 10);
6160                     return;
6161                 }
6162                 goto FakeBookMove;
6163         }
6164
6165         return;
6166     }
6167
6168     /* Set special modes for chess engines.  Later something general
6169      *  could be added here; for now there is just one kludge feature,
6170      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6171      *  when "xboard" is given as an interactive command.
6172      */
6173     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6174         cps->useSigint = FALSE;
6175         cps->useSigterm = FALSE;
6176     }
6177     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6178       ParseFeatures(message+8, cps);
6179       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6180     }
6181
6182     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6183      * want this, I was asked to put it in, and obliged.
6184      */
6185     if (!strncmp(message, "setboard ", 9)) {
6186         Board initial_position; int i;
6187
6188         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6189
6190         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6191             DisplayError(_("Bad FEN received from engine"), 0);
6192             return ;
6193         } else {
6194            Reset(FALSE, FALSE);
6195            CopyBoard(boards[0], initial_position);
6196            initialRulePlies = FENrulePlies;
6197            epStatus[0] = FENepStatus;
6198            for( i=0; i<nrCastlingRights; i++ )
6199                 castlingRights[0][i] = FENcastlingRights[i];
6200            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6201            else gameMode = MachinePlaysBlack;
6202            DrawPosition(FALSE, boards[currentMove]);
6203         }
6204         return;
6205     }
6206
6207     /*
6208      * Look for communication commands
6209      */
6210     if (!strncmp(message, "telluser ", 9)) {
6211         DisplayNote(message + 9);
6212         return;
6213     }
6214     if (!strncmp(message, "tellusererror ", 14)) {
6215         DisplayError(message + 14, 0);
6216         return;
6217     }
6218     if (!strncmp(message, "tellopponent ", 13)) {
6219       if (appData.icsActive) {
6220         if (loggedOn) {
6221           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6222           SendToICS(buf1);
6223         }
6224       } else {
6225         DisplayNote(message + 13);
6226       }
6227       return;
6228     }
6229     if (!strncmp(message, "tellothers ", 11)) {
6230       if (appData.icsActive) {
6231         if (loggedOn) {
6232           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6233           SendToICS(buf1);
6234         }
6235       }
6236       return;
6237     }
6238     if (!strncmp(message, "tellall ", 8)) {
6239       if (appData.icsActive) {
6240         if (loggedOn) {
6241           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6242           SendToICS(buf1);
6243         }
6244       } else {
6245         DisplayNote(message + 8);
6246       }
6247       return;
6248     }
6249     if (strncmp(message, "warning", 7) == 0) {
6250         /* Undocumented feature, use tellusererror in new code */
6251         DisplayError(message, 0);
6252         return;
6253     }
6254     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6255         strcpy(realname, cps->tidy);
6256         strcat(realname, " query");
6257         AskQuestion(realname, buf2, buf1, cps->pr);
6258         return;
6259     }
6260     /* Commands from the engine directly to ICS.  We don't allow these to be
6261      *  sent until we are logged on. Crafty kibitzes have been known to
6262      *  interfere with the login process.
6263      */
6264     if (loggedOn) {
6265         if (!strncmp(message, "tellics ", 8)) {
6266             SendToICS(message + 8);
6267             SendToICS("\n");
6268             return;
6269         }
6270         if (!strncmp(message, "tellicsnoalias ", 15)) {
6271             SendToICS(ics_prefix);
6272             SendToICS(message + 15);
6273             SendToICS("\n");
6274             return;
6275         }
6276         /* The following are for backward compatibility only */
6277         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6278             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6279             SendToICS(ics_prefix);
6280             SendToICS(message);
6281             SendToICS("\n");
6282             return;
6283         }
6284     }
6285     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6286         return;
6287     }
6288     /*
6289      * If the move is illegal, cancel it and redraw the board.
6290      * Also deal with other error cases.  Matching is rather loose
6291      * here to accommodate engines written before the spec.
6292      */
6293     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6294         strncmp(message, "Error", 5) == 0) {
6295         if (StrStr(message, "name") ||
6296             StrStr(message, "rating") || StrStr(message, "?") ||
6297             StrStr(message, "result") || StrStr(message, "board") ||
6298             StrStr(message, "bk") || StrStr(message, "computer") ||
6299             StrStr(message, "variant") || StrStr(message, "hint") ||
6300             StrStr(message, "random") || StrStr(message, "depth") ||
6301             StrStr(message, "accepted")) {
6302             return;
6303         }
6304         if (StrStr(message, "protover")) {
6305           /* Program is responding to input, so it's apparently done
6306              initializing, and this error message indicates it is
6307              protocol version 1.  So we don't need to wait any longer
6308              for it to initialize and send feature commands. */
6309           FeatureDone(cps, 1);
6310           cps->protocolVersion = 1;
6311           return;
6312         }
6313         cps->maybeThinking = FALSE;
6314
6315         if (StrStr(message, "draw")) {
6316             /* Program doesn't have "draw" command */
6317             cps->sendDrawOffers = 0;
6318             return;
6319         }
6320         if (cps->sendTime != 1 &&
6321             (StrStr(message, "time") || StrStr(message, "otim"))) {
6322           /* Program apparently doesn't have "time" or "otim" command */
6323           cps->sendTime = 0;
6324           return;
6325         }
6326         if (StrStr(message, "analyze")) {
6327             cps->analysisSupport = FALSE;
6328             cps->analyzing = FALSE;
6329             Reset(FALSE, TRUE);
6330             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6331             DisplayError(buf2, 0);
6332             return;
6333         }
6334         if (StrStr(message, "(no matching move)st")) {
6335           /* Special kludge for GNU Chess 4 only */
6336           cps->stKludge = TRUE;
6337           SendTimeControl(cps, movesPerSession, timeControl,
6338                           timeIncrement, appData.searchDepth,
6339                           searchTime);
6340           return;
6341         }
6342         if (StrStr(message, "(no matching move)sd")) {
6343           /* Special kludge for GNU Chess 4 only */
6344           cps->sdKludge = TRUE;
6345           SendTimeControl(cps, movesPerSession, timeControl,
6346                           timeIncrement, appData.searchDepth,
6347                           searchTime);
6348           return;
6349         }
6350         if (!StrStr(message, "llegal")) {
6351             return;
6352         }
6353         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6354             gameMode == IcsIdle) return;
6355         if (forwardMostMove <= backwardMostMove) return;
6356 #if 0
6357         /* Following removed: it caused a bug where a real illegal move
6358            message in analyze mored would be ignored. */
6359         if (cps == &first && programStats.ok_to_send == 0) {
6360             /* Bogus message from Crafty responding to "."  This filtering
6361                can miss some of the bad messages, but fortunately the bug
6362                is fixed in current Crafty versions, so it doesn't matter. */
6363             return;
6364         }
6365 #endif
6366         if (pausing) PauseEvent();
6367         if (gameMode == PlayFromGameFile) {
6368             /* Stop reading this game file */
6369             gameMode = EditGame;
6370             ModeHighlight();
6371         }
6372         currentMove = --forwardMostMove;
6373         DisplayMove(currentMove-1); /* before DisplayMoveError */
6374         SwitchClocks();
6375         DisplayBothClocks();
6376         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6377                 parseList[currentMove], cps->which);
6378         DisplayMoveError(buf1);
6379         DrawPosition(FALSE, boards[currentMove]);
6380
6381         /* [HGM] illegal-move claim should forfeit game when Xboard */
6382         /* only passes fully legal moves                            */
6383         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6384             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6385                                 "False illegal-move claim", GE_XBOARD );
6386         }
6387         return;
6388     }
6389     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6390         /* Program has a broken "time" command that
6391            outputs a string not ending in newline.
6392            Don't use it. */
6393         cps->sendTime = 0;
6394     }
6395
6396     /*
6397      * If chess program startup fails, exit with an error message.
6398      * Attempts to recover here are futile.
6399      */
6400     if ((StrStr(message, "unknown host") != NULL)
6401         || (StrStr(message, "No remote directory") != NULL)
6402         || (StrStr(message, "not found") != NULL)
6403         || (StrStr(message, "No such file") != NULL)
6404         || (StrStr(message, "can't alloc") != NULL)
6405         || (StrStr(message, "Permission denied") != NULL)) {
6406
6407         cps->maybeThinking = FALSE;
6408         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6409                 cps->which, cps->program, cps->host, message);
6410         RemoveInputSource(cps->isr);
6411         DisplayFatalError(buf1, 0, 1);
6412         return;
6413     }
6414
6415     /*
6416      * Look for hint output
6417      */
6418     if (sscanf(message, "Hint: %s", buf1) == 1) {
6419         if (cps == &first && hintRequested) {
6420             hintRequested = FALSE;
6421             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6422                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6423                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6424                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6425                                     fromY, fromX, toY, toX, promoChar, buf1);
6426                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6427                 DisplayInformation(buf2);
6428             } else {
6429                 /* Hint move could not be parsed!? */
6430               snprintf(buf2, sizeof(buf2),
6431                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6432                         buf1, cps->which);
6433                 DisplayError(buf2, 0);
6434             }
6435         } else {
6436             strcpy(lastHint, buf1);
6437         }
6438         return;
6439     }
6440
6441     /*
6442      * Ignore other messages if game is not in progress
6443      */
6444     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6445         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6446
6447     /*
6448      * look for win, lose, draw, or draw offer
6449      */
6450     if (strncmp(message, "1-0", 3) == 0) {
6451         char *p, *q, *r = "";
6452         p = strchr(message, '{');
6453         if (p) {
6454             q = strchr(p, '}');
6455             if (q) {
6456                 *q = NULLCHAR;
6457                 r = p + 1;
6458             }
6459         }
6460         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6461         return;
6462     } else if (strncmp(message, "0-1", 3) == 0) {
6463         char *p, *q, *r = "";
6464         p = strchr(message, '{');
6465         if (p) {
6466             q = strchr(p, '}');
6467             if (q) {
6468                 *q = NULLCHAR;
6469                 r = p + 1;
6470             }
6471         }
6472         /* Kludge for Arasan 4.1 bug */
6473         if (strcmp(r, "Black resigns") == 0) {
6474             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6475             return;
6476         }
6477         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6478         return;
6479     } else if (strncmp(message, "1/2", 3) == 0) {
6480         char *p, *q, *r = "";
6481         p = strchr(message, '{');
6482         if (p) {
6483             q = strchr(p, '}');
6484             if (q) {
6485                 *q = NULLCHAR;
6486                 r = p + 1;
6487             }
6488         }
6489
6490         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6491         return;
6492
6493     } else if (strncmp(message, "White resign", 12) == 0) {
6494         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6495         return;
6496     } else if (strncmp(message, "Black resign", 12) == 0) {
6497         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6498         return;
6499     } else if (strncmp(message, "White matches", 13) == 0 ||
6500                strncmp(message, "Black matches", 13) == 0   ) {
6501         /* [HGM] ignore GNUShogi noises */
6502         return;
6503     } else if (strncmp(message, "White", 5) == 0 &&
6504                message[5] != '(' &&
6505                StrStr(message, "Black") == NULL) {
6506         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6507         return;
6508     } else if (strncmp(message, "Black", 5) == 0 &&
6509                message[5] != '(') {
6510         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6511         return;
6512     } else if (strcmp(message, "resign") == 0 ||
6513                strcmp(message, "computer resigns") == 0) {
6514         switch (gameMode) {
6515           case MachinePlaysBlack:
6516           case IcsPlayingBlack:
6517             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6518             break;
6519           case MachinePlaysWhite:
6520           case IcsPlayingWhite:
6521             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6522             break;
6523           case TwoMachinesPlay:
6524             if (cps->twoMachinesColor[0] == 'w')
6525               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6526             else
6527               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6528             break;
6529           default:
6530             /* can't happen */
6531             break;
6532         }
6533         return;
6534     } else if (strncmp(message, "opponent mates", 14) == 0) {
6535         switch (gameMode) {
6536           case MachinePlaysBlack:
6537           case IcsPlayingBlack:
6538             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6539             break;
6540           case MachinePlaysWhite:
6541           case IcsPlayingWhite:
6542             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6543             break;
6544           case TwoMachinesPlay:
6545             if (cps->twoMachinesColor[0] == 'w')
6546               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6547             else
6548               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6549             break;
6550           default:
6551             /* can't happen */
6552             break;
6553         }
6554         return;
6555     } else if (strncmp(message, "computer mates", 14) == 0) {
6556         switch (gameMode) {
6557           case MachinePlaysBlack:
6558           case IcsPlayingBlack:
6559             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6560             break;
6561           case MachinePlaysWhite:
6562           case IcsPlayingWhite:
6563             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6564             break;
6565           case TwoMachinesPlay:
6566             if (cps->twoMachinesColor[0] == 'w')
6567               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6568             else
6569               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6570             break;
6571           default:
6572             /* can't happen */
6573             break;
6574         }
6575         return;
6576     } else if (strncmp(message, "checkmate", 9) == 0) {
6577         if (WhiteOnMove(forwardMostMove)) {
6578             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6579         } else {
6580             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581         }
6582         return;
6583     } else if (strstr(message, "Draw") != NULL ||
6584                strstr(message, "game is a draw") != NULL) {
6585         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6586         return;
6587     } else if (strstr(message, "offer") != NULL &&
6588                strstr(message, "draw") != NULL) {
6589 #if ZIPPY
6590         if (appData.zippyPlay && first.initDone) {
6591             /* Relay offer to ICS */
6592             SendToICS(ics_prefix);
6593             SendToICS("draw\n");
6594         }
6595 #endif
6596         cps->offeredDraw = 2; /* valid until this engine moves twice */
6597         if (gameMode == TwoMachinesPlay) {
6598             if (cps->other->offeredDraw) {
6599                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6600             /* [HGM] in two-machine mode we delay relaying draw offer      */
6601             /* until after we also have move, to see if it is really claim */
6602             }
6603 #if 0
6604               else {
6605                 if (cps->other->sendDrawOffers) {
6606                     SendToProgram("draw\n", cps->other);
6607                 }
6608             }
6609 #endif
6610         } else if (gameMode == MachinePlaysWhite ||
6611                    gameMode == MachinePlaysBlack) {
6612           if (userOfferedDraw) {
6613             DisplayInformation(_("Machine accepts your draw offer"));
6614             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6615           } else {
6616             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6617           }
6618         }
6619     }
6620
6621
6622     /*
6623      * Look for thinking output
6624      */
6625     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6626           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6627                                 ) {
6628         int plylev, mvleft, mvtot, curscore, time;
6629         char mvname[MOVE_LEN];
6630         u64 nodes; // [DM]
6631         char plyext;
6632         int ignore = FALSE;
6633         int prefixHint = FALSE;
6634         mvname[0] = NULLCHAR;
6635
6636         switch (gameMode) {
6637           case MachinePlaysBlack:
6638           case IcsPlayingBlack:
6639             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6640             break;
6641           case MachinePlaysWhite:
6642           case IcsPlayingWhite:
6643             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6644             break;
6645           case AnalyzeMode:
6646           case AnalyzeFile:
6647             break;
6648           case IcsObserving: /* [DM] icsEngineAnalyze */
6649             if (!appData.icsEngineAnalyze) ignore = TRUE;
6650             break;
6651           case TwoMachinesPlay:
6652             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6653                 ignore = TRUE;
6654             }
6655             break;
6656           default:
6657             ignore = TRUE;
6658             break;
6659         }
6660
6661         if (!ignore) {
6662             buf1[0] = NULLCHAR;
6663             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6664                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6665
6666                 if (plyext != ' ' && plyext != '\t') {
6667                     time *= 100;
6668                 }
6669
6670                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6671                 if( cps->scoreIsAbsolute &&
6672                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6673                 {
6674                     curscore = -curscore;
6675                 }
6676
6677
6678                 programStats.depth = plylev;
6679                 programStats.nodes = nodes;
6680                 programStats.time = time;
6681                 programStats.score = curscore;
6682                 programStats.got_only_move = 0;
6683
6684                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6685                         int ticklen;
6686
6687                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6688                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6689                         if(WhiteOnMove(forwardMostMove))
6690                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6691                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6692                 }
6693
6694                 /* Buffer overflow protection */
6695                 if (buf1[0] != NULLCHAR) {
6696                     if (strlen(buf1) >= sizeof(programStats.movelist)
6697                         && appData.debugMode) {
6698                         fprintf(debugFP,
6699                                 "PV is too long; using the first %d bytes.\n",
6700                                 sizeof(programStats.movelist) - 1);
6701                     }
6702
6703                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6704                 } else {
6705                     sprintf(programStats.movelist, " no PV\n");
6706                 }
6707
6708                 if (programStats.seen_stat) {
6709                     programStats.ok_to_send = 1;
6710                 }
6711
6712                 if (strchr(programStats.movelist, '(') != NULL) {
6713                     programStats.line_is_book = 1;
6714                     programStats.nr_moves = 0;
6715                     programStats.moves_left = 0;
6716                 } else {
6717                     programStats.line_is_book = 0;
6718                 }
6719
6720                 SendProgramStatsToFrontend( cps, &programStats );
6721
6722                 /*
6723                     [AS] Protect the thinkOutput buffer from overflow... this
6724                     is only useful if buf1 hasn't overflowed first!
6725                 */
6726                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6727                         plylev,
6728                         (gameMode == TwoMachinesPlay ?
6729                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6730                         ((double) curscore) / 100.0,
6731                         prefixHint ? lastHint : "",
6732                         prefixHint ? " " : "" );
6733
6734                 if( buf1[0] != NULLCHAR ) {
6735                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6736
6737                     if( strlen(buf1) > max_len ) {
6738                         if( appData.debugMode) {
6739                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6740                         }
6741                         buf1[max_len+1] = '\0';
6742                     }
6743
6744                     strcat( thinkOutput, buf1 );
6745                 }
6746
6747                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6748                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6749                     DisplayMove(currentMove - 1);
6750                     DisplayAnalysis();
6751                 }
6752                 return;
6753
6754             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6755                 /* crafty (9.25+) says "(only move) <move>"
6756                  * if there is only 1 legal move
6757                  */
6758                 sscanf(p, "(only move) %s", buf1);
6759                 sprintf(thinkOutput, "%s (only move)", buf1);
6760                 sprintf(programStats.movelist, "%s (only move)", buf1);
6761                 programStats.depth = 1;
6762                 programStats.nr_moves = 1;
6763                 programStats.moves_left = 1;
6764                 programStats.nodes = 1;
6765                 programStats.time = 1;
6766                 programStats.got_only_move = 1;
6767
6768                 /* Not really, but we also use this member to
6769                    mean "line isn't going to change" (Crafty
6770                    isn't searching, so stats won't change) */
6771                 programStats.line_is_book = 1;
6772
6773                 SendProgramStatsToFrontend( cps, &programStats );
6774
6775                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6776                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6777                     DisplayMove(currentMove - 1);
6778                     DisplayAnalysis();
6779                 }
6780                 return;
6781             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6782                               &time, &nodes, &plylev, &mvleft,
6783                               &mvtot, mvname) >= 5) {
6784                 /* The stat01: line is from Crafty (9.29+) in response
6785                    to the "." command */
6786                 programStats.seen_stat = 1;
6787                 cps->maybeThinking = TRUE;
6788
6789                 if (programStats.got_only_move || !appData.periodicUpdates)
6790                   return;
6791
6792                 programStats.depth = plylev;
6793                 programStats.time = time;
6794                 programStats.nodes = nodes;
6795                 programStats.moves_left = mvleft;
6796                 programStats.nr_moves = mvtot;
6797                 strcpy(programStats.move_name, mvname);
6798                 programStats.ok_to_send = 1;
6799                 programStats.movelist[0] = '\0';
6800
6801                 SendProgramStatsToFrontend( cps, &programStats );
6802
6803                 DisplayAnalysis();
6804                 return;
6805
6806             } else if (strncmp(message,"++",2) == 0) {
6807                 /* Crafty 9.29+ outputs this */
6808                 programStats.got_fail = 2;
6809                 return;
6810
6811             } else if (strncmp(message,"--",2) == 0) {
6812                 /* Crafty 9.29+ outputs this */
6813                 programStats.got_fail = 1;
6814                 return;
6815
6816             } else if (thinkOutput[0] != NULLCHAR &&
6817                        strncmp(message, "    ", 4) == 0) {
6818                 unsigned message_len;
6819
6820                 p = message;
6821                 while (*p && *p == ' ') p++;
6822
6823                 message_len = strlen( p );
6824
6825                 /* [AS] Avoid buffer overflow */
6826                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6827                     strcat(thinkOutput, " ");
6828                     strcat(thinkOutput, p);
6829                 }
6830
6831                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6832                     strcat(programStats.movelist, " ");
6833                     strcat(programStats.movelist, p);
6834                 }
6835
6836                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6837                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6838                     DisplayMove(currentMove - 1);
6839                     DisplayAnalysis();
6840                 }
6841                 return;
6842             }
6843         }
6844         else {
6845             buf1[0] = NULLCHAR;
6846
6847             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6848                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6849             {
6850                 ChessProgramStats cpstats;
6851
6852                 if (plyext != ' ' && plyext != '\t') {
6853                     time *= 100;
6854                 }
6855
6856                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6857                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6858                     curscore = -curscore;
6859                 }
6860
6861                 cpstats.depth = plylev;
6862                 cpstats.nodes = nodes;
6863                 cpstats.time = time;
6864                 cpstats.score = curscore;
6865                 cpstats.got_only_move = 0;
6866                 cpstats.movelist[0] = '\0';
6867
6868                 if (buf1[0] != NULLCHAR) {
6869                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6870                 }
6871
6872                 cpstats.ok_to_send = 0;
6873                 cpstats.line_is_book = 0;
6874                 cpstats.nr_moves = 0;
6875                 cpstats.moves_left = 0;
6876
6877                 SendProgramStatsToFrontend( cps, &cpstats );
6878             }
6879         }
6880     }
6881 }
6882
6883
6884 /* Parse a game score from the character string "game", and
6885    record it as the history of the current game.  The game
6886    score is NOT assumed to start from the standard position.
6887    The display is not updated in any way.
6888    */
6889 void
6890 ParseGameHistory(game)
6891      char *game;
6892 {
6893     ChessMove moveType;
6894     int fromX, fromY, toX, toY, boardIndex;
6895     char promoChar;
6896     char *p, *q;
6897     char buf[MSG_SIZ];
6898
6899     if (appData.debugMode)
6900       fprintf(debugFP, "Parsing game history: %s\n", game);
6901
6902     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6903     gameInfo.site = StrSave(appData.icsHost);
6904     gameInfo.date = PGNDate();
6905     gameInfo.round = StrSave("-");
6906
6907     /* Parse out names of players */
6908     while (*game == ' ') game++;
6909     p = buf;
6910     while (*game != ' ') *p++ = *game++;
6911     *p = NULLCHAR;
6912     gameInfo.white = StrSave(buf);
6913     while (*game == ' ') game++;
6914     p = buf;
6915     while (*game != ' ' && *game != '\n') *p++ = *game++;
6916     *p = NULLCHAR;
6917     gameInfo.black = StrSave(buf);
6918
6919     /* Parse moves */
6920     boardIndex = blackPlaysFirst ? 1 : 0;
6921     yynewstr(game);
6922     for (;;) {
6923         yyboardindex = boardIndex;
6924         moveType = (ChessMove) yylex();
6925         switch (moveType) {
6926           case IllegalMove:             /* maybe suicide chess, etc. */
6927   if (appData.debugMode) {
6928     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6930     setbuf(debugFP, NULL);
6931   }
6932           case WhitePromotionChancellor:
6933           case BlackPromotionChancellor:
6934           case WhitePromotionArchbishop:
6935           case BlackPromotionArchbishop:
6936           case WhitePromotionQueen:
6937           case BlackPromotionQueen:
6938           case WhitePromotionRook:
6939           case BlackPromotionRook:
6940           case WhitePromotionBishop:
6941           case BlackPromotionBishop:
6942           case WhitePromotionKnight:
6943           case BlackPromotionKnight:
6944           case WhitePromotionKing:
6945           case BlackPromotionKing:
6946           case NormalMove:
6947           case WhiteCapturesEnPassant:
6948           case BlackCapturesEnPassant:
6949           case WhiteKingSideCastle:
6950           case WhiteQueenSideCastle:
6951           case BlackKingSideCastle:
6952           case BlackQueenSideCastle:
6953           case WhiteKingSideCastleWild:
6954           case WhiteQueenSideCastleWild:
6955           case BlackKingSideCastleWild:
6956           case BlackQueenSideCastleWild:
6957           /* PUSH Fabien */
6958           case WhiteHSideCastleFR:
6959           case WhiteASideCastleFR:
6960           case BlackHSideCastleFR:
6961           case BlackASideCastleFR:
6962           /* POP Fabien */
6963             fromX = currentMoveString[0] - AAA;
6964             fromY = currentMoveString[1] - ONE;
6965             toX = currentMoveString[2] - AAA;
6966             toY = currentMoveString[3] - ONE;
6967             promoChar = currentMoveString[4];
6968             break;
6969           case WhiteDrop:
6970           case BlackDrop:
6971             fromX = moveType == WhiteDrop ?
6972               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6973             (int) CharToPiece(ToLower(currentMoveString[0]));
6974             fromY = DROP_RANK;
6975             toX = currentMoveString[2] - AAA;
6976             toY = currentMoveString[3] - ONE;
6977             promoChar = NULLCHAR;
6978             break;
6979           case AmbiguousMove:
6980             /* bug? */
6981             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6982   if (appData.debugMode) {
6983     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6984     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6985     setbuf(debugFP, NULL);
6986   }
6987             DisplayError(buf, 0);
6988             return;
6989           case ImpossibleMove:
6990             /* bug? */
6991             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6992   if (appData.debugMode) {
6993     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6994     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6995     setbuf(debugFP, NULL);
6996   }
6997             DisplayError(buf, 0);
6998             return;
6999           case (ChessMove) 0:   /* end of file */
7000             if (boardIndex < backwardMostMove) {
7001                 /* Oops, gap.  How did that happen? */
7002                 DisplayError(_("Gap in move list"), 0);
7003                 return;
7004             }
7005             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7006             if (boardIndex > forwardMostMove) {
7007                 forwardMostMove = boardIndex;
7008             }
7009             return;
7010           case ElapsedTime:
7011             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7012                 strcat(parseList[boardIndex-1], " ");
7013                 strcat(parseList[boardIndex-1], yy_text);
7014             }
7015             continue;
7016           case Comment:
7017           case PGNTag:
7018           case NAG:
7019           default:
7020             /* ignore */
7021             continue;
7022           case WhiteWins:
7023           case BlackWins:
7024           case GameIsDrawn:
7025           case GameUnfinished:
7026             if (gameMode == IcsExamining) {
7027                 if (boardIndex < backwardMostMove) {
7028                     /* Oops, gap.  How did that happen? */
7029                     return;
7030                 }
7031                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7032                 return;
7033             }
7034             gameInfo.result = moveType;
7035             p = strchr(yy_text, '{');
7036             if (p == NULL) p = strchr(yy_text, '(');
7037             if (p == NULL) {
7038                 p = yy_text;
7039                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7040             } else {
7041                 q = strchr(p, *p == '{' ? '}' : ')');
7042                 if (q != NULL) *q = NULLCHAR;
7043                 p++;
7044             }
7045             gameInfo.resultDetails = StrSave(p);
7046             continue;
7047         }
7048         if (boardIndex >= forwardMostMove &&
7049             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7050             backwardMostMove = blackPlaysFirst ? 1 : 0;
7051             return;
7052         }
7053         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7054                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7055                                  parseList[boardIndex]);
7056         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7057         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7058         /* currentMoveString is set as a side-effect of yylex */
7059         strcpy(moveList[boardIndex], currentMoveString);
7060         strcat(moveList[boardIndex], "\n");
7061         boardIndex++;
7062         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7063                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7064         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7065                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7066           case MT_NONE:
7067           case MT_STALEMATE:
7068           default:
7069             break;
7070           case MT_CHECK:
7071             if(gameInfo.variant != VariantShogi)
7072                 strcat(parseList[boardIndex - 1], "+");
7073             break;
7074           case MT_CHECKMATE:
7075           case MT_STAINMATE:
7076             strcat(parseList[boardIndex - 1], "#");
7077             break;
7078         }
7079     }
7080 }
7081
7082
7083 /* Apply a move to the given board  */
7084 void
7085 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7086      int fromX, fromY, toX, toY;
7087      int promoChar;
7088      Board board;
7089      char *castling;
7090      char *ep;
7091 {
7092   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7093
7094     /* [HGM] compute & store e.p. status and castling rights for new position */
7095     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7096     { int i;
7097
7098       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7099       oldEP = *ep;
7100       *ep = EP_NONE;
7101
7102       if( board[toY][toX] != EmptySquare )
7103            *ep = EP_CAPTURE;
7104
7105       if( board[fromY][fromX] == WhitePawn ) {
7106            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7107                *ep = EP_PAWN_MOVE;
7108            if( toY-fromY==2) {
7109                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7110                         gameInfo.variant != VariantBerolina || toX < fromX)
7111                       *ep = toX | berolina;
7112                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7113                         gameInfo.variant != VariantBerolina || toX > fromX)
7114                       *ep = toX;
7115            }
7116       } else
7117       if( board[fromY][fromX] == BlackPawn ) {
7118            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7119                *ep = EP_PAWN_MOVE;
7120            if( toY-fromY== -2) {
7121                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7122                         gameInfo.variant != VariantBerolina || toX < fromX)
7123                       *ep = toX | berolina;
7124                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7125                         gameInfo.variant != VariantBerolina || toX > fromX)
7126                       *ep = toX;
7127            }
7128        }
7129
7130        for(i=0; i<nrCastlingRights; i++) {
7131            if(castling[i] == fromX && castlingRank[i] == fromY ||
7132               castling[i] == toX   && castlingRank[i] == toY
7133              ) castling[i] = -1; // revoke for moved or captured piece
7134        }
7135
7136     }
7137
7138   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7139   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7140        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7141
7142   if (fromX == toX && fromY == toY) return;
7143
7144   if (fromY == DROP_RANK) {
7145         /* must be first */
7146         piece = board[toY][toX] = (ChessSquare) fromX;
7147   } else {
7148      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7149      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7150      if(gameInfo.variant == VariantKnightmate)
7151          king += (int) WhiteUnicorn - (int) WhiteKing;
7152
7153     /* Code added by Tord: */
7154     /* FRC castling assumed when king captures friendly rook. */
7155     if (board[fromY][fromX] == WhiteKing &&
7156              board[toY][toX] == WhiteRook) {
7157       board[fromY][fromX] = EmptySquare;
7158       board[toY][toX] = EmptySquare;
7159       if(toX > fromX) {
7160         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7161       } else {
7162         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7163       }
7164     } else if (board[fromY][fromX] == BlackKing &&
7165                board[toY][toX] == BlackRook) {
7166       board[fromY][fromX] = EmptySquare;
7167       board[toY][toX] = EmptySquare;
7168       if(toX > fromX) {
7169         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7170       } else {
7171         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7172       }
7173     /* End of code added by Tord */
7174
7175     } else if (board[fromY][fromX] == king
7176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177         && toY == fromY && toX > fromX+1) {
7178         board[fromY][fromX] = EmptySquare;
7179         board[toY][toX] = king;
7180         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7181         board[fromY][BOARD_RGHT-1] = EmptySquare;
7182     } else if (board[fromY][fromX] == king
7183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7184                && toY == fromY && toX < fromX-1) {
7185         board[fromY][fromX] = EmptySquare;
7186         board[toY][toX] = king;
7187         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7188         board[fromY][BOARD_LEFT] = EmptySquare;
7189     } else if (board[fromY][fromX] == WhitePawn
7190                && toY == BOARD_HEIGHT-1
7191                && gameInfo.variant != VariantXiangqi
7192                ) {
7193         /* white pawn promotion */
7194         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7195         if (board[toY][toX] == EmptySquare) {
7196             board[toY][toX] = WhiteQueen;
7197         }
7198         if(gameInfo.variant==VariantBughouse ||
7199            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7200             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7201         board[fromY][fromX] = EmptySquare;
7202     } else if ((fromY == BOARD_HEIGHT-4)
7203                && (toX != fromX)
7204                && gameInfo.variant != VariantXiangqi
7205                && gameInfo.variant != VariantBerolina
7206                && (board[fromY][fromX] == WhitePawn)
7207                && (board[toY][toX] == EmptySquare)) {
7208         board[fromY][fromX] = EmptySquare;
7209         board[toY][toX] = WhitePawn;
7210         captured = board[toY - 1][toX];
7211         board[toY - 1][toX] = EmptySquare;
7212     } else if ((fromY == BOARD_HEIGHT-4)
7213                && (toX == fromX)
7214                && gameInfo.variant == VariantBerolina
7215                && (board[fromY][fromX] == WhitePawn)
7216                && (board[toY][toX] == EmptySquare)) {
7217         board[fromY][fromX] = EmptySquare;
7218         board[toY][toX] = WhitePawn;
7219         if(oldEP & EP_BEROLIN_A) {
7220                 captured = board[fromY][fromX-1];
7221                 board[fromY][fromX-1] = EmptySquare;
7222         }else{  captured = board[fromY][fromX+1];
7223                 board[fromY][fromX+1] = EmptySquare;
7224         }
7225     } else if (board[fromY][fromX] == king
7226         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7227                && toY == fromY && toX > fromX+1) {
7228         board[fromY][fromX] = EmptySquare;
7229         board[toY][toX] = king;
7230         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7231         board[fromY][BOARD_RGHT-1] = EmptySquare;
7232     } else if (board[fromY][fromX] == king
7233         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7234                && toY == fromY && toX < fromX-1) {
7235         board[fromY][fromX] = EmptySquare;
7236         board[toY][toX] = king;
7237         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7238         board[fromY][BOARD_LEFT] = EmptySquare;
7239     } else if (fromY == 7 && fromX == 3
7240                && board[fromY][fromX] == BlackKing
7241                && toY == 7 && toX == 5) {
7242         board[fromY][fromX] = EmptySquare;
7243         board[toY][toX] = BlackKing;
7244         board[fromY][7] = EmptySquare;
7245         board[toY][4] = BlackRook;
7246     } else if (fromY == 7 && fromX == 3
7247                && board[fromY][fromX] == BlackKing
7248                && toY == 7 && toX == 1) {
7249         board[fromY][fromX] = EmptySquare;
7250         board[toY][toX] = BlackKing;
7251         board[fromY][0] = EmptySquare;
7252         board[toY][2] = BlackRook;
7253     } else if (board[fromY][fromX] == BlackPawn
7254                && toY == 0
7255                && gameInfo.variant != VariantXiangqi
7256                ) {
7257         /* black pawn promotion */
7258         board[0][toX] = CharToPiece(ToLower(promoChar));
7259         if (board[0][toX] == EmptySquare) {
7260             board[0][toX] = BlackQueen;
7261         }
7262         if(gameInfo.variant==VariantBughouse ||
7263            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7264             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7265         board[fromY][fromX] = EmptySquare;
7266     } else if ((fromY == 3)
7267                && (toX != fromX)
7268                && gameInfo.variant != VariantXiangqi
7269                && gameInfo.variant != VariantBerolina
7270                && (board[fromY][fromX] == BlackPawn)
7271                && (board[toY][toX] == EmptySquare)) {
7272         board[fromY][fromX] = EmptySquare;
7273         board[toY][toX] = BlackPawn;
7274         captured = board[toY + 1][toX];
7275         board[toY + 1][toX] = EmptySquare;
7276     } else if ((fromY == 3)
7277                && (toX == fromX)
7278                && gameInfo.variant == VariantBerolina
7279                && (board[fromY][fromX] == BlackPawn)
7280                && (board[toY][toX] == EmptySquare)) {
7281         board[fromY][fromX] = EmptySquare;
7282         board[toY][toX] = BlackPawn;
7283         if(oldEP & EP_BEROLIN_A) {
7284                 captured = board[fromY][fromX-1];
7285                 board[fromY][fromX-1] = EmptySquare;
7286         }else{  captured = board[fromY][fromX+1];
7287                 board[fromY][fromX+1] = EmptySquare;
7288         }
7289     } else {
7290         board[toY][toX] = board[fromY][fromX];
7291         board[fromY][fromX] = EmptySquare;
7292     }
7293
7294     /* [HGM] now we promote for Shogi, if needed */
7295     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7296         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7297   }
7298
7299     if (gameInfo.holdingsWidth != 0) {
7300
7301       /* !!A lot more code needs to be written to support holdings  */
7302       /* [HGM] OK, so I have written it. Holdings are stored in the */
7303       /* penultimate board files, so they are automaticlly stored   */
7304       /* in the game history.                                       */
7305       if (fromY == DROP_RANK) {
7306         /* Delete from holdings, by decreasing count */
7307         /* and erasing image if necessary            */
7308         p = (int) fromX;
7309         if(p < (int) BlackPawn) { /* white drop */
7310              p -= (int)WhitePawn;
7311              if(p >= gameInfo.holdingsSize) p = 0;
7312              if(--board[p][BOARD_WIDTH-2] == 0)
7313                   board[p][BOARD_WIDTH-1] = EmptySquare;
7314         } else {                  /* black drop */
7315              p -= (int)BlackPawn;
7316              if(p >= gameInfo.holdingsSize) p = 0;
7317              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7318                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7319         }
7320       }
7321       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7322           && gameInfo.variant != VariantBughouse        ) {
7323         /* [HGM] holdings: Add to holdings, if holdings exist */
7324         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7325                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7326                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7327         }
7328         p = (int) captured;
7329         if (p >= (int) BlackPawn) {
7330           p -= (int)BlackPawn;
7331           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7332                   /* in Shogi restore piece to its original  first */
7333                   captured = (ChessSquare) (DEMOTED captured);
7334                   p = DEMOTED p;
7335           }
7336           p = PieceToNumber((ChessSquare)p);
7337           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7338           board[p][BOARD_WIDTH-2]++;
7339           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7340         } else {
7341           p -= (int)WhitePawn;
7342           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7343                   captured = (ChessSquare) (DEMOTED captured);
7344                   p = DEMOTED p;
7345           }
7346           p = PieceToNumber((ChessSquare)p);
7347           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7348           board[BOARD_HEIGHT-1-p][1]++;
7349           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7350         }
7351       }
7352
7353     } else if (gameInfo.variant == VariantAtomic) {
7354       if (captured != EmptySquare) {
7355         int y, x;
7356         for (y = toY-1; y <= toY+1; y++) {
7357           for (x = toX-1; x <= toX+1; x++) {
7358             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7359                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7360               board[y][x] = EmptySquare;
7361             }
7362           }
7363         }
7364         board[toY][toX] = EmptySquare;
7365       }
7366     }
7367     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7368         /* [HGM] Shogi promotions */
7369         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7370     }
7371
7372     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7373                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7374         // [HGM] superchess: take promotion piece out of holdings
7375         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7376         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7377             if(!--board[k][BOARD_WIDTH-2])
7378                 board[k][BOARD_WIDTH-1] = EmptySquare;
7379         } else {
7380             if(!--board[BOARD_HEIGHT-1-k][1])
7381                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7382         }
7383     }
7384
7385 }
7386
7387 /* Updates forwardMostMove */
7388 void
7389 MakeMove(fromX, fromY, toX, toY, promoChar)
7390      int fromX, fromY, toX, toY;
7391      int promoChar;
7392 {
7393 //    forwardMostMove++; // [HGM] bare: moved downstream
7394
7395     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7396         int timeLeft; static int lastLoadFlag=0; int king, piece;
7397         piece = boards[forwardMostMove][fromY][fromX];
7398         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7399         if(gameInfo.variant == VariantKnightmate)
7400             king += (int) WhiteUnicorn - (int) WhiteKing;
7401         if(forwardMostMove == 0) {
7402             if(blackPlaysFirst)
7403                 fprintf(serverMoves, "%s;", second.tidy);
7404             fprintf(serverMoves, "%s;", first.tidy);
7405             if(!blackPlaysFirst)
7406                 fprintf(serverMoves, "%s;", second.tidy);
7407         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7408         lastLoadFlag = loadFlag;
7409         // print base move
7410         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7411         // print castling suffix
7412         if( toY == fromY && piece == king ) {
7413             if(toX-fromX > 1)
7414                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7415             if(fromX-toX >1)
7416                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7417         }
7418         // e.p. suffix
7419         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7420              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7421              boards[forwardMostMove][toY][toX] == EmptySquare
7422              && fromX != toX )
7423                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7424         // promotion suffix
7425         if(promoChar != NULLCHAR)
7426                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7427         if(!loadFlag) {
7428             fprintf(serverMoves, "/%d/%d",
7429                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7430             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7431             else                      timeLeft = blackTimeRemaining/1000;
7432             fprintf(serverMoves, "/%d", timeLeft);
7433         }
7434         fflush(serverMoves);
7435     }
7436
7437     if (forwardMostMove+1 >= MAX_MOVES) {
7438       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7439                         0, 1);
7440       return;
7441     }
7442     SwitchClocks();
7443     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7444     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7445     if (commentList[forwardMostMove+1] != NULL) {
7446         free(commentList[forwardMostMove+1]);
7447         commentList[forwardMostMove+1] = NULL;
7448     }
7449     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7450     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7451     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7452                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7453     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7454     gameInfo.result = GameUnfinished;
7455     if (gameInfo.resultDetails != NULL) {
7456         free(gameInfo.resultDetails);
7457         gameInfo.resultDetails = NULL;
7458     }
7459     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7460                               moveList[forwardMostMove - 1]);
7461     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7462                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7463                              fromY, fromX, toY, toX, promoChar,
7464                              parseList[forwardMostMove - 1]);
7465     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7466                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7467                             castlingRights[forwardMostMove]) ) {
7468       case MT_NONE:
7469       case MT_STALEMATE:
7470       default:
7471         break;
7472       case MT_CHECK:
7473         if(gameInfo.variant != VariantShogi)
7474             strcat(parseList[forwardMostMove - 1], "+");
7475         break;
7476       case MT_CHECKMATE:
7477       case MT_STAINMATE:
7478         strcat(parseList[forwardMostMove - 1], "#");
7479         break;
7480     }
7481     if (appData.debugMode) {
7482         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7483     }
7484
7485 }
7486
7487 /* Updates currentMove if not pausing */
7488 void
7489 ShowMove(fromX, fromY, toX, toY)
7490 {
7491     int instant = (gameMode == PlayFromGameFile) ?
7492         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7493
7494     if(appData.noGUI) return;
7495
7496     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7497       {
7498         if (!instant)
7499           {
7500             if (forwardMostMove == currentMove + 1)
7501               {
7502 //TODO
7503 //              AnimateMove(boards[forwardMostMove - 1],
7504 //                          fromX, fromY, toX, toY);
7505               }
7506             if (appData.highlightLastMove)
7507               {
7508                 SetHighlights(fromX, fromY, toX, toY);
7509               }
7510           }
7511         currentMove = forwardMostMove;
7512     }
7513
7514     if (instant) return;
7515
7516     DisplayMove(currentMove - 1);
7517     DrawPosition(FALSE, boards[currentMove]);
7518     DisplayBothClocks();
7519     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7520
7521     return;
7522 }
7523
7524 void SendEgtPath(ChessProgramState *cps)
7525 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7526         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7527
7528         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7529
7530         while(*p) {
7531             char c, *q = name+1, *r, *s;
7532
7533             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7534             while(*p && *p != ',') *q++ = *p++;
7535             *q++ = ':'; *q = 0;
7536             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7537                 strcmp(name, ",nalimov:") == 0 ) {
7538                 // take nalimov path from the menu-changeable option first, if it is defined
7539                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7540                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7541             } else
7542             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7543                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7544                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7545                 s = r = StrStr(s, ":") + 1; // beginning of path info
7546                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7547                 c = *r; *r = 0;             // temporarily null-terminate path info
7548                     *--q = 0;               // strip of trailig ':' from name
7549                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7550                 *r = c;
7551                 SendToProgram(buf,cps);     // send egtbpath command for this format
7552             }
7553             if(*p == ',') p++; // read away comma to position for next format name
7554         }
7555 }
7556
7557 void
7558 InitChessProgram(cps, setup)
7559      ChessProgramState *cps;
7560      int setup; /* [HGM] needed to setup FRC opening position */
7561 {
7562     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7563     if (appData.noChessProgram) return;
7564     hintRequested = FALSE;
7565     bookRequested = FALSE;
7566
7567     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7568     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7569     if(cps->memSize) { /* [HGM] memory */
7570         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7571         SendToProgram(buf, cps);
7572     }
7573     SendEgtPath(cps); /* [HGM] EGT */
7574     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7575         sprintf(buf, "cores %d\n", appData.smpCores);
7576         SendToProgram(buf, cps);
7577     }
7578
7579     SendToProgram(cps->initString, cps);
7580     if (gameInfo.variant != VariantNormal &&
7581         gameInfo.variant != VariantLoadable
7582         /* [HGM] also send variant if board size non-standard */
7583         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7584                                             ) {
7585       char *v = VariantName(gameInfo.variant);
7586       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7587         /* [HGM] in protocol 1 we have to assume all variants valid */
7588         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7589         DisplayFatalError(buf, 0, 1);
7590         return;
7591       }
7592
7593       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7594       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7595       if( gameInfo.variant == VariantXiangqi )
7596            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7597       if( gameInfo.variant == VariantShogi )
7598            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7599       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7600            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7601       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7602                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7603            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7604       if( gameInfo.variant == VariantCourier )
7605            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7606       if( gameInfo.variant == VariantSuper )
7607            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7608       if( gameInfo.variant == VariantGreat )
7609            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7610
7611       if(overruled) {
7612            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7613                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7614            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7615            if(StrStr(cps->variants, b) == NULL) {
7616                // specific sized variant not known, check if general sizing allowed
7617                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7618                    if(StrStr(cps->variants, "boardsize") == NULL) {
7619                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7620                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7621                        DisplayFatalError(buf, 0, 1);
7622                        return;
7623                    }
7624                    /* [HGM] here we really should compare with the maximum supported board size */
7625                }
7626            }
7627       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7628       sprintf(buf, "variant %s\n", b);
7629       SendToProgram(buf, cps);
7630     }
7631     currentlyInitializedVariant = gameInfo.variant;
7632
7633     /* [HGM] send opening position in FRC to first engine */
7634     if(setup) {
7635           SendToProgram("force\n", cps);
7636           SendBoard(cps, 0);
7637           /* engine is now in force mode! Set flag to wake it up after first move. */
7638           setboardSpoiledMachineBlack = 1;
7639     }
7640
7641     if (cps->sendICS) {
7642       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7643       SendToProgram(buf, cps);
7644     }
7645     cps->maybeThinking = FALSE;
7646     cps->offeredDraw = 0;
7647     if (!appData.icsActive) {
7648         SendTimeControl(cps, movesPerSession, timeControl,
7649                         timeIncrement, appData.searchDepth,
7650                         searchTime);
7651     }
7652     if (appData.showThinking
7653         // [HGM] thinking: four options require thinking output to be sent
7654         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7655                                 ) {
7656         SendToProgram("post\n", cps);
7657     }
7658     SendToProgram("hard\n", cps);
7659     if (!appData.ponderNextMove) {
7660         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7661            it without being sure what state we are in first.  "hard"
7662            is not a toggle, so that one is OK.
7663          */
7664         SendToProgram("easy\n", cps);
7665     }
7666     if (cps->usePing) {
7667       sprintf(buf, "ping %d\n", ++cps->lastPing);
7668       SendToProgram(buf, cps);
7669     }
7670     cps->initDone = TRUE;
7671 }
7672
7673
7674 void
7675 StartChessProgram(cps)
7676      ChessProgramState *cps;
7677 {
7678     char buf[MSG_SIZ];
7679     int err;
7680
7681     if (appData.noChessProgram) return;
7682     cps->initDone = FALSE;
7683
7684     if (strcmp(cps->host, "localhost") == 0) {
7685         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7686     } else if (*appData.remoteShell == NULLCHAR) {
7687         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7688     } else {
7689         if (*appData.remoteUser == NULLCHAR) {
7690           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7691                     cps->program);
7692         } else {
7693           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7694                     cps->host, appData.remoteUser, cps->program);
7695         }
7696         err = StartChildProcess(buf, "", &cps->pr);
7697     }
7698
7699     if (err != 0) {
7700         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7701         DisplayFatalError(buf, err, 1);
7702         cps->pr = NoProc;
7703         cps->isr = NULL;
7704         return;
7705     }
7706
7707     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7708     if (cps->protocolVersion > 1) {
7709       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7710       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7711       cps->comboCnt = 0;  //                and values of combo boxes
7712       SendToProgram(buf, cps);
7713     } else {
7714       SendToProgram("xboard\n", cps);
7715     }
7716 }
7717
7718
7719 void
7720 TwoMachinesEventIfReady P((void))
7721 {
7722   if (first.lastPing != first.lastPong) {
7723     DisplayMessage("", _("Waiting for first chess program"));
7724     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7725     return;
7726   }
7727   if (second.lastPing != second.lastPong) {
7728     DisplayMessage("", _("Waiting for second chess program"));
7729     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7730     return;
7731   }
7732   ThawUI();
7733   TwoMachinesEvent();
7734 }
7735
7736 void
7737 NextMatchGame P((void))
7738 {
7739     int index; /* [HGM] autoinc: step lod index during match */
7740     Reset(FALSE, TRUE);
7741     if (*appData.loadGameFile != NULLCHAR) {
7742         index = appData.loadGameIndex;
7743         if(index < 0) { // [HGM] autoinc
7744             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7745             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7746         }
7747         LoadGameFromFile(appData.loadGameFile,
7748                          index,
7749                          appData.loadGameFile, FALSE);
7750     } else if (*appData.loadPositionFile != NULLCHAR) {
7751         index = appData.loadPositionIndex;
7752         if(index < 0) { // [HGM] autoinc
7753             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7754             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7755         }
7756         LoadPositionFromFile(appData.loadPositionFile,
7757                              index,
7758                              appData.loadPositionFile);
7759     }
7760     TwoMachinesEventIfReady();
7761 }
7762
7763 void UserAdjudicationEvent( int result )
7764 {
7765     ChessMove gameResult = GameIsDrawn;
7766
7767     if( result > 0 ) {
7768         gameResult = WhiteWins;
7769     }
7770     else if( result < 0 ) {
7771         gameResult = BlackWins;
7772     }
7773
7774     if( gameMode == TwoMachinesPlay ) {
7775         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7776     }
7777 }
7778
7779
7780 // [HGM] save: calculate checksum of game to make games easily identifiable
7781 int StringCheckSum(char *s)
7782 {
7783         int i = 0;
7784         if(s==NULL) return 0;
7785         while(*s) i = i*259 + *s++;
7786         return i;
7787 }
7788
7789 int GameCheckSum()
7790 {
7791         int i, sum=0;
7792         for(i=backwardMostMove; i<forwardMostMove; i++) {
7793                 sum += pvInfoList[i].depth;
7794                 sum += StringCheckSum(parseList[i]);
7795                 sum += StringCheckSum(commentList[i]);
7796                 sum *= 261;
7797         }
7798         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7799         return sum + StringCheckSum(commentList[i]);
7800 } // end of save patch
7801
7802 void
7803 GameEnds(result, resultDetails, whosays)
7804      ChessMove result;
7805      char *resultDetails;
7806      int whosays;
7807 {
7808     GameMode nextGameMode;
7809     int isIcsGame;
7810     char buf[MSG_SIZ];
7811
7812     if(endingGame) return; /* [HGM] crash: forbid recursion */
7813     endingGame = 1;
7814
7815     if (appData.debugMode) {
7816       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7817               result, resultDetails ? resultDetails : "(null)", whosays);
7818     }
7819
7820     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7821         /* If we are playing on ICS, the server decides when the
7822            game is over, but the engine can offer to draw, claim
7823            a draw, or resign.
7824          */
7825 #if ZIPPY
7826         if (appData.zippyPlay && first.initDone) {
7827             if (result == GameIsDrawn) {
7828                 /* In case draw still needs to be claimed */
7829                 SendToICS(ics_prefix);
7830                 SendToICS("draw\n");
7831             } else if (StrCaseStr(resultDetails, "resign")) {
7832                 SendToICS(ics_prefix);
7833                 SendToICS("resign\n");
7834             }
7835         }
7836 #endif
7837         endingGame = 0; /* [HGM] crash */
7838         return;
7839     }
7840
7841     /* If we're loading the game from a file, stop */
7842     if (whosays == GE_FILE) {
7843       (void) StopLoadGameTimer();
7844       gameFileFP = NULL;
7845     }
7846
7847     /* Cancel draw offers */
7848     first.offeredDraw = second.offeredDraw = 0;
7849
7850     /* If this is an ICS game, only ICS can really say it's done;
7851        if not, anyone can. */
7852     isIcsGame = (gameMode == IcsPlayingWhite ||
7853                  gameMode == IcsPlayingBlack ||
7854                  gameMode == IcsObserving    ||
7855                  gameMode == IcsExamining);
7856
7857     if (!isIcsGame || whosays == GE_ICS) {
7858         /* OK -- not an ICS game, or ICS said it was done */
7859         StopClocks();
7860         if (!isIcsGame && !appData.noChessProgram)
7861           SetUserThinkingEnables();
7862
7863         /* [HGM] if a machine claims the game end we verify this claim */
7864         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7865             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7866                 char claimer;
7867                 ChessMove trueResult = (ChessMove) -1;
7868
7869                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7870                                             first.twoMachinesColor[0] :
7871                                             second.twoMachinesColor[0] ;
7872
7873                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7874                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7875                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7876                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7877                 } else
7878                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7879                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7880                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7881                 } else
7882                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7883                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7884                 }
7885
7886                 // now verify win claims, but not in drop games, as we don't understand those yet
7887                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7888                                                  || gameInfo.variant == VariantGreat) &&
7889                     (result == WhiteWins && claimer == 'w' ||
7890                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7891                       if (appData.debugMode) {
7892                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7893                                 result, epStatus[forwardMostMove], forwardMostMove);
7894                       }
7895                       if(result != trueResult) {
7896                               sprintf(buf, "False win claim: '%s'", resultDetails);
7897                               result = claimer == 'w' ? BlackWins : WhiteWins;
7898                               resultDetails = buf;
7899                       }
7900                 } else
7901                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7902                     && (forwardMostMove <= backwardMostMove ||
7903                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7904                         (claimer=='b')==(forwardMostMove&1))
7905                                                                                   ) {
7906                       /* [HGM] verify: draws that were not flagged are false claims */
7907                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7908                       result = claimer == 'w' ? BlackWins : WhiteWins;
7909                       resultDetails = buf;
7910                 }
7911                 /* (Claiming a loss is accepted no questions asked!) */
7912             }
7913
7914             /* [HGM] bare: don't allow bare King to win */
7915             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7916                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7917                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7918                && result != GameIsDrawn)
7919             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7920                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7921                         int p = (int)boards[forwardMostMove][i][j] - color;
7922                         if(p >= 0 && p <= (int)WhiteKing) k++;
7923                 }
7924                 if (appData.debugMode) {
7925                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7926                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7927                 }
7928                 if(k <= 1) {
7929                         result = GameIsDrawn;
7930                         sprintf(buf, "%s but bare king", resultDetails);
7931                         resultDetails = buf;
7932                 }
7933             }
7934         }
7935
7936         if(serverMoves != NULL && !loadFlag) { char c = '=';
7937             if(result==WhiteWins) c = '+';
7938             if(result==BlackWins) c = '-';
7939             if(resultDetails != NULL)
7940                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7941         }
7942         if (resultDetails != NULL) {
7943             gameInfo.result = result;
7944             gameInfo.resultDetails = StrSave(resultDetails);
7945
7946             /* display last move only if game was not loaded from file */
7947             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7948                 DisplayMove(currentMove - 1);
7949
7950             if (forwardMostMove != 0) {
7951                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7952                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7953                                                                 ) {
7954                     if (*appData.saveGameFile != NULLCHAR) {
7955                         SaveGameToFile(appData.saveGameFile, TRUE);
7956                     } else if (appData.autoSaveGames) {
7957                         AutoSaveGame();
7958                     }
7959                     if (*appData.savePositionFile != NULLCHAR) {
7960                         SavePositionToFile(appData.savePositionFile);
7961                     }
7962                 }
7963             }
7964
7965             /* Tell program how game ended in case it is learning */
7966             /* [HGM] Moved this to after saving the PGN, just in case */
7967             /* engine died and we got here through time loss. In that */
7968             /* case we will get a fatal error writing the pipe, which */
7969             /* would otherwise lose us the PGN.                       */
7970             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7971             /* output during GameEnds should never be fatal anymore   */
7972             if (gameMode == MachinePlaysWhite ||
7973                 gameMode == MachinePlaysBlack ||
7974                 gameMode == TwoMachinesPlay ||
7975                 gameMode == IcsPlayingWhite ||
7976                 gameMode == IcsPlayingBlack ||
7977                 gameMode == BeginningOfGame) {
7978                 char buf[MSG_SIZ];
7979                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7980                         resultDetails);
7981                 if (first.pr != NoProc) {
7982                     SendToProgram(buf, &first);
7983                 }
7984                 if (second.pr != NoProc &&
7985                     gameMode == TwoMachinesPlay) {
7986                     SendToProgram(buf, &second);
7987                 }
7988             }
7989         }
7990
7991         if (appData.icsActive) {
7992             if (appData.quietPlay &&
7993                 (gameMode == IcsPlayingWhite ||
7994                  gameMode == IcsPlayingBlack)) {
7995                 SendToICS(ics_prefix);
7996                 SendToICS("set shout 1\n");
7997             }
7998             nextGameMode = IcsIdle;
7999             ics_user_moved = FALSE;
8000             /* clean up premove.  It's ugly when the game has ended and the
8001              * premove highlights are still on the board.
8002              */
8003             if (gotPremove) {
8004               gotPremove = FALSE;
8005               ClearPremoveHighlights();
8006               DrawPosition(FALSE, boards[currentMove]);
8007             }
8008             if (whosays == GE_ICS) {
8009                 switch (result) {
8010                 case WhiteWins:
8011                     if (gameMode == IcsPlayingWhite)
8012                         PlayIcsWinSound();
8013                     else if(gameMode == IcsPlayingBlack)
8014                         PlayIcsLossSound();
8015                     break;
8016                 case BlackWins:
8017                     if (gameMode == IcsPlayingBlack)
8018                         PlayIcsWinSound();
8019                     else if(gameMode == IcsPlayingWhite)
8020                         PlayIcsLossSound();
8021                     break;
8022                 case GameIsDrawn:
8023                     PlayIcsDrawSound();
8024                     break;
8025                 default:
8026                     PlayIcsUnfinishedSound();
8027                 }
8028             }
8029         } else if (gameMode == EditGame ||
8030                    gameMode == PlayFromGameFile ||
8031                    gameMode == AnalyzeMode ||
8032                    gameMode == AnalyzeFile) {
8033             nextGameMode = gameMode;
8034         } else {
8035             nextGameMode = EndOfGame;
8036         }
8037         pausing = FALSE;
8038         ModeHighlight();
8039     } else {
8040         nextGameMode = gameMode;
8041     }
8042
8043     if (appData.noChessProgram) {
8044         gameMode = nextGameMode;
8045         ModeHighlight();
8046         endingGame = 0; /* [HGM] crash */
8047         return;
8048     }
8049
8050     if (first.reuse) {
8051         /* Put first chess program into idle state */
8052         if (first.pr != NoProc &&
8053             (gameMode == MachinePlaysWhite ||
8054              gameMode == MachinePlaysBlack ||
8055              gameMode == TwoMachinesPlay ||
8056              gameMode == IcsPlayingWhite ||
8057              gameMode == IcsPlayingBlack ||
8058              gameMode == BeginningOfGame)) {
8059             SendToProgram("force\n", &first);
8060             if (first.usePing) {
8061               char buf[MSG_SIZ];
8062               sprintf(buf, "ping %d\n", ++first.lastPing);
8063               SendToProgram(buf, &first);
8064             }
8065         }
8066     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8067         /* Kill off first chess program */
8068         if (first.isr != NULL)
8069           RemoveInputSource(first.isr);
8070         first.isr = NULL;
8071
8072         if (first.pr != NoProc) {
8073             ExitAnalyzeMode();
8074             DoSleep( appData.delayBeforeQuit );
8075             SendToProgram("quit\n", &first);
8076             DoSleep( appData.delayAfterQuit );
8077             DestroyChildProcess(first.pr, first.useSigterm);
8078         }
8079         first.pr = NoProc;
8080     }
8081     if (second.reuse) {
8082         /* Put second chess program into idle state */
8083         if (second.pr != NoProc &&
8084             gameMode == TwoMachinesPlay) {
8085             SendToProgram("force\n", &second);
8086             if (second.usePing) {
8087               char buf[MSG_SIZ];
8088               sprintf(buf, "ping %d\n", ++second.lastPing);
8089               SendToProgram(buf, &second);
8090             }
8091         }
8092     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8093         /* Kill off second chess program */
8094         if (second.isr != NULL)
8095           RemoveInputSource(second.isr);
8096         second.isr = NULL;
8097
8098         if (second.pr != NoProc) {
8099             DoSleep( appData.delayBeforeQuit );
8100             SendToProgram("quit\n", &second);
8101             DoSleep( appData.delayAfterQuit );
8102             DestroyChildProcess(second.pr, second.useSigterm);
8103         }
8104         second.pr = NoProc;
8105     }
8106
8107     if (matchMode && gameMode == TwoMachinesPlay) {
8108         switch (result) {
8109         case WhiteWins:
8110           if (first.twoMachinesColor[0] == 'w') {
8111             first.matchWins++;
8112           } else {
8113             second.matchWins++;
8114           }
8115           break;
8116         case BlackWins:
8117           if (first.twoMachinesColor[0] == 'b') {
8118             first.matchWins++;
8119           } else {
8120             second.matchWins++;
8121           }
8122           break;
8123         default:
8124           break;
8125         }
8126         if (matchGame < appData.matchGames) {
8127             char *tmp;
8128             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8129                 tmp = first.twoMachinesColor;
8130                 first.twoMachinesColor = second.twoMachinesColor;
8131                 second.twoMachinesColor = tmp;
8132             }
8133             gameMode = nextGameMode;
8134             matchGame++;
8135             if(appData.matchPause>10000 || appData.matchPause<10)
8136                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8137             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8138             endingGame = 0; /* [HGM] crash */
8139             return;
8140         } else {
8141             char buf[MSG_SIZ];
8142             gameMode = nextGameMode;
8143             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8144                     first.tidy, second.tidy,
8145                     first.matchWins, second.matchWins,
8146                     appData.matchGames - (first.matchWins + second.matchWins));
8147             DisplayFatalError(buf, 0, 0);
8148         }
8149     }
8150     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8151         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8152       ExitAnalyzeMode();
8153     gameMode = nextGameMode;
8154     ModeHighlight();
8155     endingGame = 0;  /* [HGM] crash */
8156 }
8157
8158 /* Assumes program was just initialized (initString sent).
8159    Leaves program in force mode. */
8160 void
8161 FeedMovesToProgram(cps, upto)
8162      ChessProgramState *cps;
8163      int upto;
8164 {
8165     int i;
8166
8167     if (appData.debugMode)
8168       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8169               startedFromSetupPosition ? "position and " : "",
8170               backwardMostMove, upto, cps->which);
8171     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8172         // [HGM] variantswitch: make engine aware of new variant
8173         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8174                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8175         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8176         SendToProgram(buf, cps);
8177         currentlyInitializedVariant = gameInfo.variant;
8178     }
8179     SendToProgram("force\n", cps);
8180     if (startedFromSetupPosition) {
8181         SendBoard(cps, backwardMostMove);
8182     if (appData.debugMode) {
8183         fprintf(debugFP, "feedMoves\n");
8184     }
8185     }
8186     for (i = backwardMostMove; i < upto; i++) {
8187         SendMoveToProgram(i, cps);
8188     }
8189 }
8190
8191
8192 void
8193 ResurrectChessProgram()
8194 {
8195      /* The chess program may have exited.
8196         If so, restart it and feed it all the moves made so far. */
8197
8198     if (appData.noChessProgram || first.pr != NoProc) return;
8199
8200     StartChessProgram(&first);
8201     InitChessProgram(&first, FALSE);
8202     FeedMovesToProgram(&first, currentMove);
8203
8204     if (!first.sendTime) {
8205         /* can't tell gnuchess what its clock should read,
8206            so we bow to its notion. */
8207         ResetClocks();
8208         timeRemaining[0][currentMove] = whiteTimeRemaining;
8209         timeRemaining[1][currentMove] = blackTimeRemaining;
8210     }
8211
8212     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8213                 appData.icsEngineAnalyze) && first.analysisSupport) {
8214       SendToProgram("analyze\n", &first);
8215       first.analyzing = TRUE;
8216     }
8217 }
8218
8219 /*
8220  * Button procedures
8221  */
8222 void
8223 Reset(redraw, init)
8224      int redraw, init;
8225 {
8226     int i;
8227
8228     if (appData.debugMode) {
8229         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8230                 redraw, init, gameMode);
8231     }
8232     pausing = pauseExamInvalid = FALSE;
8233     startedFromSetupPosition = blackPlaysFirst = FALSE;
8234     firstMove = TRUE;
8235     whiteFlag = blackFlag = FALSE;
8236     userOfferedDraw = FALSE;
8237     hintRequested = bookRequested = FALSE;
8238     first.maybeThinking = FALSE;
8239     second.maybeThinking = FALSE;
8240     first.bookSuspend = FALSE; // [HGM] book
8241     second.bookSuspend = FALSE;
8242     thinkOutput[0] = NULLCHAR;
8243     lastHint[0] = NULLCHAR;
8244     ClearGameInfo(&gameInfo);
8245     gameInfo.variant = StringToVariant(appData.variant);
8246     ics_user_moved = ics_clock_paused = FALSE;
8247     ics_getting_history = H_FALSE;
8248     ics_gamenum = -1;
8249     white_holding[0] = black_holding[0] = NULLCHAR;
8250     ClearProgramStats();
8251     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8252
8253     ResetFrontEnd();
8254     ClearHighlights();
8255     flipView = appData.flipView;
8256     ClearPremoveHighlights();
8257     gotPremove = FALSE;
8258     alarmSounded = FALSE;
8259
8260     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8261     if(appData.serverMovesName != NULL) {
8262         /* [HGM] prepare to make moves file for broadcasting */
8263         clock_t t = clock();
8264         if(serverMoves != NULL) fclose(serverMoves);
8265         serverMoves = fopen(appData.serverMovesName, "r");
8266         if(serverMoves != NULL) {
8267             fclose(serverMoves);
8268             /* delay 15 sec before overwriting, so all clients can see end */
8269             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8270         }
8271         serverMoves = fopen(appData.serverMovesName, "w");
8272     }
8273
8274     ExitAnalyzeMode();
8275     gameMode = BeginningOfGame;
8276     ModeHighlight();
8277
8278     if(appData.icsActive) gameInfo.variant = VariantNormal;
8279     InitPosition(redraw);
8280     for (i = 0; i < MAX_MOVES; i++) {
8281         if (commentList[i] != NULL) {
8282             free(commentList[i]);
8283             commentList[i] = NULL;
8284         }
8285     }
8286
8287     ResetClocks();
8288     timeRemaining[0][0] = whiteTimeRemaining;
8289     timeRemaining[1][0] = blackTimeRemaining;
8290     if (first.pr == NULL) {
8291         StartChessProgram(&first);
8292     }
8293     if (init) {
8294             InitChessProgram(&first, startedFromSetupPosition);
8295     }
8296
8297     DisplayTitle("");
8298     DisplayMessage("", "");
8299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8300     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8301     return;
8302 }
8303
8304 void
8305 AutoPlayGameLoop()
8306 {
8307     for (;;) {
8308         if (!AutoPlayOneMove())
8309           return;
8310         if (matchMode || appData.timeDelay == 0)
8311           continue;
8312         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8313           return;
8314         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8315         break;
8316     }
8317 }
8318
8319
8320 int
8321 AutoPlayOneMove()
8322 {
8323     int fromX, fromY, toX, toY;
8324
8325     if (appData.debugMode) {
8326       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8327     }
8328
8329     if (gameMode != PlayFromGameFile)
8330       return FALSE;
8331
8332     if (currentMove >= forwardMostMove) {
8333       gameMode = EditGame;
8334       ModeHighlight();
8335
8336       /* [AS] Clear current move marker at the end of a game */
8337       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8338
8339       return FALSE;
8340     }
8341
8342     toX = moveList[currentMove][2] - AAA;
8343     toY = moveList[currentMove][3] - ONE;
8344
8345     if (moveList[currentMove][1] == '@') {
8346         if (appData.highlightLastMove) {
8347             SetHighlights(-1, -1, toX, toY);
8348         }
8349     } else {
8350         fromX = moveList[currentMove][0] - AAA;
8351         fromY = moveList[currentMove][1] - ONE;
8352
8353         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8354
8355         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8356
8357         if (appData.highlightLastMove) {
8358             SetHighlights(fromX, fromY, toX, toY);
8359         }
8360     }
8361     DisplayMove(currentMove);
8362     SendMoveToProgram(currentMove++, &first);
8363     DisplayBothClocks();
8364     DrawPosition(FALSE, boards[currentMove]);
8365     // [HGM] PV info: always display, routine tests if empty
8366     DisplayComment(currentMove - 1, commentList[currentMove]);
8367     return TRUE;
8368 }
8369
8370
8371 int
8372 LoadGameOneMove(readAhead)
8373      ChessMove readAhead;
8374 {
8375     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8376     char promoChar = NULLCHAR;
8377     ChessMove moveType;
8378     char move[MSG_SIZ];
8379     char *p, *q;
8380
8381     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8382         gameMode != AnalyzeMode && gameMode != Training) {
8383         gameFileFP = NULL;
8384         return FALSE;
8385     }
8386
8387     yyboardindex = forwardMostMove;
8388     if (readAhead != (ChessMove)0) {
8389       moveType = readAhead;
8390     } else {
8391       if (gameFileFP == NULL)
8392           return FALSE;
8393       moveType = (ChessMove) yylex();
8394     }
8395
8396     done = FALSE;
8397     switch (moveType) {
8398       case Comment:
8399         if (appData.debugMode)
8400           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8401         p = yy_text;
8402         if (*p == '{' || *p == '[' || *p == '(') {
8403             p[strlen(p) - 1] = NULLCHAR;
8404             p++;
8405         }
8406
8407         /* append the comment but don't display it */
8408         while (*p == '\n') p++;
8409         AppendComment(currentMove, p);
8410         return TRUE;
8411
8412       case WhiteCapturesEnPassant:
8413       case BlackCapturesEnPassant:
8414       case WhitePromotionChancellor:
8415       case BlackPromotionChancellor:
8416       case WhitePromotionArchbishop:
8417       case BlackPromotionArchbishop:
8418       case WhitePromotionCentaur:
8419       case BlackPromotionCentaur:
8420       case WhitePromotionQueen:
8421       case BlackPromotionQueen:
8422       case WhitePromotionRook:
8423       case BlackPromotionRook:
8424       case WhitePromotionBishop:
8425       case BlackPromotionBishop:
8426       case WhitePromotionKnight:
8427       case BlackPromotionKnight:
8428       case WhitePromotionKing:
8429       case BlackPromotionKing:
8430       case NormalMove:
8431       case WhiteKingSideCastle:
8432       case WhiteQueenSideCastle:
8433       case BlackKingSideCastle:
8434       case BlackQueenSideCastle:
8435       case WhiteKingSideCastleWild:
8436       case WhiteQueenSideCastleWild:
8437       case BlackKingSideCastleWild:
8438       case BlackQueenSideCastleWild:
8439       /* PUSH Fabien */
8440       case WhiteHSideCastleFR:
8441       case WhiteASideCastleFR:
8442       case BlackHSideCastleFR:
8443       case BlackASideCastleFR:
8444       /* POP Fabien */
8445         if (appData.debugMode)
8446           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8447         fromX = currentMoveString[0] - AAA;
8448         fromY = currentMoveString[1] - ONE;
8449         toX = currentMoveString[2] - AAA;
8450         toY = currentMoveString[3] - ONE;
8451         promoChar = currentMoveString[4];
8452         break;
8453
8454       case WhiteDrop:
8455       case BlackDrop:
8456         if (appData.debugMode)
8457           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8458         fromX = moveType == WhiteDrop ?
8459           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8460         (int) CharToPiece(ToLower(currentMoveString[0]));
8461         fromY = DROP_RANK;
8462         toX = currentMoveString[2] - AAA;
8463         toY = currentMoveString[3] - ONE;
8464         break;
8465
8466       case WhiteWins:
8467       case BlackWins:
8468       case GameIsDrawn:
8469       case GameUnfinished:
8470         if (appData.debugMode)
8471           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8472         p = strchr(yy_text, '{');
8473         if (p == NULL) p = strchr(yy_text, '(');
8474         if (p == NULL) {
8475             p = yy_text;
8476             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8477         } else {
8478             q = strchr(p, *p == '{' ? '}' : ')');
8479             if (q != NULL) *q = NULLCHAR;
8480             p++;
8481         }
8482         GameEnds(moveType, p, GE_FILE);
8483         done = TRUE;
8484         if (cmailMsgLoaded) {
8485             ClearHighlights();
8486             flipView = WhiteOnMove(currentMove);
8487             if (moveType == GameUnfinished) flipView = !flipView;
8488             if (appData.debugMode)
8489               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8490         }
8491         break;
8492
8493       case (ChessMove) 0:       /* end of file */
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parser hit end of file\n");
8496         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8497                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8498           case MT_NONE:
8499           case MT_CHECK:
8500             break;
8501           case MT_CHECKMATE:
8502           case MT_STAINMATE:
8503             if (WhiteOnMove(currentMove)) {
8504                 GameEnds(BlackWins, "Black mates", GE_FILE);
8505             } else {
8506                 GameEnds(WhiteWins, "White mates", GE_FILE);
8507             }
8508             break;
8509           case MT_STALEMATE:
8510             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8511             break;
8512         }
8513         done = TRUE;
8514         break;
8515
8516       case MoveNumberOne:
8517         if (lastLoadGameStart == GNUChessGame) {
8518             /* GNUChessGames have numbers, but they aren't move numbers */
8519             if (appData.debugMode)
8520               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8521                       yy_text, (int) moveType);
8522             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8523         }
8524         /* else fall thru */
8525
8526       case XBoardGame:
8527       case GNUChessGame:
8528       case PGNTag:
8529         /* Reached start of next game in file */
8530         if (appData.debugMode)
8531           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8532         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8533                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8534           case MT_NONE:
8535           case MT_CHECK:
8536             break;
8537           case MT_CHECKMATE:
8538           case MT_STAINMATE:
8539             if (WhiteOnMove(currentMove)) {
8540                 GameEnds(BlackWins, "Black mates", GE_FILE);
8541             } else {
8542                 GameEnds(WhiteWins, "White mates", GE_FILE);
8543             }
8544             break;
8545           case MT_STALEMATE:
8546             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8547             break;
8548         }
8549         done = TRUE;
8550         break;
8551
8552       case PositionDiagram:     /* should not happen; ignore */
8553       case ElapsedTime:         /* ignore */
8554       case NAG:                 /* ignore */
8555         if (appData.debugMode)
8556           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8557                   yy_text, (int) moveType);
8558         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8559
8560       case IllegalMove:
8561         if (appData.testLegality) {
8562             if (appData.debugMode)
8563               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8564             sprintf(move, _("Illegal move: %d.%s%s"),
8565                     (forwardMostMove / 2) + 1,
8566                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8567             DisplayError(move, 0);
8568             done = TRUE;
8569         } else {
8570             if (appData.debugMode)
8571               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8572                       yy_text, currentMoveString);
8573             fromX = currentMoveString[0] - AAA;
8574             fromY = currentMoveString[1] - ONE;
8575             toX = currentMoveString[2] - AAA;
8576             toY = currentMoveString[3] - ONE;
8577             promoChar = currentMoveString[4];
8578         }
8579         break;
8580
8581       case AmbiguousMove:
8582         if (appData.debugMode)
8583           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8584         sprintf(move, _("Ambiguous move: %d.%s%s"),
8585                 (forwardMostMove / 2) + 1,
8586                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8587         DisplayError(move, 0);
8588         done = TRUE;
8589         break;
8590
8591       default:
8592       case ImpossibleMove:
8593         if (appData.debugMode)
8594           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8595         sprintf(move, _("Illegal move: %d.%s%s"),
8596                 (forwardMostMove / 2) + 1,
8597                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8598         DisplayError(move, 0);
8599         done = TRUE;
8600         break;
8601     }
8602
8603     if (done) {
8604         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8605             DrawPosition(FALSE, boards[currentMove]);
8606             DisplayBothClocks();
8607             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8608               DisplayComment(currentMove - 1, commentList[currentMove]);
8609         }
8610         (void) StopLoadGameTimer();
8611         gameFileFP = NULL;
8612         cmailOldMove = forwardMostMove;
8613         return FALSE;
8614     } else {
8615         /* currentMoveString is set as a side-effect of yylex */
8616         strcat(currentMoveString, "\n");
8617         strcpy(moveList[forwardMostMove], currentMoveString);
8618
8619         thinkOutput[0] = NULLCHAR;
8620         MakeMove(fromX, fromY, toX, toY, promoChar);
8621         currentMove = forwardMostMove;
8622         return TRUE;
8623     }
8624 }
8625
8626 /* Load the nth game from the given file */
8627 int
8628 LoadGameFromFile(filename, n, title, useList)
8629      char *filename;
8630      int n;
8631      char *title;
8632      /*Boolean*/ int useList;
8633 {
8634     FILE *f;
8635     char buf[MSG_SIZ];
8636
8637     if (strcmp(filename, "-") == 0) {
8638         f = stdin;
8639         title = "stdin";
8640     } else {
8641         f = fopen(filename, "rb");
8642         if (f == NULL) {
8643           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8644             DisplayError(buf, errno);
8645             return FALSE;
8646         }
8647     }
8648     if (fseek(f, 0, 0) == -1) {
8649         /* f is not seekable; probably a pipe */
8650         useList = FALSE;
8651     }
8652     if (useList && n == 0) {
8653         int error = GameListBuild(f);
8654         if (error) {
8655             DisplayError(_("Cannot build game list"), error);
8656         } else if (!ListEmpty(&gameList) &&
8657                    ((ListGame *) gameList.tailPred)->number > 1) {
8658             GameListPopUp(f, title);
8659             return TRUE;
8660         }
8661         GameListDestroy();
8662         n = 1;
8663     }
8664     if (n == 0) n = 1;
8665     return LoadGame(f, n, title, FALSE);
8666 }
8667
8668
8669 void
8670 MakeRegisteredMove()
8671 {
8672     int fromX, fromY, toX, toY;
8673     char promoChar;
8674     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8675         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8676           case CMAIL_MOVE:
8677           case CMAIL_DRAW:
8678             if (appData.debugMode)
8679               fprintf(debugFP, "Restoring %s for game %d\n",
8680                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8681
8682             thinkOutput[0] = NULLCHAR;
8683             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8684             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8685             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8686             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8687             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8688             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8689             MakeMove(fromX, fromY, toX, toY, promoChar);
8690             ShowMove(fromX, fromY, toX, toY);
8691
8692             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8693                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8694               case MT_NONE:
8695               case MT_CHECK:
8696                 break;
8697
8698               case MT_CHECKMATE:
8699               case MT_STAINMATE:
8700                 if (WhiteOnMove(currentMove)) {
8701                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8702                 } else {
8703                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8704                 }
8705                 break;
8706
8707               case MT_STALEMATE:
8708                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8709                 break;
8710             }
8711
8712             break;
8713
8714           case CMAIL_RESIGN:
8715             if (WhiteOnMove(currentMove)) {
8716                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8717             } else {
8718                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8719             }
8720             break;
8721
8722           case CMAIL_ACCEPT:
8723             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8724             break;
8725
8726           default:
8727             break;
8728         }
8729     }
8730
8731     return;
8732 }
8733
8734 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8735 int
8736 CmailLoadGame(f, gameNumber, title, useList)
8737      FILE *f;
8738      int gameNumber;
8739      char *title;
8740      int useList;
8741 {
8742     int retVal;
8743
8744     if (gameNumber > nCmailGames) {
8745         DisplayError(_("No more games in this message"), 0);
8746         return FALSE;
8747     }
8748     if (f == lastLoadGameFP) {
8749         int offset = gameNumber - lastLoadGameNumber;
8750         if (offset == 0) {
8751             cmailMsg[0] = NULLCHAR;
8752             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8753                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8754                 nCmailMovesRegistered--;
8755             }
8756             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8757             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8758                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8759             }
8760         } else {
8761             if (! RegisterMove()) return FALSE;
8762         }
8763     }
8764
8765     retVal = LoadGame(f, gameNumber, title, useList);
8766
8767     /* Make move registered during previous look at this game, if any */
8768     MakeRegisteredMove();
8769
8770     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8771         commentList[currentMove]
8772           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8773         DisplayComment(currentMove - 1, commentList[currentMove]);
8774     }
8775
8776     return retVal;
8777 }
8778
8779 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8780 int
8781 ReloadGame(offset)
8782      int offset;
8783 {
8784     int gameNumber = lastLoadGameNumber + offset;
8785     if (lastLoadGameFP == NULL) {
8786         DisplayError(_("No game has been loaded yet"), 0);
8787         return FALSE;
8788     }
8789     if (gameNumber <= 0) {
8790         DisplayError(_("Can't back up any further"), 0);
8791         return FALSE;
8792     }
8793     if (cmailMsgLoaded) {
8794         return CmailLoadGame(lastLoadGameFP, gameNumber,
8795                              lastLoadGameTitle, lastLoadGameUseList);
8796     } else {
8797         return LoadGame(lastLoadGameFP, gameNumber,
8798                         lastLoadGameTitle, lastLoadGameUseList);
8799     }
8800 }
8801
8802
8803
8804 /* Load the nth game from open file f */
8805 int
8806 LoadGame(f, gameNumber, title, useList)
8807      FILE *f;
8808      int gameNumber;
8809      char *title;
8810      int useList;
8811 {
8812     ChessMove cm;
8813     char buf[MSG_SIZ];
8814     int gn = gameNumber;
8815     ListGame *lg = NULL;
8816     int numPGNTags = 0;
8817     int err;
8818     GameMode oldGameMode;
8819     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8820
8821     if (appData.debugMode)
8822         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8823
8824     if (gameMode == Training )
8825         SetTrainingModeOff();
8826
8827     oldGameMode = gameMode;
8828     if (gameMode != BeginningOfGame) {
8829       Reset(FALSE, TRUE);
8830     }
8831
8832     gameFileFP = f;
8833     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8834         fclose(lastLoadGameFP);
8835     }
8836
8837     if (useList) {
8838         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8839
8840         if (lg) {
8841             fseek(f, lg->offset, 0);
8842             GameListHighlight(gameNumber);
8843             gn = 1;
8844         }
8845         else {
8846             DisplayError(_("Game number out of range"), 0);
8847             return FALSE;
8848         }
8849     } else {
8850         GameListDestroy();
8851         if (fseek(f, 0, 0) == -1) {
8852             if (f == lastLoadGameFP ?
8853                 gameNumber == lastLoadGameNumber + 1 :
8854                 gameNumber == 1) {
8855                 gn = 1;
8856             } else {
8857                 DisplayError(_("Can't seek on game file"), 0);
8858                 return FALSE;
8859             }
8860         }
8861     }
8862     lastLoadGameFP = f;
8863     lastLoadGameNumber = gameNumber;
8864     strcpy(lastLoadGameTitle, title);
8865     lastLoadGameUseList = useList;
8866
8867     yynewfile(f);
8868
8869     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8870       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8871                 lg->gameInfo.black);
8872             DisplayTitle(buf);
8873     } else if (*title != NULLCHAR) {
8874         if (gameNumber > 1) {
8875             sprintf(buf, "%s %d", title, gameNumber);
8876             DisplayTitle(buf);
8877         } else {
8878             DisplayTitle(title);
8879         }
8880     }
8881
8882     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8883         gameMode = PlayFromGameFile;
8884         ModeHighlight();
8885     }
8886
8887     currentMove = forwardMostMove = backwardMostMove = 0;
8888     CopyBoard(boards[0], initialPosition);
8889     StopClocks();
8890
8891     /*
8892      * Skip the first gn-1 games in the file.
8893      * Also skip over anything that precedes an identifiable
8894      * start of game marker, to avoid being confused by
8895      * garbage at the start of the file.  Currently
8896      * recognized start of game markers are the move number "1",
8897      * the pattern "gnuchess .* game", the pattern
8898      * "^[#;%] [^ ]* game file", and a PGN tag block.
8899      * A game that starts with one of the latter two patterns
8900      * will also have a move number 1, possibly
8901      * following a position diagram.
8902      * 5-4-02: Let's try being more lenient and allowing a game to
8903      * start with an unnumbered move.  Does that break anything?
8904      */
8905     cm = lastLoadGameStart = (ChessMove) 0;
8906     while (gn > 0) {
8907         yyboardindex = forwardMostMove;
8908         cm = (ChessMove) yylex();
8909         switch (cm) {
8910           case (ChessMove) 0:
8911             if (cmailMsgLoaded) {
8912                 nCmailGames = CMAIL_MAX_GAMES - gn;
8913             } else {
8914                 Reset(TRUE, TRUE);
8915                 DisplayError(_("Game not found in file"), 0);
8916             }
8917             return FALSE;
8918
8919           case GNUChessGame:
8920           case XBoardGame:
8921             gn--;
8922             lastLoadGameStart = cm;
8923             break;
8924
8925           case MoveNumberOne:
8926             switch (lastLoadGameStart) {
8927               case GNUChessGame:
8928               case XBoardGame:
8929               case PGNTag:
8930                 break;
8931               case MoveNumberOne:
8932               case (ChessMove) 0:
8933                 gn--;           /* count this game */
8934                 lastLoadGameStart = cm;
8935                 break;
8936               default:
8937                 /* impossible */
8938                 break;
8939             }
8940             break;
8941
8942           case PGNTag:
8943             switch (lastLoadGameStart) {
8944               case GNUChessGame:
8945               case PGNTag:
8946               case MoveNumberOne:
8947               case (ChessMove) 0:
8948                 gn--;           /* count this game */
8949                 lastLoadGameStart = cm;
8950                 break;
8951               case XBoardGame:
8952                 lastLoadGameStart = cm; /* game counted already */
8953                 break;
8954               default:
8955                 /* impossible */
8956                 break;
8957             }
8958             if (gn > 0) {
8959                 do {
8960                     yyboardindex = forwardMostMove;
8961                     cm = (ChessMove) yylex();
8962                 } while (cm == PGNTag || cm == Comment);
8963             }
8964             break;
8965
8966           case WhiteWins:
8967           case BlackWins:
8968           case GameIsDrawn:
8969             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8970                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8971                     != CMAIL_OLD_RESULT) {
8972                     nCmailResults ++ ;
8973                     cmailResult[  CMAIL_MAX_GAMES
8974                                 - gn - 1] = CMAIL_OLD_RESULT;
8975                 }
8976             }
8977             break;
8978
8979           case NormalMove:
8980             /* Only a NormalMove can be at the start of a game
8981              * without a position diagram. */
8982             if (lastLoadGameStart == (ChessMove) 0) {
8983               gn--;
8984               lastLoadGameStart = MoveNumberOne;
8985             }
8986             break;
8987
8988           default:
8989             break;
8990         }
8991     }
8992
8993     if (appData.debugMode)
8994       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8995
8996     if (cm == XBoardGame) {
8997         /* Skip any header junk before position diagram and/or move 1 */
8998         for (;;) {
8999             yyboardindex = forwardMostMove;
9000             cm = (ChessMove) yylex();
9001
9002             if (cm == (ChessMove) 0 ||
9003                 cm == GNUChessGame || cm == XBoardGame) {
9004                 /* Empty game; pretend end-of-file and handle later */
9005                 cm = (ChessMove) 0;
9006                 break;
9007             }
9008
9009             if (cm == MoveNumberOne || cm == PositionDiagram ||
9010                 cm == PGNTag || cm == Comment)
9011               break;
9012         }
9013     } else if (cm == GNUChessGame) {
9014         if (gameInfo.event != NULL) {
9015             free(gameInfo.event);
9016         }
9017         gameInfo.event = StrSave(yy_text);
9018     }
9019
9020     startedFromSetupPosition = FALSE;
9021     while (cm == PGNTag) {
9022         if (appData.debugMode)
9023           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9024         err = ParsePGNTag(yy_text, &gameInfo);
9025         if (!err) numPGNTags++;
9026
9027         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9028         if(gameInfo.variant != oldVariant) {
9029             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9030             InitPosition(TRUE);
9031             oldVariant = gameInfo.variant;
9032             if (appData.debugMode)
9033               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9034         }
9035
9036
9037         if (gameInfo.fen != NULL) {
9038           Board initial_position;
9039           startedFromSetupPosition = TRUE;
9040           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9041             Reset(TRUE, TRUE);
9042             DisplayError(_("Bad FEN position in file"), 0);
9043             return FALSE;
9044           }
9045           CopyBoard(boards[0], initial_position);
9046           if (blackPlaysFirst) {
9047             currentMove = forwardMostMove = backwardMostMove = 1;
9048             CopyBoard(boards[1], initial_position);
9049             strcpy(moveList[0], "");
9050             strcpy(parseList[0], "");
9051             timeRemaining[0][1] = whiteTimeRemaining;
9052             timeRemaining[1][1] = blackTimeRemaining;
9053             if (commentList[0] != NULL) {
9054               commentList[1] = commentList[0];
9055               commentList[0] = NULL;
9056             }
9057           } else {
9058             currentMove = forwardMostMove = backwardMostMove = 0;
9059           }
9060           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9061           {   int i;
9062               initialRulePlies = FENrulePlies;
9063               epStatus[forwardMostMove] = FENepStatus;
9064               for( i=0; i< nrCastlingRights; i++ )
9065                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9066           }
9067           yyboardindex = forwardMostMove;
9068           free(gameInfo.fen);
9069           gameInfo.fen = NULL;
9070         }
9071
9072         yyboardindex = forwardMostMove;
9073         cm = (ChessMove) yylex();
9074
9075         /* Handle comments interspersed among the tags */
9076         while (cm == Comment) {
9077             char *p;
9078             if (appData.debugMode)
9079               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9080             p = yy_text;
9081             if (*p == '{' || *p == '[' || *p == '(') {
9082                 p[strlen(p) - 1] = NULLCHAR;
9083                 p++;
9084             }
9085             while (*p == '\n') p++;
9086             AppendComment(currentMove, p);
9087             yyboardindex = forwardMostMove;
9088             cm = (ChessMove) yylex();
9089         }
9090     }
9091
9092     /* don't rely on existence of Event tag since if game was
9093      * pasted from clipboard the Event tag may not exist
9094      */
9095     if (numPGNTags > 0){
9096         char *tags;
9097         if (gameInfo.variant == VariantNormal) {
9098           gameInfo.variant = StringToVariant(gameInfo.event);
9099         }
9100         if (!matchMode) {
9101           if( appData.autoDisplayTags ) {
9102             tags = PGNTags(&gameInfo);
9103             TagsPopUp(tags, CmailMsg());
9104             free(tags);
9105           }
9106         }
9107     } else {
9108         /* Make something up, but don't display it now */
9109         SetGameInfo();
9110         TagsPopDown();
9111     }
9112
9113     if (cm == PositionDiagram) {
9114         int i, j;
9115         char *p;
9116         Board initial_position;
9117
9118         if (appData.debugMode)
9119           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9120
9121         if (!startedFromSetupPosition) {
9122             p = yy_text;
9123             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9124               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9125                 switch (*p) {
9126                   case '[':
9127                   case '-':
9128                   case ' ':
9129                   case '\t':
9130                   case '\n':
9131                   case '\r':
9132                     break;
9133                   default:
9134                     initial_position[i][j++] = CharToPiece(*p);
9135                     break;
9136                 }
9137             while (*p == ' ' || *p == '\t' ||
9138                    *p == '\n' || *p == '\r') p++;
9139
9140             if (strncmp(p, "black", strlen("black"))==0)
9141               blackPlaysFirst = TRUE;
9142             else
9143               blackPlaysFirst = FALSE;
9144             startedFromSetupPosition = TRUE;
9145
9146             CopyBoard(boards[0], initial_position);
9147             if (blackPlaysFirst) {
9148                 currentMove = forwardMostMove = backwardMostMove = 1;
9149                 CopyBoard(boards[1], initial_position);
9150                 strcpy(moveList[0], "");
9151                 strcpy(parseList[0], "");
9152                 timeRemaining[0][1] = whiteTimeRemaining;
9153                 timeRemaining[1][1] = blackTimeRemaining;
9154                 if (commentList[0] != NULL) {
9155                     commentList[1] = commentList[0];
9156                     commentList[0] = NULL;
9157                 }
9158             } else {
9159                 currentMove = forwardMostMove = backwardMostMove = 0;
9160             }
9161         }
9162         yyboardindex = forwardMostMove;
9163         cm = (ChessMove) yylex();
9164     }
9165
9166     if (first.pr == NoProc) {
9167         StartChessProgram(&first);
9168     }
9169     InitChessProgram(&first, FALSE);
9170     SendToProgram("force\n", &first);
9171     if (startedFromSetupPosition) {
9172         SendBoard(&first, forwardMostMove);
9173     if (appData.debugMode) {
9174         fprintf(debugFP, "Load Game\n");
9175     }
9176         DisplayBothClocks();
9177     }
9178
9179     /* [HGM] server: flag to write setup moves in broadcast file as one */
9180     loadFlag = appData.suppressLoadMoves;
9181
9182     while (cm == Comment) {
9183         char *p;
9184         if (appData.debugMode)
9185           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9186         p = yy_text;
9187         if (*p == '{' || *p == '[' || *p == '(') {
9188             p[strlen(p) - 1] = NULLCHAR;
9189             p++;
9190         }
9191         while (*p == '\n') p++;
9192         AppendComment(currentMove, p);
9193         yyboardindex = forwardMostMove;
9194         cm = (ChessMove) yylex();
9195     }
9196
9197     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9198         cm == WhiteWins || cm == BlackWins ||
9199         cm == GameIsDrawn || cm == GameUnfinished) {
9200         DisplayMessage("", _("No moves in game"));
9201         if (cmailMsgLoaded) {
9202             if (appData.debugMode)
9203               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9204             ClearHighlights();
9205             flipView = FALSE;
9206         }
9207         DrawPosition(FALSE, boards[currentMove]);
9208         DisplayBothClocks();
9209         gameMode = EditGame;
9210         ModeHighlight();
9211         gameFileFP = NULL;
9212         cmailOldMove = 0;
9213         return TRUE;
9214     }
9215
9216     // [HGM] PV info: routine tests if comment empty
9217     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9218         DisplayComment(currentMove - 1, commentList[currentMove]);
9219     }
9220     if (!matchMode && appData.timeDelay != 0)
9221       DrawPosition(FALSE, boards[currentMove]);
9222
9223     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9224       programStats.ok_to_send = 1;
9225     }
9226
9227     /* if the first token after the PGN tags is a move
9228      * and not move number 1, retrieve it from the parser
9229      */
9230     if (cm != MoveNumberOne)
9231         LoadGameOneMove(cm);
9232
9233     /* load the remaining moves from the file */
9234     while (LoadGameOneMove((ChessMove)0)) {
9235       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9236       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9237     }
9238
9239     /* rewind to the start of the game */
9240     currentMove = backwardMostMove;
9241
9242     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9243
9244     if (oldGameMode == AnalyzeFile ||
9245         oldGameMode == AnalyzeMode) {
9246       AnalyzeFileEvent();
9247     }
9248
9249     if (matchMode || appData.timeDelay == 0) {
9250       ToEndEvent();
9251       gameMode = EditGame;
9252       ModeHighlight();
9253     } else if (appData.timeDelay > 0) {
9254       AutoPlayGameLoop();
9255     }
9256
9257     if (appData.debugMode)
9258         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9259
9260     loadFlag = 0; /* [HGM] true game starts */
9261     return TRUE;
9262 }
9263
9264 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9265 int
9266 ReloadPosition(offset)
9267      int offset;
9268 {
9269     int positionNumber = lastLoadPositionNumber + offset;
9270     if (lastLoadPositionFP == NULL) {
9271         DisplayError(_("No position has been loaded yet"), 0);
9272         return FALSE;
9273     }
9274     if (positionNumber <= 0) {
9275         DisplayError(_("Can't back up any further"), 0);
9276         return FALSE;
9277     }
9278     return LoadPosition(lastLoadPositionFP, positionNumber,
9279                         lastLoadPositionTitle);
9280 }
9281
9282 /* Load the nth position from the given file */
9283 int
9284 LoadPositionFromFile(filename, n, title)
9285      char *filename;
9286      int n;
9287      char *title;
9288 {
9289     FILE *f;
9290     char buf[MSG_SIZ];
9291
9292     if (strcmp(filename, "-") == 0) {
9293         return LoadPosition(stdin, n, "stdin");
9294     } else {
9295         f = fopen(filename, "rb");
9296         if (f == NULL) {
9297             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9298             DisplayError(buf, errno);
9299             return FALSE;
9300         } else {
9301             return LoadPosition(f, n, title);
9302         }
9303     }
9304 }
9305
9306 /* Load the nth position from the given open file, and close it */
9307 int
9308 LoadPosition(f, positionNumber, title)
9309      FILE *f;
9310      int positionNumber;
9311      char *title;
9312 {
9313     char *p, line[MSG_SIZ];
9314     Board initial_position;
9315     int i, j, fenMode, pn;
9316
9317     if (gameMode == Training )
9318         SetTrainingModeOff();
9319
9320     if (gameMode != BeginningOfGame) {
9321         Reset(FALSE, TRUE);
9322     }
9323     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9324         fclose(lastLoadPositionFP);
9325     }
9326     if (positionNumber == 0) positionNumber = 1;
9327     lastLoadPositionFP = f;
9328     lastLoadPositionNumber = positionNumber;
9329     strcpy(lastLoadPositionTitle, title);
9330     if (first.pr == NoProc) {
9331       StartChessProgram(&first);
9332       InitChessProgram(&first, FALSE);
9333     }
9334     pn = positionNumber;
9335     if (positionNumber < 0) {
9336         /* Negative position number means to seek to that byte offset */
9337         if (fseek(f, -positionNumber, 0) == -1) {
9338             DisplayError(_("Can't seek on position file"), 0);
9339             return FALSE;
9340         };
9341         pn = 1;
9342     } else {
9343         if (fseek(f, 0, 0) == -1) {
9344             if (f == lastLoadPositionFP ?
9345                 positionNumber == lastLoadPositionNumber + 1 :
9346                 positionNumber == 1) {
9347                 pn = 1;
9348             } else {
9349                 DisplayError(_("Can't seek on position file"), 0);
9350                 return FALSE;
9351             }
9352         }
9353     }
9354     /* See if this file is FEN or old-style xboard */
9355     if (fgets(line, MSG_SIZ, f) == NULL) {
9356         DisplayError(_("Position not found in file"), 0);
9357         return FALSE;
9358     }
9359 #if 0
9360     switch (line[0]) {
9361       case '#':  case 'x':
9362       default:
9363         fenMode = FALSE;
9364         break;
9365       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9366       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9367       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9368       case '7':  case '8':  case '9':
9369       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9370       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9371       case 'C':  case 'W':             case 'c':  case 'w':
9372         fenMode = TRUE;
9373         break;
9374     }
9375 #else
9376     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9377     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9378 #endif
9379
9380     if (pn >= 2) {
9381         if (fenMode || line[0] == '#') pn--;
9382         while (pn > 0) {
9383             /* skip positions before number pn */
9384             if (fgets(line, MSG_SIZ, f) == NULL) {
9385                 Reset(TRUE, TRUE);
9386                 DisplayError(_("Position not found in file"), 0);
9387                 return FALSE;
9388             }
9389             if (fenMode || line[0] == '#') pn--;
9390         }
9391     }
9392
9393     if (fenMode) {
9394         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9395             DisplayError(_("Bad FEN position in file"), 0);
9396             return FALSE;
9397         }
9398     } else {
9399         (void) fgets(line, MSG_SIZ, f);
9400         (void) fgets(line, MSG_SIZ, f);
9401
9402         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9403             (void) fgets(line, MSG_SIZ, f);
9404             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9405                 if (*p == ' ')
9406                   continue;
9407                 initial_position[i][j++] = CharToPiece(*p);
9408             }
9409         }
9410
9411         blackPlaysFirst = FALSE;
9412         if (!feof(f)) {
9413             (void) fgets(line, MSG_SIZ, f);
9414             if (strncmp(line, "black", strlen("black"))==0)
9415               blackPlaysFirst = TRUE;
9416         }
9417     }
9418     startedFromSetupPosition = TRUE;
9419
9420     SendToProgram("force\n", &first);
9421     CopyBoard(boards[0], initial_position);
9422     if (blackPlaysFirst) {
9423         currentMove = forwardMostMove = backwardMostMove = 1;
9424         strcpy(moveList[0], "");
9425         strcpy(parseList[0], "");
9426         CopyBoard(boards[1], initial_position);
9427         DisplayMessage("", _("Black to play"));
9428     } else {
9429         currentMove = forwardMostMove = backwardMostMove = 0;
9430         DisplayMessage("", _("White to play"));
9431     }
9432           /* [HGM] copy FEN attributes as well */
9433           {   int i;
9434               initialRulePlies = FENrulePlies;
9435               epStatus[forwardMostMove] = FENepStatus;
9436               for( i=0; i< nrCastlingRights; i++ )
9437                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9438           }
9439     SendBoard(&first, forwardMostMove);
9440     if (appData.debugMode) {
9441 int i, j;
9442   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9443   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9444         fprintf(debugFP, "Load Position\n");
9445     }
9446
9447     if (positionNumber > 1) {
9448         sprintf(line, "%s %d", title, positionNumber);
9449         DisplayTitle(line);
9450     } else {
9451         DisplayTitle(title);
9452     }
9453     gameMode = EditGame;
9454     ModeHighlight();
9455     ResetClocks();
9456     timeRemaining[0][1] = whiteTimeRemaining;
9457     timeRemaining[1][1] = blackTimeRemaining;
9458     DrawPosition(FALSE, boards[currentMove]);
9459
9460     return TRUE;
9461 }
9462
9463
9464 void
9465 CopyPlayerNameIntoFileName(dest, src)
9466      char **dest, *src;
9467 {
9468     while (*src != NULLCHAR && *src != ',') {
9469         if (*src == ' ') {
9470             *(*dest)++ = '_';
9471             src++;
9472         } else {
9473             *(*dest)++ = *src++;
9474         }
9475     }
9476 }
9477
9478 char *DefaultFileName(ext)
9479      char *ext;
9480 {
9481     static char def[MSG_SIZ];
9482     char *p;
9483
9484     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9485         p = def;
9486         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9487         *p++ = '-';
9488         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9489         *p++ = '.';
9490         strcpy(p, ext);
9491     } else {
9492         def[0] = NULLCHAR;
9493     }
9494     return def;
9495 }
9496
9497 /* Save the current game to the given file */
9498 int
9499 SaveGameToFile(filename, append)
9500      char *filename;
9501      int append;
9502 {
9503     FILE *f;
9504     char buf[MSG_SIZ];
9505
9506     if (strcmp(filename, "-") == 0) {
9507         return SaveGame(stdout, 0, NULL);
9508     } else {
9509         f = fopen(filename, append ? "a" : "w");
9510         if (f == NULL) {
9511             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9512             DisplayError(buf, errno);
9513             return FALSE;
9514         } else {
9515             return SaveGame(f, 0, NULL);
9516         }
9517     }
9518 }
9519
9520 char *
9521 SavePart(str)
9522      char *str;
9523 {
9524     static char buf[MSG_SIZ];
9525     char *p;
9526
9527     p = strchr(str, ' ');
9528     if (p == NULL) return str;
9529     strncpy(buf, str, p - str);
9530     buf[p - str] = NULLCHAR;
9531     return buf;
9532 }
9533
9534 #define PGN_MAX_LINE 75
9535
9536 #define PGN_SIDE_WHITE  0
9537 #define PGN_SIDE_BLACK  1
9538
9539 /* [AS] */
9540 static int FindFirstMoveOutOfBook( int side )
9541 {
9542     int result = -1;
9543
9544     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9545         int index = backwardMostMove;
9546         int has_book_hit = 0;
9547
9548         if( (index % 2) != side ) {
9549             index++;
9550         }
9551
9552         while( index < forwardMostMove ) {
9553             /* Check to see if engine is in book */
9554             int depth = pvInfoList[index].depth;
9555             int score = pvInfoList[index].score;
9556             int in_book = 0;
9557
9558             if( depth <= 2 ) {
9559                 in_book = 1;
9560             }
9561             else if( score == 0 && depth == 63 ) {
9562                 in_book = 1; /* Zappa */
9563             }
9564             else if( score == 2 && depth == 99 ) {
9565                 in_book = 1; /* Abrok */
9566             }
9567
9568             has_book_hit += in_book;
9569
9570             if( ! in_book ) {
9571                 result = index;
9572
9573                 break;
9574             }
9575
9576             index += 2;
9577         }
9578     }
9579
9580     return result;
9581 }
9582
9583 /* [AS] */
9584 void GetOutOfBookInfo( char * buf )
9585 {
9586     int oob[2];
9587     int i;
9588     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9589
9590     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9591     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9592
9593     *buf = '\0';
9594
9595     if( oob[0] >= 0 || oob[1] >= 0 ) {
9596         for( i=0; i<2; i++ ) {
9597             int idx = oob[i];
9598
9599             if( idx >= 0 ) {
9600                 if( i > 0 && oob[0] >= 0 ) {
9601                     strcat( buf, "   " );
9602                 }
9603
9604                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9605                 sprintf( buf+strlen(buf), "%s%.2f",
9606                     pvInfoList[idx].score >= 0 ? "+" : "",
9607                     pvInfoList[idx].score / 100.0 );
9608             }
9609         }
9610     }
9611 }
9612
9613 /* Save game in PGN style and close the file */
9614 int
9615 SaveGamePGN(f)
9616      FILE *f;
9617 {
9618     int i, offset, linelen, newblock;
9619     time_t tm;
9620 //    char *movetext;
9621     char numtext[32];
9622     int movelen, numlen, blank;
9623     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9624
9625     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9626
9627     tm = time((time_t *) NULL);
9628
9629     PrintPGNTags(f, &gameInfo);
9630
9631     if (backwardMostMove > 0 || startedFromSetupPosition) {
9632         char *fen = PositionToFEN(backwardMostMove, NULL);
9633         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9634         fprintf(f, "\n{--------------\n");
9635         PrintPosition(f, backwardMostMove);
9636         fprintf(f, "--------------}\n");
9637         free(fen);
9638     }
9639     else {
9640         /* [AS] Out of book annotation */
9641         if( appData.saveOutOfBookInfo ) {
9642             char buf[64];
9643
9644             GetOutOfBookInfo( buf );
9645
9646             if( buf[0] != '\0' ) {
9647                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9648             }
9649         }
9650
9651         fprintf(f, "\n");
9652     }
9653
9654     i = backwardMostMove;
9655     linelen = 0;
9656     newblock = TRUE;
9657
9658     while (i < forwardMostMove) {
9659         /* Print comments preceding this move */
9660         if (commentList[i] != NULL) {
9661             if (linelen > 0) fprintf(f, "\n");
9662             fprintf(f, "{\n%s}\n", commentList[i]);
9663             linelen = 0;
9664             newblock = TRUE;
9665         }
9666
9667         /* Format move number */
9668         if ((i % 2) == 0) {
9669             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9670         } else {
9671             if (newblock) {
9672                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9673             } else {
9674                 numtext[0] = NULLCHAR;
9675             }
9676         }
9677         numlen = strlen(numtext);
9678         newblock = FALSE;
9679
9680         /* Print move number */
9681         blank = linelen > 0 && numlen > 0;
9682         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9683             fprintf(f, "\n");
9684             linelen = 0;
9685             blank = 0;
9686         }
9687         if (blank) {
9688             fprintf(f, " ");
9689             linelen++;
9690         }
9691         fprintf(f, numtext);
9692         linelen += numlen;
9693
9694         /* Get move */
9695         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9696         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9697 #if 0
9698         // SavePart already does this!
9699         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9700                 int p = movelen - 1;
9701                 if(move_buffer[p] == ' ') p--;
9702                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9703                     while(p && move_buffer[--p] != '(');
9704                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9705                 }
9706         }
9707 #endif
9708         /* Print move */
9709         blank = linelen > 0 && movelen > 0;
9710         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9711             fprintf(f, "\n");
9712             linelen = 0;
9713             blank = 0;
9714         }
9715         if (blank) {
9716             fprintf(f, " ");
9717             linelen++;
9718         }
9719         fprintf(f, move_buffer);
9720         linelen += movelen;
9721
9722         /* [AS] Add PV info if present */
9723         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9724             /* [HGM] add time */
9725             char buf[MSG_SIZ]; int seconds = 0;
9726
9727 #if 1
9728             if(i >= backwardMostMove) {
9729                 if(WhiteOnMove(i))
9730                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9731                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9732                 else
9733                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9734                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9735             }
9736             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9737 #else
9738             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9739 #endif
9740
9741             if( seconds <= 0) buf[0] = 0; else
9742             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9743                 seconds = (seconds + 4)/10; // round to full seconds
9744                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9745                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9746             }
9747
9748             sprintf( move_buffer, "{%s%.2f/%d%s}",
9749                 pvInfoList[i].score >= 0 ? "+" : "",
9750                 pvInfoList[i].score / 100.0,
9751                 pvInfoList[i].depth,
9752                 buf );
9753
9754             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9755
9756             /* Print score/depth */
9757             blank = linelen > 0 && movelen > 0;
9758             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9759                 fprintf(f, "\n");
9760                 linelen = 0;
9761                 blank = 0;
9762             }
9763             if (blank) {
9764                 fprintf(f, " ");
9765                 linelen++;
9766             }
9767             fprintf(f, move_buffer);
9768             linelen += movelen;
9769         }
9770
9771         i++;
9772     }
9773
9774     /* Start a new line */
9775     if (linelen > 0) fprintf(f, "\n");
9776
9777     /* Print comments after last move */
9778     if (commentList[i] != NULL) {
9779         fprintf(f, "{\n%s}\n", commentList[i]);
9780     }
9781
9782     /* Print result */
9783     if (gameInfo.resultDetails != NULL &&
9784         gameInfo.resultDetails[0] != NULLCHAR) {
9785         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9786                 PGNResult(gameInfo.result));
9787     } else {
9788         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9789     }
9790
9791     fclose(f);
9792     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9793     return TRUE;
9794 }
9795
9796 /* Save game in old style and close the file */
9797 int
9798 SaveGameOldStyle(f)
9799      FILE *f;
9800 {
9801     int i, offset;
9802     time_t tm;
9803
9804     tm = time((time_t *) NULL);
9805
9806     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9807     PrintOpponents(f);
9808
9809     if (backwardMostMove > 0 || startedFromSetupPosition) {
9810         fprintf(f, "\n[--------------\n");
9811         PrintPosition(f, backwardMostMove);
9812         fprintf(f, "--------------]\n");
9813     } else {
9814         fprintf(f, "\n");
9815     }
9816
9817     i = backwardMostMove;
9818     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9819
9820     while (i < forwardMostMove) {
9821         if (commentList[i] != NULL) {
9822             fprintf(f, "[%s]\n", commentList[i]);
9823         }
9824
9825         if ((i % 2) == 1) {
9826             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9827             i++;
9828         } else {
9829             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9830             i++;
9831             if (commentList[i] != NULL) {
9832                 fprintf(f, "\n");
9833                 continue;
9834             }
9835             if (i >= forwardMostMove) {
9836                 fprintf(f, "\n");
9837                 break;
9838             }
9839             fprintf(f, "%s\n", parseList[i]);
9840             i++;
9841         }
9842     }
9843
9844     if (commentList[i] != NULL) {
9845         fprintf(f, "[%s]\n", commentList[i]);
9846     }
9847
9848     /* This isn't really the old style, but it's close enough */
9849     if (gameInfo.resultDetails != NULL &&
9850         gameInfo.resultDetails[0] != NULLCHAR) {
9851         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9852                 gameInfo.resultDetails);
9853     } else {
9854         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9855     }
9856
9857     fclose(f);
9858     return TRUE;
9859 }
9860
9861 /* Save the current game to open file f and close the file */
9862 int
9863 SaveGame(f, dummy, dummy2)
9864      FILE *f;
9865      int dummy;
9866      char *dummy2;
9867 {
9868     if (gameMode == EditPosition) EditPositionDone();
9869     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9870     if (appData.oldSaveStyle)
9871       return SaveGameOldStyle(f);
9872     else
9873       return SaveGamePGN(f);
9874 }
9875
9876 /* Save the current position to the given file */
9877 int
9878 SavePositionToFile(filename)
9879      char *filename;
9880 {
9881     FILE *f;
9882     char buf[MSG_SIZ];
9883
9884     if (strcmp(filename, "-") == 0) {
9885         return SavePosition(stdout, 0, NULL);
9886     } else {
9887         f = fopen(filename, "a");
9888         if (f == NULL) {
9889             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9890             DisplayError(buf, errno);
9891             return FALSE;
9892         } else {
9893             SavePosition(f, 0, NULL);
9894             return TRUE;
9895         }
9896     }
9897 }
9898
9899 /* Save the current position to the given open file and close the file */
9900 int
9901 SavePosition(f, dummy, dummy2)
9902      FILE *f;
9903      int dummy;
9904      char *dummy2;
9905 {
9906     time_t tm;
9907     char *fen;
9908
9909     if (appData.oldSaveStyle) {
9910         tm = time((time_t *) NULL);
9911
9912         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9913         PrintOpponents(f);
9914         fprintf(f, "[--------------\n");
9915         PrintPosition(f, currentMove);
9916         fprintf(f, "--------------]\n");
9917     } else {
9918         fen = PositionToFEN(currentMove, NULL);
9919         fprintf(f, "%s\n", fen);
9920         free(fen);
9921     }
9922     fclose(f);
9923     return TRUE;
9924 }
9925
9926 void
9927 ReloadCmailMsgEvent(unregister)
9928      int unregister;
9929 {
9930 #if !WIN32
9931     static char *inFilename = NULL;
9932     static char *outFilename;
9933     int i;
9934     struct stat inbuf, outbuf;
9935     int status;
9936
9937     /* Any registered moves are unregistered if unregister is set, */
9938     /* i.e. invoked by the signal handler */
9939     if (unregister) {
9940         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9941             cmailMoveRegistered[i] = FALSE;
9942             if (cmailCommentList[i] != NULL) {
9943                 free(cmailCommentList[i]);
9944                 cmailCommentList[i] = NULL;
9945             }
9946         }
9947         nCmailMovesRegistered = 0;
9948     }
9949
9950     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9951         cmailResult[i] = CMAIL_NOT_RESULT;
9952     }
9953     nCmailResults = 0;
9954
9955     if (inFilename == NULL) {
9956         /* Because the filenames are static they only get malloced once  */
9957         /* and they never get freed                                      */
9958         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9959         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9960
9961         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9962         sprintf(outFilename, "%s.out", appData.cmailGameName);
9963     }
9964
9965     status = stat(outFilename, &outbuf);
9966     if (status < 0) {
9967         cmailMailedMove = FALSE;
9968     } else {
9969         status = stat(inFilename, &inbuf);
9970         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9971     }
9972
9973     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9974        counts the games, notes how each one terminated, etc.
9975
9976        It would be nice to remove this kludge and instead gather all
9977        the information while building the game list.  (And to keep it
9978        in the game list nodes instead of having a bunch of fixed-size
9979        parallel arrays.)  Note this will require getting each game's
9980        termination from the PGN tags, as the game list builder does
9981        not process the game moves.  --mann
9982        */
9983     cmailMsgLoaded = TRUE;
9984     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9985
9986     /* Load first game in the file or popup game menu */
9987     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9988
9989 #endif /* !WIN32 */
9990     return;
9991 }
9992
9993 int
9994 RegisterMove()
9995 {
9996     FILE *f;
9997     char string[MSG_SIZ];
9998
9999     if (   cmailMailedMove
10000         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10001         return TRUE;            /* Allow free viewing  */
10002     }
10003
10004     /* Unregister move to ensure that we don't leave RegisterMove        */
10005     /* with the move registered when the conditions for registering no   */
10006     /* longer hold                                                       */
10007     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10008         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10009         nCmailMovesRegistered --;
10010
10011         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10012           {
10013               free(cmailCommentList[lastLoadGameNumber - 1]);
10014               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10015           }
10016     }
10017
10018     if (cmailOldMove == -1) {
10019         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10020         return FALSE;
10021     }
10022
10023     if (currentMove > cmailOldMove + 1) {
10024         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10025         return FALSE;
10026     }
10027
10028     if (currentMove < cmailOldMove) {
10029         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10030         return FALSE;
10031     }
10032
10033     if (forwardMostMove > currentMove) {
10034         /* Silently truncate extra moves */
10035         TruncateGame();
10036     }
10037
10038     if (   (currentMove == cmailOldMove + 1)
10039         || (   (currentMove == cmailOldMove)
10040             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10041                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10042         if (gameInfo.result != GameUnfinished) {
10043             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10044         }
10045
10046         if (commentList[currentMove] != NULL) {
10047             cmailCommentList[lastLoadGameNumber - 1]
10048               = StrSave(commentList[currentMove]);
10049         }
10050         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10051
10052         if (appData.debugMode)
10053           fprintf(debugFP, "Saving %s for game %d\n",
10054                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10055
10056         sprintf(string,
10057                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10058
10059         f = fopen(string, "w");
10060         if (appData.oldSaveStyle) {
10061             SaveGameOldStyle(f); /* also closes the file */
10062
10063             sprintf(string, "%s.pos.out", appData.cmailGameName);
10064             f = fopen(string, "w");
10065             SavePosition(f, 0, NULL); /* also closes the file */
10066         } else {
10067             fprintf(f, "{--------------\n");
10068             PrintPosition(f, currentMove);
10069             fprintf(f, "--------------}\n\n");
10070
10071             SaveGame(f, 0, NULL); /* also closes the file*/
10072         }
10073
10074         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10075         nCmailMovesRegistered ++;
10076     } else if (nCmailGames == 1) {
10077         DisplayError(_("You have not made a move yet"), 0);
10078         return FALSE;
10079     }
10080
10081     return TRUE;
10082 }
10083
10084 void
10085 MailMoveEvent()
10086 {
10087 #if !WIN32
10088     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10089     FILE *commandOutput;
10090     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10091     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10092     int nBuffers;
10093     int i;
10094     int archived;
10095     char *arcDir;
10096
10097     if (! cmailMsgLoaded) {
10098         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10099         return;
10100     }
10101
10102     if (nCmailGames == nCmailResults) {
10103         DisplayError(_("No unfinished games"), 0);
10104         return;
10105     }
10106
10107 #if CMAIL_PROHIBIT_REMAIL
10108     if (cmailMailedMove) {
10109         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);
10110         DisplayError(msg, 0);
10111         return;
10112     }
10113 #endif
10114
10115     if (! (cmailMailedMove || RegisterMove())) return;
10116
10117     if (   cmailMailedMove
10118         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10119         sprintf(string, partCommandString,
10120                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10121         commandOutput = popen(string, "r");
10122
10123         if (commandOutput == NULL) {
10124             DisplayError(_("Failed to invoke cmail"), 0);
10125         } else {
10126             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10127                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10128             }
10129             if (nBuffers > 1) {
10130                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10131                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10132                 nBytes = MSG_SIZ - 1;
10133             } else {
10134                 (void) memcpy(msg, buffer, nBytes);
10135             }
10136             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10137
10138             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10139                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10140
10141                 archived = TRUE;
10142                 for (i = 0; i < nCmailGames; i ++) {
10143                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10144                         archived = FALSE;
10145                     }
10146                 }
10147                 if (   archived
10148                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10149                         != NULL)) {
10150                     sprintf(buffer, "%s/%s.%s.archive",
10151                             arcDir,
10152                             appData.cmailGameName,
10153                             gameInfo.date);
10154                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10155                     cmailMsgLoaded = FALSE;
10156                 }
10157             }
10158
10159             DisplayInformation(msg);
10160             pclose(commandOutput);
10161         }
10162     } else {
10163         if ((*cmailMsg) != '\0') {
10164             DisplayInformation(cmailMsg);
10165         }
10166     }
10167
10168     return;
10169 #endif /* !WIN32 */
10170 }
10171
10172 char *
10173 CmailMsg()
10174 {
10175 #if WIN32
10176     return NULL;
10177 #else
10178     int  prependComma = 0;
10179     char number[5];
10180     char string[MSG_SIZ];       /* Space for game-list */
10181     int  i;
10182
10183     if (!cmailMsgLoaded) return "";
10184
10185     if (cmailMailedMove) {
10186         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10187     } else {
10188         /* Create a list of games left */
10189         sprintf(string, "[");
10190         for (i = 0; i < nCmailGames; i ++) {
10191             if (! (   cmailMoveRegistered[i]
10192                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10193                 if (prependComma) {
10194                     sprintf(number, ",%d", i + 1);
10195                 } else {
10196                     sprintf(number, "%d", i + 1);
10197                     prependComma = 1;
10198                 }
10199
10200                 strcat(string, number);
10201             }
10202         }
10203         strcat(string, "]");
10204
10205         if (nCmailMovesRegistered + nCmailResults == 0) {
10206             switch (nCmailGames) {
10207               case 1:
10208                 sprintf(cmailMsg,
10209                         _("Still need to make move for game\n"));
10210                 break;
10211
10212               case 2:
10213                 sprintf(cmailMsg,
10214                         _("Still need to make moves for both games\n"));
10215                 break;
10216
10217               default:
10218                 sprintf(cmailMsg,
10219                         _("Still need to make moves for all %d games\n"),
10220                         nCmailGames);
10221                 break;
10222             }
10223         } else {
10224             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10225               case 1:
10226                 sprintf(cmailMsg,
10227                         _("Still need to make a move for game %s\n"),
10228                         string);
10229                 break;
10230
10231               case 0:
10232                 if (nCmailResults == nCmailGames) {
10233                     sprintf(cmailMsg, _("No unfinished games\n"));
10234                 } else {
10235                     sprintf(cmailMsg, _("Ready to send mail\n"));
10236                 }
10237                 break;
10238
10239               default:
10240                 sprintf(cmailMsg,
10241                         _("Still need to make moves for games %s\n"),
10242                         string);
10243             }
10244         }
10245     }
10246     return cmailMsg;
10247 #endif /* WIN32 */
10248 }
10249
10250 void
10251 ResetGameEvent()
10252 {
10253     if (gameMode == Training)
10254       SetTrainingModeOff();
10255
10256     Reset(TRUE, TRUE);
10257     cmailMsgLoaded = FALSE;
10258     if (appData.icsActive) {
10259       SendToICS(ics_prefix);
10260       SendToICS("refresh\n");
10261     }
10262 }
10263
10264 void
10265 ExitEvent(status)
10266      int status;
10267 {
10268     exiting++;
10269     if (exiting > 2) {
10270       /* Give up on clean exit */
10271       exit(status);
10272     }
10273     if (exiting > 1) {
10274       /* Keep trying for clean exit */
10275       return;
10276     }
10277
10278     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10279
10280     if (telnetISR != NULL) {
10281       RemoveInputSource(telnetISR);
10282     }
10283     if (icsPR != NoProc) {
10284       DestroyChildProcess(icsPR, TRUE);
10285     }
10286 #if 0
10287     /* Save game if resource set and not already saved by GameEnds() */
10288     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10289                              && forwardMostMove > 0) {
10290       if (*appData.saveGameFile != NULLCHAR) {
10291         SaveGameToFile(appData.saveGameFile, TRUE);
10292       } else if (appData.autoSaveGames) {
10293         AutoSaveGame();
10294       }
10295       if (*appData.savePositionFile != NULLCHAR) {
10296         SavePositionToFile(appData.savePositionFile);
10297       }
10298     }
10299     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10300 #else
10301     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10302     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10303 #endif
10304     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10305     /* make sure this other one finishes before killing it!                  */
10306     if(endingGame) { int count = 0;
10307         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10308         while(endingGame && count++ < 10) DoSleep(1);
10309         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10310     }
10311
10312     /* Kill off chess programs */
10313     if (first.pr != NoProc) {
10314         ExitAnalyzeMode();
10315
10316         DoSleep( appData.delayBeforeQuit );
10317         SendToProgram("quit\n", &first);
10318         DoSleep( appData.delayAfterQuit );
10319         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10320     }
10321     if (second.pr != NoProc) {
10322         DoSleep( appData.delayBeforeQuit );
10323         SendToProgram("quit\n", &second);
10324         DoSleep( appData.delayAfterQuit );
10325         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10326     }
10327     if (first.isr != NULL) {
10328         RemoveInputSource(first.isr);
10329     }
10330     if (second.isr != NULL) {
10331         RemoveInputSource(second.isr);
10332     }
10333
10334     ShutDownFrontEnd();
10335     exit(status);
10336 }
10337
10338 void
10339 PauseEvent()
10340 {
10341     if (appData.debugMode)
10342         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10343     if (pausing) {
10344         pausing = FALSE;
10345         ModeHighlight();
10346         if (gameMode == MachinePlaysWhite ||
10347             gameMode == MachinePlaysBlack) {
10348             StartClocks();
10349         } else {
10350             DisplayBothClocks();
10351         }
10352         if (gameMode == PlayFromGameFile) {
10353             if (appData.timeDelay >= 0)
10354                 AutoPlayGameLoop();
10355         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10356             Reset(FALSE, TRUE);
10357             SendToICS(ics_prefix);
10358             SendToICS("refresh\n");
10359         } else if (currentMove < forwardMostMove) {
10360             ForwardInner(forwardMostMove);
10361         }
10362         pauseExamInvalid = FALSE;
10363     } else {
10364         switch (gameMode) {
10365           default:
10366             return;
10367           case IcsExamining:
10368             pauseExamForwardMostMove = forwardMostMove;
10369             pauseExamInvalid = FALSE;
10370             /* fall through */
10371           case IcsObserving:
10372           case IcsPlayingWhite:
10373           case IcsPlayingBlack:
10374             pausing = TRUE;
10375             ModeHighlight();
10376             return;
10377           case PlayFromGameFile:
10378             (void) StopLoadGameTimer();
10379             pausing = TRUE;
10380             ModeHighlight();
10381             break;
10382           case BeginningOfGame:
10383             if (appData.icsActive) return;
10384             /* else fall through */
10385           case MachinePlaysWhite:
10386           case MachinePlaysBlack:
10387           case TwoMachinesPlay:
10388             if (forwardMostMove == 0)
10389               return;           /* don't pause if no one has moved */
10390             if ((gameMode == MachinePlaysWhite &&
10391                  !WhiteOnMove(forwardMostMove)) ||
10392                 (gameMode == MachinePlaysBlack &&
10393                  WhiteOnMove(forwardMostMove))) {
10394                 StopClocks();
10395             }
10396             pausing = TRUE;
10397             ModeHighlight();
10398             break;
10399         }
10400     }
10401 }
10402
10403 void
10404 EditCommentEvent()
10405 {
10406     char title[MSG_SIZ];
10407
10408     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10409         strcpy(title, _("Edit comment"));
10410     } else {
10411         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10412                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10413                 parseList[currentMove - 1]);
10414     }
10415
10416     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10417 }
10418
10419
10420 void
10421 EditTagsEvent()
10422 {
10423     char *tags = PGNTags(&gameInfo);
10424     EditTagsPopUp(tags);
10425     free(tags);
10426 }
10427
10428 void
10429 AnalyzeModeEvent()
10430 {
10431     if (appData.noChessProgram || gameMode == AnalyzeMode)
10432       return;
10433
10434     if (gameMode != AnalyzeFile) {
10435         if (!appData.icsEngineAnalyze) {
10436                EditGameEvent();
10437                if (gameMode != EditGame) return;
10438         }
10439         ResurrectChessProgram();
10440         SendToProgram("analyze\n", &first);
10441         first.analyzing = TRUE;
10442         /*first.maybeThinking = TRUE;*/
10443         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10444         AnalysisPopUp(_("Analysis"),
10445                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10446     }
10447     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10448     pausing = FALSE;
10449     ModeHighlight();
10450     SetGameInfo();
10451
10452     StartAnalysisClock();
10453     GetTimeMark(&lastNodeCountTime);
10454     lastNodeCount = 0;
10455 }
10456
10457 void
10458 AnalyzeFileEvent()
10459 {
10460     if (appData.noChessProgram || gameMode == AnalyzeFile)
10461       return;
10462
10463     if (gameMode != AnalyzeMode) {
10464         EditGameEvent();
10465         if (gameMode != EditGame) return;
10466         ResurrectChessProgram();
10467         SendToProgram("analyze\n", &first);
10468         first.analyzing = TRUE;
10469         /*first.maybeThinking = TRUE;*/
10470         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10471         AnalysisPopUp(_("Analysis"),
10472                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10473     }
10474     gameMode = AnalyzeFile;
10475     pausing = FALSE;
10476     ModeHighlight();
10477     SetGameInfo();
10478
10479     StartAnalysisClock();
10480     GetTimeMark(&lastNodeCountTime);
10481     lastNodeCount = 0;
10482 }
10483
10484 void
10485 MachineWhiteEvent()
10486 {
10487     char buf[MSG_SIZ];
10488     char *bookHit = NULL;
10489
10490     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10491       return;
10492
10493
10494     if (gameMode == PlayFromGameFile ||
10495         gameMode == TwoMachinesPlay  ||
10496         gameMode == Training         ||
10497         gameMode == AnalyzeMode      ||
10498         gameMode == EndOfGame)
10499         EditGameEvent();
10500
10501     if (gameMode == EditPosition)
10502         EditPositionDone();
10503
10504     if (!WhiteOnMove(currentMove)) {
10505         DisplayError(_("It is not White's turn"), 0);
10506         return;
10507     }
10508
10509     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10510       ExitAnalyzeMode();
10511
10512     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10513         gameMode == AnalyzeFile)
10514         TruncateGame();
10515
10516     ResurrectChessProgram();    /* in case it isn't running */
10517     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10518         gameMode = MachinePlaysWhite;
10519         ResetClocks();
10520     } else
10521     gameMode = MachinePlaysWhite;
10522     pausing = FALSE;
10523     ModeHighlight();
10524     SetGameInfo();
10525     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10526     DisplayTitle(buf);
10527     if (first.sendName) {
10528       sprintf(buf, "name %s\n", gameInfo.black);
10529       SendToProgram(buf, &first);
10530     }
10531     if (first.sendTime) {
10532       if (first.useColors) {
10533         SendToProgram("black\n", &first); /*gnu kludge*/
10534       }
10535       SendTimeRemaining(&first, TRUE);
10536     }
10537     if (first.useColors) {
10538       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10539     }
10540     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10541     SetMachineThinkingEnables();
10542     first.maybeThinking = TRUE;
10543     StartClocks();
10544
10545     if (appData.autoFlipView && !flipView) {
10546       flipView = !flipView;
10547       DrawPosition(FALSE, NULL);
10548       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10549     }
10550
10551     if(bookHit) { // [HGM] book: simulate book reply
10552         static char bookMove[MSG_SIZ]; // a bit generous?
10553
10554         programStats.nodes = programStats.depth = programStats.time =
10555         programStats.score = programStats.got_only_move = 0;
10556         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10557
10558         strcpy(bookMove, "move ");
10559         strcat(bookMove, bookHit);
10560         HandleMachineMove(bookMove, &first);
10561     }
10562 }
10563
10564 void
10565 MachineBlackEvent()
10566 {
10567     char buf[MSG_SIZ];
10568    char *bookHit = NULL;
10569
10570     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10571         return;
10572
10573
10574     if (gameMode == PlayFromGameFile ||
10575         gameMode == TwoMachinesPlay  ||
10576         gameMode == Training         ||
10577         gameMode == AnalyzeMode      ||
10578         gameMode == EndOfGame)
10579         EditGameEvent();
10580
10581     if (gameMode == EditPosition)
10582         EditPositionDone();
10583
10584     if (WhiteOnMove(currentMove)) {
10585         DisplayError(_("It is not Black's turn"), 0);
10586         return;
10587     }
10588
10589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10590       ExitAnalyzeMode();
10591
10592     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10593         gameMode == AnalyzeFile)
10594         TruncateGame();
10595
10596     ResurrectChessProgram();    /* in case it isn't running */
10597     gameMode = MachinePlaysBlack;
10598     pausing = FALSE;
10599     ModeHighlight();
10600     SetGameInfo();
10601     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10602     DisplayTitle(buf);
10603     if (first.sendName) {
10604       sprintf(buf, "name %s\n", gameInfo.white);
10605       SendToProgram(buf, &first);
10606     }
10607     if (first.sendTime) {
10608       if (first.useColors) {
10609         SendToProgram("white\n", &first); /*gnu kludge*/
10610       }
10611       SendTimeRemaining(&first, FALSE);
10612     }
10613     if (first.useColors) {
10614       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10615     }
10616     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10617     SetMachineThinkingEnables();
10618     first.maybeThinking = TRUE;
10619     StartClocks();
10620
10621     if (appData.autoFlipView && flipView) {
10622       flipView = !flipView;
10623       DrawPosition(FALSE, NULL);
10624       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10625     }
10626     if(bookHit) { // [HGM] book: simulate book reply
10627         static char bookMove[MSG_SIZ]; // a bit generous?
10628
10629         programStats.nodes = programStats.depth = programStats.time =
10630         programStats.score = programStats.got_only_move = 0;
10631         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10632
10633         strcpy(bookMove, "move ");
10634         strcat(bookMove, bookHit);
10635         HandleMachineMove(bookMove, &first);
10636     }
10637 }
10638
10639
10640 void
10641 DisplayTwoMachinesTitle()
10642 {
10643     char buf[MSG_SIZ];
10644     if (appData.matchGames > 0) {
10645         if (first.twoMachinesColor[0] == 'w') {
10646             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10647                     gameInfo.white, gameInfo.black,
10648                     first.matchWins, second.matchWins,
10649                     matchGame - 1 - (first.matchWins + second.matchWins));
10650         } else {
10651             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10652                     gameInfo.white, gameInfo.black,
10653                     second.matchWins, first.matchWins,
10654                     matchGame - 1 - (first.matchWins + second.matchWins));
10655         }
10656     } else {
10657         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10658     }
10659     DisplayTitle(buf);
10660 }
10661
10662 void
10663 TwoMachinesEvent P((void))
10664 {
10665     int i;
10666     char buf[MSG_SIZ];
10667     ChessProgramState *onmove;
10668     char *bookHit = NULL;
10669
10670     if (appData.noChessProgram) return;
10671
10672     switch (gameMode) {
10673       case TwoMachinesPlay:
10674         return;
10675       case MachinePlaysWhite:
10676       case MachinePlaysBlack:
10677         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10678             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10679             return;
10680         }
10681         /* fall through */
10682       case BeginningOfGame:
10683       case PlayFromGameFile:
10684       case EndOfGame:
10685         EditGameEvent();
10686         if (gameMode != EditGame) return;
10687         break;
10688       case EditPosition:
10689         EditPositionDone();
10690         break;
10691       case AnalyzeMode:
10692       case AnalyzeFile:
10693         ExitAnalyzeMode();
10694         break;
10695       case EditGame:
10696       default:
10697         break;
10698     }
10699
10700     forwardMostMove = currentMove;
10701     ResurrectChessProgram();    /* in case first program isn't running */
10702
10703     if (second.pr == NULL) {
10704         StartChessProgram(&second);
10705         if (second.protocolVersion == 1) {
10706           TwoMachinesEventIfReady();
10707         } else {
10708           /* kludge: allow timeout for initial "feature" command */
10709           FreezeUI();
10710           DisplayMessage("", _("Starting second chess program"));
10711           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10712         }
10713         return;
10714     }
10715     DisplayMessage("", "");
10716     InitChessProgram(&second, FALSE);
10717     SendToProgram("force\n", &second);
10718     if (startedFromSetupPosition) {
10719         SendBoard(&second, backwardMostMove);
10720     if (appData.debugMode) {
10721         fprintf(debugFP, "Two Machines\n");
10722     }
10723     }
10724     for (i = backwardMostMove; i < forwardMostMove; i++) {
10725         SendMoveToProgram(i, &second);
10726     }
10727
10728     gameMode = TwoMachinesPlay;
10729     pausing = FALSE;
10730     ModeHighlight();
10731     SetGameInfo();
10732     DisplayTwoMachinesTitle();
10733     firstMove = TRUE;
10734     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10735         onmove = &first;
10736     } else {
10737         onmove = &second;
10738     }
10739
10740     SendToProgram(first.computerString, &first);
10741     if (first.sendName) {
10742       sprintf(buf, "name %s\n", second.tidy);
10743       SendToProgram(buf, &first);
10744     }
10745     SendToProgram(second.computerString, &second);
10746     if (second.sendName) {
10747       sprintf(buf, "name %s\n", first.tidy);
10748       SendToProgram(buf, &second);
10749     }
10750
10751     ResetClocks();
10752     if (!first.sendTime || !second.sendTime) {
10753         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10754         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10755     }
10756     if (onmove->sendTime) {
10757       if (onmove->useColors) {
10758         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10759       }
10760       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10761     }
10762     if (onmove->useColors) {
10763       SendToProgram(onmove->twoMachinesColor, onmove);
10764     }
10765     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10766 //    SendToProgram("go\n", onmove);
10767     onmove->maybeThinking = TRUE;
10768     SetMachineThinkingEnables();
10769
10770     StartClocks();
10771
10772     if(bookHit) { // [HGM] book: simulate book reply
10773         static char bookMove[MSG_SIZ]; // a bit generous?
10774
10775         programStats.nodes = programStats.depth = programStats.time =
10776         programStats.score = programStats.got_only_move = 0;
10777         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10778
10779         strcpy(bookMove, "move ");
10780         strcat(bookMove, bookHit);
10781         HandleMachineMove(bookMove, &first);
10782     }
10783 }
10784
10785 void
10786 TrainingEvent()
10787 {
10788     if (gameMode == Training) {
10789       SetTrainingModeOff();
10790       gameMode = PlayFromGameFile;
10791       DisplayMessage("", _("Training mode off"));
10792     } else {
10793       gameMode = Training;
10794       animateTraining = appData.animate;
10795
10796       /* make sure we are not already at the end of the game */
10797       if (currentMove < forwardMostMove) {
10798         SetTrainingModeOn();
10799         DisplayMessage("", _("Training mode on"));
10800       } else {
10801         gameMode = PlayFromGameFile;
10802         DisplayError(_("Already at end of game"), 0);
10803       }
10804     }
10805     ModeHighlight();
10806 }
10807
10808 void
10809 IcsClientEvent()
10810 {
10811     if (!appData.icsActive) return;
10812     switch (gameMode) {
10813       case IcsPlayingWhite:
10814       case IcsPlayingBlack:
10815       case IcsObserving:
10816       case IcsIdle:
10817       case BeginningOfGame:
10818       case IcsExamining:
10819         return;
10820
10821       case EditGame:
10822         break;
10823
10824       case EditPosition:
10825         EditPositionDone();
10826         break;
10827
10828       case AnalyzeMode:
10829       case AnalyzeFile:
10830         ExitAnalyzeMode();
10831         break;
10832
10833       default:
10834         EditGameEvent();
10835         break;
10836     }
10837
10838     gameMode = IcsIdle;
10839     ModeHighlight();
10840     return;
10841 }
10842
10843
10844 void
10845 EditGameEvent()
10846 {
10847     int i;
10848
10849     switch (gameMode) {
10850       case Training:
10851         SetTrainingModeOff();
10852         break;
10853       case MachinePlaysWhite:
10854       case MachinePlaysBlack:
10855       case BeginningOfGame:
10856         SendToProgram("force\n", &first);
10857         SetUserThinkingEnables();
10858         break;
10859       case PlayFromGameFile:
10860         (void) StopLoadGameTimer();
10861         if (gameFileFP != NULL) {
10862             gameFileFP = NULL;
10863         }
10864         break;
10865       case EditPosition:
10866         EditPositionDone();
10867         break;
10868       case AnalyzeMode:
10869       case AnalyzeFile:
10870         ExitAnalyzeMode();
10871         SendToProgram("force\n", &first);
10872         break;
10873       case TwoMachinesPlay:
10874         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10875         ResurrectChessProgram();
10876         SetUserThinkingEnables();
10877         break;
10878       case EndOfGame:
10879         ResurrectChessProgram();
10880         break;
10881       case IcsPlayingBlack:
10882       case IcsPlayingWhite:
10883         DisplayError(_("Warning: You are still playing a game"), 0);
10884         break;
10885       case IcsObserving:
10886         DisplayError(_("Warning: You are still observing a game"), 0);
10887         break;
10888       case IcsExamining:
10889         DisplayError(_("Warning: You are still examining a game"), 0);
10890         break;
10891       case IcsIdle:
10892         break;
10893       case EditGame:
10894       default:
10895         return;
10896     }
10897
10898     pausing = FALSE;
10899     StopClocks();
10900     first.offeredDraw = second.offeredDraw = 0;
10901
10902     if (gameMode == PlayFromGameFile) {
10903         whiteTimeRemaining = timeRemaining[0][currentMove];
10904         blackTimeRemaining = timeRemaining[1][currentMove];
10905         DisplayTitle("");
10906     }
10907
10908     if (gameMode == MachinePlaysWhite ||
10909         gameMode == MachinePlaysBlack ||
10910         gameMode == TwoMachinesPlay ||
10911         gameMode == EndOfGame) {
10912         i = forwardMostMove;
10913         while (i > currentMove) {
10914             SendToProgram("undo\n", &first);
10915             i--;
10916         }
10917         whiteTimeRemaining = timeRemaining[0][currentMove];
10918         blackTimeRemaining = timeRemaining[1][currentMove];
10919         DisplayBothClocks();
10920         if (whiteFlag || blackFlag) {
10921             whiteFlag = blackFlag = 0;
10922         }
10923         DisplayTitle("");
10924     }
10925
10926     gameMode = EditGame;
10927     ModeHighlight();
10928     SetGameInfo();
10929 }
10930
10931
10932 void
10933 EditPositionEvent()
10934 {
10935     if (gameMode == EditPosition) {
10936         EditGameEvent();
10937         return;
10938     }
10939
10940     EditGameEvent();
10941     if (gameMode != EditGame) return;
10942
10943     gameMode = EditPosition;
10944     ModeHighlight();
10945     SetGameInfo();
10946     if (currentMove > 0)
10947       CopyBoard(boards[0], boards[currentMove]);
10948
10949     blackPlaysFirst = !WhiteOnMove(currentMove);
10950     ResetClocks();
10951     currentMove = forwardMostMove = backwardMostMove = 0;
10952     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10953     DisplayMove(-1);
10954 }
10955
10956 void
10957 ExitAnalyzeMode()
10958 {
10959     /* [DM] icsEngineAnalyze - possible call from other functions */
10960     if (appData.icsEngineAnalyze) {
10961         appData.icsEngineAnalyze = FALSE;
10962
10963         DisplayMessage("",_("Close ICS engine analyze..."));
10964     }
10965     if (first.analysisSupport && first.analyzing) {
10966       SendToProgram("exit\n", &first);
10967       first.analyzing = FALSE;
10968     }
10969     AnalysisPopDown();
10970     thinkOutput[0] = NULLCHAR;
10971 }
10972
10973 void
10974 EditPositionDone()
10975 {
10976     startedFromSetupPosition = TRUE;
10977     InitChessProgram(&first, FALSE);
10978     SendToProgram("force\n", &first);
10979     if (blackPlaysFirst) {
10980         strcpy(moveList[0], "");
10981         strcpy(parseList[0], "");
10982         currentMove = forwardMostMove = backwardMostMove = 1;
10983         CopyBoard(boards[1], boards[0]);
10984         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10985         { int i;
10986           epStatus[1] = epStatus[0];
10987           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10988         }
10989     } else {
10990         currentMove = forwardMostMove = backwardMostMove = 0;
10991     }
10992     SendBoard(&first, forwardMostMove);
10993     if (appData.debugMode) {
10994         fprintf(debugFP, "EditPosDone\n");
10995     }
10996     DisplayTitle("");
10997     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10998     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10999     gameMode = EditGame;
11000     ModeHighlight();
11001     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11002     ClearHighlights(); /* [AS] */
11003 }
11004
11005 /* Pause for `ms' milliseconds */
11006 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11007 void
11008 TimeDelay(ms)
11009      long ms;
11010 {
11011     TimeMark m1, m2;
11012
11013     GetTimeMark(&m1);
11014     do {
11015         GetTimeMark(&m2);
11016     } while (SubtractTimeMarks(&m2, &m1) < ms);
11017 }
11018
11019 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11020 void
11021 SendMultiLineToICS(buf)
11022      char *buf;
11023 {
11024     char temp[MSG_SIZ+1], *p;
11025     int len;
11026
11027     len = strlen(buf);
11028     if (len > MSG_SIZ)
11029       len = MSG_SIZ;
11030
11031     strncpy(temp, buf, len);
11032     temp[len] = 0;
11033
11034     p = temp;
11035     while (*p) {
11036         if (*p == '\n' || *p == '\r')
11037           *p = ' ';
11038         ++p;
11039     }
11040
11041     strcat(temp, "\n");
11042     SendToICS(temp);
11043     SendToPlayer(temp, strlen(temp));
11044 }
11045
11046 void
11047 SetWhiteToPlayEvent()
11048 {
11049     if (gameMode == EditPosition) {
11050         blackPlaysFirst = FALSE;
11051         DisplayBothClocks();    /* works because currentMove is 0 */
11052     } else if (gameMode == IcsExamining) {
11053         SendToICS(ics_prefix);
11054         SendToICS("tomove white\n");
11055     }
11056 }
11057
11058 void
11059 SetBlackToPlayEvent()
11060 {
11061     if (gameMode == EditPosition) {
11062         blackPlaysFirst = TRUE;
11063         currentMove = 1;        /* kludge */
11064         DisplayBothClocks();
11065         currentMove = 0;
11066     } else if (gameMode == IcsExamining) {
11067         SendToICS(ics_prefix);
11068         SendToICS("tomove black\n");
11069     }
11070 }
11071
11072 void
11073 EditPositionMenuEvent(selection, x, y)
11074      ChessSquare selection;
11075      int x, y;
11076 {
11077     char buf[MSG_SIZ];
11078     ChessSquare piece = boards[0][y][x];
11079
11080     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11081
11082     switch (selection) {
11083       case ClearBoard:
11084         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11085             SendToICS(ics_prefix);
11086             SendToICS("bsetup clear\n");
11087         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11088             SendToICS(ics_prefix);
11089             SendToICS("clearboard\n");
11090         } else {
11091             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11092                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11093                 for (y = 0; y < BOARD_HEIGHT; y++) {
11094                     if (gameMode == IcsExamining) {
11095                         if (boards[currentMove][y][x] != EmptySquare) {
11096                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11097                                     AAA + x, ONE + y);
11098                             SendToICS(buf);
11099                         }
11100                     } else {
11101                         boards[0][y][x] = p;
11102                     }
11103                 }
11104             }
11105         }
11106         if (gameMode == EditPosition) {
11107             DrawPosition(FALSE, boards[0]);
11108         }
11109         break;
11110
11111       case WhitePlay:
11112         SetWhiteToPlayEvent();
11113         break;
11114
11115       case BlackPlay:
11116         SetBlackToPlayEvent();
11117         break;
11118
11119       case EmptySquare:
11120         if (gameMode == IcsExamining) {
11121             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11122             SendToICS(buf);
11123         } else {
11124             boards[0][y][x] = EmptySquare;
11125             DrawPosition(FALSE, boards[0]);
11126         }
11127         break;
11128
11129       case PromotePiece:
11130         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11131            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11132             selection = (ChessSquare) (PROMOTED piece);
11133         } else if(piece == EmptySquare) selection = WhiteSilver;
11134         else selection = (ChessSquare)((int)piece - 1);
11135         goto defaultlabel;
11136
11137       case DemotePiece:
11138         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11139            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11140             selection = (ChessSquare) (DEMOTED piece);
11141         } else if(piece == EmptySquare) selection = BlackSilver;
11142         else selection = (ChessSquare)((int)piece + 1);
11143         goto defaultlabel;
11144
11145       case WhiteQueen:
11146       case BlackQueen:
11147         if(gameInfo.variant == VariantShatranj ||
11148            gameInfo.variant == VariantXiangqi  ||
11149            gameInfo.variant == VariantCourier    )
11150             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11151         goto defaultlabel;
11152
11153       case WhiteKing:
11154       case BlackKing:
11155         if(gameInfo.variant == VariantXiangqi)
11156             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11157         if(gameInfo.variant == VariantKnightmate)
11158             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11159       default:
11160         defaultlabel:
11161         if (gameMode == IcsExamining) {
11162             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11163                     PieceToChar(selection), AAA + x, ONE + y);
11164             SendToICS(buf);
11165         } else {
11166             boards[0][y][x] = selection;
11167             DrawPosition(FALSE, boards[0]);
11168         }
11169         break;
11170     }
11171 }
11172
11173
11174 void
11175 DropMenuEvent(selection, x, y)
11176      ChessSquare selection;
11177      int x, y;
11178 {
11179     ChessMove moveType;
11180
11181     switch (gameMode) {
11182       case IcsPlayingWhite:
11183       case MachinePlaysBlack:
11184         if (!WhiteOnMove(currentMove)) {
11185             DisplayMoveError(_("It is Black's turn"));
11186             return;
11187         }
11188         moveType = WhiteDrop;
11189         break;
11190       case IcsPlayingBlack:
11191       case MachinePlaysWhite:
11192         if (WhiteOnMove(currentMove)) {
11193             DisplayMoveError(_("It is White's turn"));
11194             return;
11195         }
11196         moveType = BlackDrop;
11197         break;
11198       case EditGame:
11199         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11200         break;
11201       default:
11202         return;
11203     }
11204
11205     if (moveType == BlackDrop && selection < BlackPawn) {
11206       selection = (ChessSquare) ((int) selection
11207                                  + (int) BlackPawn - (int) WhitePawn);
11208     }
11209     if (boards[currentMove][y][x] != EmptySquare) {
11210         DisplayMoveError(_("That square is occupied"));
11211         return;
11212     }
11213
11214     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11215 }
11216
11217 void
11218 AcceptEvent()
11219 {
11220     /* Accept a pending offer of any kind from opponent */
11221
11222     if (appData.icsActive) {
11223         SendToICS(ics_prefix);
11224         SendToICS("accept\n");
11225     } else if (cmailMsgLoaded) {
11226         if (currentMove == cmailOldMove &&
11227             commentList[cmailOldMove] != NULL &&
11228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229                    "Black offers a draw" : "White offers a draw")) {
11230             TruncateGame();
11231             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11232             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11233         } else {
11234             DisplayError(_("There is no pending offer on this move"), 0);
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11236         }
11237     } else {
11238         /* Not used for offers from chess program */
11239     }
11240 }
11241
11242 void
11243 DeclineEvent()
11244 {
11245     /* Decline a pending offer of any kind from opponent */
11246
11247     if (appData.icsActive) {
11248         SendToICS(ics_prefix);
11249         SendToICS("decline\n");
11250     } else if (cmailMsgLoaded) {
11251         if (currentMove == cmailOldMove &&
11252             commentList[cmailOldMove] != NULL &&
11253             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11254                    "Black offers a draw" : "White offers a draw")) {
11255 #ifdef NOTDEF
11256             AppendComment(cmailOldMove, "Draw declined");
11257             DisplayComment(cmailOldMove - 1, "Draw declined");
11258 #endif /*NOTDEF*/
11259         } else {
11260             DisplayError(_("There is no pending offer on this move"), 0);
11261         }
11262     } else {
11263         /* Not used for offers from chess program */
11264     }
11265 }
11266
11267 void
11268 RematchEvent()
11269 {
11270     /* Issue ICS rematch command */
11271     if (appData.icsActive) {
11272         SendToICS(ics_prefix);
11273         SendToICS("rematch\n");
11274     }
11275 }
11276
11277 void
11278 CallFlagEvent()
11279 {
11280     /* Call your opponent's flag (claim a win on time) */
11281     if (appData.icsActive) {
11282         SendToICS(ics_prefix);
11283         SendToICS("flag\n");
11284     } else {
11285         switch (gameMode) {
11286           default:
11287             return;
11288           case MachinePlaysWhite:
11289             if (whiteFlag) {
11290                 if (blackFlag)
11291                   GameEnds(GameIsDrawn, "Both players ran out of time",
11292                            GE_PLAYER);
11293                 else
11294                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11295             } else {
11296                 DisplayError(_("Your opponent is not out of time"), 0);
11297             }
11298             break;
11299           case MachinePlaysBlack:
11300             if (blackFlag) {
11301                 if (whiteFlag)
11302                   GameEnds(GameIsDrawn, "Both players ran out of time",
11303                            GE_PLAYER);
11304                 else
11305                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11306             } else {
11307                 DisplayError(_("Your opponent is not out of time"), 0);
11308             }
11309             break;
11310         }
11311     }
11312 }
11313
11314 void
11315 DrawEvent()
11316 {
11317     /* Offer draw or accept pending draw offer from opponent */
11318
11319     if (appData.icsActive) {
11320         /* Note: tournament rules require draw offers to be
11321            made after you make your move but before you punch
11322            your clock.  Currently ICS doesn't let you do that;
11323            instead, you immediately punch your clock after making
11324            a move, but you can offer a draw at any time. */
11325
11326         SendToICS(ics_prefix);
11327         SendToICS("draw\n");
11328     } else if (cmailMsgLoaded) {
11329         if (currentMove == cmailOldMove &&
11330             commentList[cmailOldMove] != NULL &&
11331             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11332                    "Black offers a draw" : "White offers a draw")) {
11333             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11334             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11335         } else if (currentMove == cmailOldMove + 1) {
11336             char *offer = WhiteOnMove(cmailOldMove) ?
11337               "White offers a draw" : "Black offers a draw";
11338             AppendComment(currentMove, offer);
11339             DisplayComment(currentMove - 1, offer);
11340             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11341         } else {
11342             DisplayError(_("You must make your move before offering a draw"), 0);
11343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11344         }
11345     } else if (first.offeredDraw) {
11346         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11347     } else {
11348         if (first.sendDrawOffers) {
11349             SendToProgram("draw\n", &first);
11350             userOfferedDraw = TRUE;
11351         }
11352     }
11353 }
11354
11355 void
11356 AdjournEvent()
11357 {
11358     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11359
11360     if (appData.icsActive) {
11361         SendToICS(ics_prefix);
11362         SendToICS("adjourn\n");
11363     } else {
11364         /* Currently GNU Chess doesn't offer or accept Adjourns */
11365     }
11366 }
11367
11368
11369 void
11370 AbortEvent()
11371 {
11372     /* Offer Abort or accept pending Abort offer from opponent */
11373
11374     if (appData.icsActive) {
11375         SendToICS(ics_prefix);
11376         SendToICS("abort\n");
11377     } else {
11378         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11379     }
11380 }
11381
11382 void
11383 ResignEvent()
11384 {
11385     /* Resign.  You can do this even if it's not your turn. */
11386
11387     if (appData.icsActive) {
11388         SendToICS(ics_prefix);
11389         SendToICS("resign\n");
11390     } else {
11391         switch (gameMode) {
11392           case MachinePlaysWhite:
11393             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11394             break;
11395           case MachinePlaysBlack:
11396             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11397             break;
11398           case EditGame:
11399             if (cmailMsgLoaded) {
11400                 TruncateGame();
11401                 if (WhiteOnMove(cmailOldMove)) {
11402                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11403                 } else {
11404                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11405                 }
11406                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11407             }
11408             break;
11409           default:
11410             break;
11411         }
11412     }
11413 }
11414
11415
11416 void
11417 StopObservingEvent()
11418 {
11419     /* Stop observing current games */
11420     SendToICS(ics_prefix);
11421     SendToICS("unobserve\n");
11422 }
11423
11424 void
11425 StopExaminingEvent()
11426 {
11427     /* Stop observing current game */
11428     SendToICS(ics_prefix);
11429     SendToICS("unexamine\n");
11430 }
11431
11432 void
11433 ForwardInner(target)
11434      int target;
11435 {
11436     int limit;
11437
11438     if (appData.debugMode)
11439         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11440                 target, currentMove, forwardMostMove);
11441
11442     if (gameMode == EditPosition)
11443       return;
11444
11445     if (gameMode == PlayFromGameFile && !pausing)
11446       PauseEvent();
11447
11448     if (gameMode == IcsExamining && pausing)
11449       limit = pauseExamForwardMostMove;
11450     else
11451       limit = forwardMostMove;
11452
11453     if (target > limit) target = limit;
11454
11455     if (target > 0 && moveList[target - 1][0]) {
11456         int fromX, fromY, toX, toY;
11457         toX = moveList[target - 1][2] - AAA;
11458         toY = moveList[target - 1][3] - ONE;
11459         if (moveList[target - 1][1] == '@') {
11460             if (appData.highlightLastMove) {
11461                 SetHighlights(-1, -1, toX, toY);
11462             }
11463         } else {
11464             fromX = moveList[target - 1][0] - AAA;
11465             fromY = moveList[target - 1][1] - ONE;
11466             if (target == currentMove + 1) {
11467                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11468             }
11469             if (appData.highlightLastMove) {
11470                 SetHighlights(fromX, fromY, toX, toY);
11471             }
11472         }
11473     }
11474     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11475         gameMode == Training || gameMode == PlayFromGameFile ||
11476         gameMode == AnalyzeFile) {
11477         while (currentMove < target) {
11478             SendMoveToProgram(currentMove++, &first);
11479         }
11480     } else {
11481         currentMove = target;
11482     }
11483
11484     if (gameMode == EditGame || gameMode == EndOfGame) {
11485         whiteTimeRemaining = timeRemaining[0][currentMove];
11486         blackTimeRemaining = timeRemaining[1][currentMove];
11487     }
11488     DisplayBothClocks();
11489     DisplayMove(currentMove - 1);
11490     DrawPosition(FALSE, boards[currentMove]);
11491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11493         DisplayComment(currentMove - 1, commentList[currentMove]);
11494     }
11495 }
11496
11497
11498 void
11499 ForwardEvent()
11500 {
11501     if (gameMode == IcsExamining && !pausing) {
11502         SendToICS(ics_prefix);
11503         SendToICS("forward\n");
11504     } else {
11505         ForwardInner(currentMove + 1);
11506     }
11507 }
11508
11509 void
11510 ToEndEvent()
11511 {
11512     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11513         /* to optimze, we temporarily turn off analysis mode while we feed
11514          * the remaining moves to the engine. Otherwise we get analysis output
11515          * after each move.
11516          */
11517         if (first.analysisSupport) {
11518           SendToProgram("exit\nforce\n", &first);
11519           first.analyzing = FALSE;
11520         }
11521     }
11522
11523     if (gameMode == IcsExamining && !pausing) {
11524         SendToICS(ics_prefix);
11525         SendToICS("forward 999999\n");
11526     } else {
11527         ForwardInner(forwardMostMove);
11528     }
11529
11530     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11531         /* we have fed all the moves, so reactivate analysis mode */
11532         SendToProgram("analyze\n", &first);
11533         first.analyzing = TRUE;
11534         /*first.maybeThinking = TRUE;*/
11535         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11536     }
11537 }
11538
11539 void
11540 BackwardInner(target)
11541      int target;
11542 {
11543     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11544
11545     if (appData.debugMode)
11546         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11547                 target, currentMove, forwardMostMove);
11548
11549     if (gameMode == EditPosition) return;
11550     if (currentMove <= backwardMostMove) {
11551         ClearHighlights();
11552         DrawPosition(full_redraw, boards[currentMove]);
11553         return;
11554     }
11555     if (gameMode == PlayFromGameFile && !pausing)
11556       PauseEvent();
11557
11558     if (moveList[target][0]) {
11559         int fromX, fromY, toX, toY;
11560         toX = moveList[target][2] - AAA;
11561         toY = moveList[target][3] - ONE;
11562         if (moveList[target][1] == '@') {
11563             if (appData.highlightLastMove) {
11564                 SetHighlights(-1, -1, toX, toY);
11565             }
11566         } else {
11567             fromX = moveList[target][0] - AAA;
11568             fromY = moveList[target][1] - ONE;
11569             if (target == currentMove - 1) {
11570                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11571             }
11572             if (appData.highlightLastMove) {
11573                 SetHighlights(fromX, fromY, toX, toY);
11574             }
11575         }
11576     }
11577     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11578         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11579         while (currentMove > target) {
11580             SendToProgram("undo\n", &first);
11581             currentMove--;
11582         }
11583     } else {
11584         currentMove = target;
11585     }
11586
11587     if (gameMode == EditGame || gameMode == EndOfGame) {
11588         whiteTimeRemaining = timeRemaining[0][currentMove];
11589         blackTimeRemaining = timeRemaining[1][currentMove];
11590     }
11591     DisplayBothClocks();
11592     DisplayMove(currentMove - 1);
11593     DrawPosition(full_redraw, boards[currentMove]);
11594     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11595     // [HGM] PV info: routine tests if comment empty
11596     DisplayComment(currentMove - 1, commentList[currentMove]);
11597 }
11598
11599 void
11600 BackwardEvent()
11601 {
11602     if (gameMode == IcsExamining && !pausing) {
11603         SendToICS(ics_prefix);
11604         SendToICS("backward\n");
11605     } else {
11606         BackwardInner(currentMove - 1);
11607     }
11608 }
11609
11610 void
11611 ToStartEvent()
11612 {
11613     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11614         /* to optimze, we temporarily turn off analysis mode while we undo
11615          * all the moves. Otherwise we get analysis output after each undo.
11616          */
11617         if (first.analysisSupport) {
11618           SendToProgram("exit\nforce\n", &first);
11619           first.analyzing = FALSE;
11620         }
11621     }
11622
11623     if (gameMode == IcsExamining && !pausing) {
11624         SendToICS(ics_prefix);
11625         SendToICS("backward 999999\n");
11626     } else {
11627         BackwardInner(backwardMostMove);
11628     }
11629
11630     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11631         /* we have fed all the moves, so reactivate analysis mode */
11632         SendToProgram("analyze\n", &first);
11633         first.analyzing = TRUE;
11634         /*first.maybeThinking = TRUE;*/
11635         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11636     }
11637 }
11638
11639 void
11640 ToNrEvent(int to)
11641 {
11642   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11643   if (to >= forwardMostMove) to = forwardMostMove;
11644   if (to <= backwardMostMove) to = backwardMostMove;
11645   if (to < currentMove) {
11646     BackwardInner(to);
11647   } else {
11648     ForwardInner(to);
11649   }
11650 }
11651
11652 void
11653 RevertEvent()
11654 {
11655     if (gameMode != IcsExamining) {
11656         DisplayError(_("You are not examining a game"), 0);
11657         return;
11658     }
11659     if (pausing) {
11660         DisplayError(_("You can't revert while pausing"), 0);
11661         return;
11662     }
11663     SendToICS(ics_prefix);
11664     SendToICS("revert\n");
11665 }
11666
11667 void
11668 RetractMoveEvent()
11669 {
11670     switch (gameMode) {
11671       case MachinePlaysWhite:
11672       case MachinePlaysBlack:
11673         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11674             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11675             return;
11676         }
11677         if (forwardMostMove < 2) return;
11678         currentMove = forwardMostMove = forwardMostMove - 2;
11679         whiteTimeRemaining = timeRemaining[0][currentMove];
11680         blackTimeRemaining = timeRemaining[1][currentMove];
11681         DisplayBothClocks();
11682         DisplayMove(currentMove - 1);
11683         ClearHighlights();/*!! could figure this out*/
11684         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11685         SendToProgram("remove\n", &first);
11686         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11687         break;
11688
11689       case BeginningOfGame:
11690       default:
11691         break;
11692
11693       case IcsPlayingWhite:
11694       case IcsPlayingBlack:
11695         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11696             SendToICS(ics_prefix);
11697             SendToICS("takeback 2\n");
11698         } else {
11699             SendToICS(ics_prefix);
11700             SendToICS("takeback 1\n");
11701         }
11702         break;
11703     }
11704 }
11705
11706 void
11707 MoveNowEvent()
11708 {
11709     ChessProgramState *cps;
11710
11711     switch (gameMode) {
11712       case MachinePlaysWhite:
11713         if (!WhiteOnMove(forwardMostMove)) {
11714             DisplayError(_("It is your turn"), 0);
11715             return;
11716         }
11717         cps = &first;
11718         break;
11719       case MachinePlaysBlack:
11720         if (WhiteOnMove(forwardMostMove)) {
11721             DisplayError(_("It is your turn"), 0);
11722             return;
11723         }
11724         cps = &first;
11725         break;
11726       case TwoMachinesPlay:
11727         if (WhiteOnMove(forwardMostMove) ==
11728             (first.twoMachinesColor[0] == 'w')) {
11729             cps = &first;
11730         } else {
11731             cps = &second;
11732         }
11733         break;
11734       case BeginningOfGame:
11735       default:
11736         return;
11737     }
11738     SendToProgram("?\n", cps);
11739 }
11740
11741 void
11742 TruncateGameEvent()
11743 {
11744     EditGameEvent();
11745     if (gameMode != EditGame) return;
11746     TruncateGame();
11747 }
11748
11749 void
11750 TruncateGame()
11751 {
11752     if (forwardMostMove > currentMove) {
11753         if (gameInfo.resultDetails != NULL) {
11754             free(gameInfo.resultDetails);
11755             gameInfo.resultDetails = NULL;
11756             gameInfo.result = GameUnfinished;
11757         }
11758         forwardMostMove = currentMove;
11759         HistorySet(parseList, backwardMostMove, forwardMostMove,
11760                    currentMove-1);
11761     }
11762 }
11763
11764 void
11765 HintEvent()
11766 {
11767     if (appData.noChessProgram) return;
11768     switch (gameMode) {
11769       case MachinePlaysWhite:
11770         if (WhiteOnMove(forwardMostMove)) {
11771             DisplayError(_("Wait until your turn"), 0);
11772             return;
11773         }
11774         break;
11775       case BeginningOfGame:
11776       case MachinePlaysBlack:
11777         if (!WhiteOnMove(forwardMostMove)) {
11778             DisplayError(_("Wait until your turn"), 0);
11779             return;
11780         }
11781         break;
11782       default:
11783         DisplayError(_("No hint available"), 0);
11784         return;
11785     }
11786     SendToProgram("hint\n", &first);
11787     hintRequested = TRUE;
11788 }
11789
11790 void
11791 BookEvent()
11792 {
11793     if (appData.noChessProgram) return;
11794     switch (gameMode) {
11795       case MachinePlaysWhite:
11796         if (WhiteOnMove(forwardMostMove)) {
11797             DisplayError(_("Wait until your turn"), 0);
11798             return;
11799         }
11800         break;
11801       case BeginningOfGame:
11802       case MachinePlaysBlack:
11803         if (!WhiteOnMove(forwardMostMove)) {
11804             DisplayError(_("Wait until your turn"), 0);
11805             return;
11806         }
11807         break;
11808       case EditPosition:
11809         EditPositionDone();
11810         break;
11811       case TwoMachinesPlay:
11812         return;
11813       default:
11814         break;
11815     }
11816     SendToProgram("bk\n", &first);
11817     bookOutput[0] = NULLCHAR;
11818     bookRequested = TRUE;
11819 }
11820
11821 void
11822 AboutGameEvent()
11823 {
11824     char *tags = PGNTags(&gameInfo);
11825     TagsPopUp(tags, CmailMsg());
11826     free(tags);
11827 }
11828
11829 /* end button procedures */
11830
11831 void
11832 PrintPosition(fp, move)
11833      FILE *fp;
11834      int move;
11835 {
11836     int i, j;
11837
11838     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11839         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11840             char c = PieceToChar(boards[move][i][j]);
11841             fputc(c == 'x' ? '.' : c, fp);
11842             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11843         }
11844     }
11845     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11846       fprintf(fp, "white to play\n");
11847     else
11848       fprintf(fp, "black to play\n");
11849 }
11850
11851 void
11852 PrintOpponents(fp)
11853      FILE *fp;
11854 {
11855     if (gameInfo.white != NULL) {
11856         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11857     } else {
11858         fprintf(fp, "\n");
11859     }
11860 }
11861
11862 /* Find last component of program's own name, using some heuristics */
11863 void
11864 TidyProgramName(prog, host, buf)
11865      char *prog, *host, buf[MSG_SIZ];
11866 {
11867     char *p, *q;
11868     int local = (strcmp(host, "localhost") == 0);
11869     while (!local && (p = strchr(prog, ';')) != NULL) {
11870         p++;
11871         while (*p == ' ') p++;
11872         prog = p;
11873     }
11874     if (*prog == '"' || *prog == '\'') {
11875         q = strchr(prog + 1, *prog);
11876     } else {
11877         q = strchr(prog, ' ');
11878     }
11879     if (q == NULL) q = prog + strlen(prog);
11880     p = q;
11881     while (p >= prog && *p != '/' && *p != '\\') p--;
11882     p++;
11883     if(p == prog && *p == '"') p++;
11884     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11885     memcpy(buf, p, q - p);
11886     buf[q - p] = NULLCHAR;
11887     if (!local) {
11888         strcat(buf, "@");
11889         strcat(buf, host);
11890     }
11891 }
11892
11893 char *
11894 TimeControlTagValue()
11895 {
11896     char buf[MSG_SIZ];
11897     if (!appData.clockMode) {
11898         strcpy(buf, "-");
11899     } else if (movesPerSession > 0) {
11900         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11901     } else if (timeIncrement == 0) {
11902         sprintf(buf, "%ld", timeControl/1000);
11903     } else {
11904         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11905     }
11906     return StrSave(buf);
11907 }
11908
11909 void
11910 SetGameInfo()
11911 {
11912     /* This routine is used only for certain modes */
11913     VariantClass v = gameInfo.variant;
11914     ClearGameInfo(&gameInfo);
11915     gameInfo.variant = v;
11916
11917     switch (gameMode) {
11918       case MachinePlaysWhite:
11919         gameInfo.event = StrSave( appData.pgnEventHeader );
11920         gameInfo.site = StrSave(HostName());
11921         gameInfo.date = PGNDate();
11922         gameInfo.round = StrSave("-");
11923         gameInfo.white = StrSave(first.tidy);
11924         gameInfo.black = StrSave(UserName());
11925         gameInfo.timeControl = TimeControlTagValue();
11926         break;
11927
11928       case MachinePlaysBlack:
11929         gameInfo.event = StrSave( appData.pgnEventHeader );
11930         gameInfo.site = StrSave(HostName());
11931         gameInfo.date = PGNDate();
11932         gameInfo.round = StrSave("-");
11933         gameInfo.white = StrSave(UserName());
11934         gameInfo.black = StrSave(first.tidy);
11935         gameInfo.timeControl = TimeControlTagValue();
11936         break;
11937
11938       case TwoMachinesPlay:
11939         gameInfo.event = StrSave( appData.pgnEventHeader );
11940         gameInfo.site = StrSave(HostName());
11941         gameInfo.date = PGNDate();
11942         if (matchGame > 0) {
11943             char buf[MSG_SIZ];
11944             sprintf(buf, "%d", matchGame);
11945             gameInfo.round = StrSave(buf);
11946         } else {
11947             gameInfo.round = StrSave("-");
11948         }
11949         if (first.twoMachinesColor[0] == 'w') {
11950             gameInfo.white = StrSave(first.tidy);
11951             gameInfo.black = StrSave(second.tidy);
11952         } else {
11953             gameInfo.white = StrSave(second.tidy);
11954             gameInfo.black = StrSave(first.tidy);
11955         }
11956         gameInfo.timeControl = TimeControlTagValue();
11957         break;
11958
11959       case EditGame:
11960         gameInfo.event = StrSave("Edited game");
11961         gameInfo.site = StrSave(HostName());
11962         gameInfo.date = PGNDate();
11963         gameInfo.round = StrSave("-");
11964         gameInfo.white = StrSave("-");
11965         gameInfo.black = StrSave("-");
11966         break;
11967
11968       case EditPosition:
11969         gameInfo.event = StrSave("Edited position");
11970         gameInfo.site = StrSave(HostName());
11971         gameInfo.date = PGNDate();
11972         gameInfo.round = StrSave("-");
11973         gameInfo.white = StrSave("-");
11974         gameInfo.black = StrSave("-");
11975         break;
11976
11977       case IcsPlayingWhite:
11978       case IcsPlayingBlack:
11979       case IcsObserving:
11980       case IcsExamining:
11981         break;
11982
11983       case PlayFromGameFile:
11984         gameInfo.event = StrSave("Game from non-PGN file");
11985         gameInfo.site = StrSave(HostName());
11986         gameInfo.date = PGNDate();
11987         gameInfo.round = StrSave("-");
11988         gameInfo.white = StrSave("?");
11989         gameInfo.black = StrSave("?");
11990         break;
11991
11992       default:
11993         break;
11994     }
11995 }
11996
11997 void
11998 ReplaceComment(index, text)
11999      int index;
12000      char *text;
12001 {
12002     int len;
12003
12004     while (*text == '\n') text++;
12005     len = strlen(text);
12006     while (len > 0 && text[len - 1] == '\n') len--;
12007
12008     if (commentList[index] != NULL)
12009       free(commentList[index]);
12010
12011     if (len == 0) {
12012         commentList[index] = NULL;
12013         return;
12014     }
12015     commentList[index] = (char *) malloc(len + 2);
12016     strncpy(commentList[index], text, len);
12017     commentList[index][len] = '\n';
12018     commentList[index][len + 1] = NULLCHAR;
12019 }
12020
12021 void
12022 CrushCRs(text)
12023      char *text;
12024 {
12025   char *p = text;
12026   char *q = text;
12027   char ch;
12028
12029   do {
12030     ch = *p++;
12031     if (ch == '\r') continue;
12032     *q++ = ch;
12033   } while (ch != '\0');
12034 }
12035
12036 void
12037 AppendComment(index, text)
12038      int index;
12039      char *text;
12040 {
12041     int oldlen, len;
12042     char *old;
12043
12044     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12045
12046     CrushCRs(text);
12047     while (*text == '\n') text++;
12048     len = strlen(text);
12049     while (len > 0 && text[len - 1] == '\n') len--;
12050
12051     if (len == 0) return;
12052
12053     if (commentList[index] != NULL) {
12054         old = commentList[index];
12055         oldlen = strlen(old);
12056         commentList[index] = (char *) malloc(oldlen + len + 2);
12057         strcpy(commentList[index], old);
12058         free(old);
12059         strncpy(&commentList[index][oldlen], text, len);
12060         commentList[index][oldlen + len] = '\n';
12061         commentList[index][oldlen + len + 1] = NULLCHAR;
12062     } else {
12063         commentList[index] = (char *) malloc(len + 2);
12064         strncpy(commentList[index], text, len);
12065         commentList[index][len] = '\n';
12066         commentList[index][len + 1] = NULLCHAR;
12067     }
12068 }
12069
12070 static char * FindStr( char * text, char * sub_text )
12071 {
12072     char * result = strstr( text, sub_text );
12073
12074     if( result != NULL ) {
12075         result += strlen( sub_text );
12076     }
12077
12078     return result;
12079 }
12080
12081 /* [AS] Try to extract PV info from PGN comment */
12082 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12083 char *GetInfoFromComment( int index, char * text )
12084 {
12085     char * sep = text;
12086
12087     if( text != NULL && index > 0 ) {
12088         int score = 0;
12089         int depth = 0;
12090         int time = -1, sec = 0, deci;
12091         char * s_eval = FindStr( text, "[%eval " );
12092         char * s_emt = FindStr( text, "[%emt " );
12093
12094         if( s_eval != NULL || s_emt != NULL ) {
12095             /* New style */
12096             char delim;
12097
12098             if( s_eval != NULL ) {
12099                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12100                     return text;
12101                 }
12102
12103                 if( delim != ']' ) {
12104                     return text;
12105                 }
12106             }
12107
12108             if( s_emt != NULL ) {
12109             }
12110         }
12111         else {
12112             /* We expect something like: [+|-]nnn.nn/dd */
12113             int score_lo = 0;
12114
12115             sep = strchr( text, '/' );
12116             if( sep == NULL || sep < (text+4) ) {
12117                 return text;
12118             }
12119
12120             time = -1; sec = -1; deci = -1;
12121             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12122                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12123                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12124                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12125                 return text;
12126             }
12127
12128             if( score_lo < 0 || score_lo >= 100 ) {
12129                 return text;
12130             }
12131
12132             if(sec >= 0) time = 600*time + 10*sec; else
12133             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12134
12135             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12136
12137             /* [HGM] PV time: now locate end of PV info */
12138             while( *++sep >= '0' && *sep <= '9'); // strip depth
12139             if(time >= 0)
12140             while( *++sep >= '0' && *sep <= '9'); // strip time
12141             if(sec >= 0)
12142             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12143             if(deci >= 0)
12144             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12145             while(*sep == ' ') sep++;
12146         }
12147
12148         if( depth <= 0 ) {
12149             return text;
12150         }
12151
12152         if( time < 0 ) {
12153             time = -1;
12154         }
12155
12156         pvInfoList[index-1].depth = depth;
12157         pvInfoList[index-1].score = score;
12158         pvInfoList[index-1].time  = 10*time; // centi-sec
12159     }
12160     return sep;
12161 }
12162
12163 void
12164 SendToProgram(message, cps)
12165      char *message;
12166      ChessProgramState *cps;
12167 {
12168     int count, outCount, error;
12169     char buf[MSG_SIZ];
12170
12171     if (cps->pr == NULL) return;
12172     Attention(cps);
12173
12174     if (appData.debugMode) {
12175         TimeMark now;
12176         GetTimeMark(&now);
12177         fprintf(debugFP, "%ld >%-6s: %s",
12178                 SubtractTimeMarks(&now, &programStartTime),
12179                 cps->which, message);
12180     }
12181
12182     count = strlen(message);
12183     outCount = OutputToProcess(cps->pr, message, count, &error);
12184     if (outCount < count && !exiting
12185                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12186         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12187         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12188             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12189                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12190                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12191             } else {
12192                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12193             }
12194             gameInfo.resultDetails = buf;
12195         }
12196         DisplayFatalError(buf, error, 1);
12197     }
12198 }
12199
12200 void
12201 ReceiveFromProgram(isr, closure, message, count, error)
12202      InputSourceRef isr;
12203      VOIDSTAR closure;
12204      char *message;
12205      int count;
12206      int error;
12207 {
12208     char *end_str;
12209     char buf[MSG_SIZ];
12210     ChessProgramState *cps = (ChessProgramState *)closure;
12211
12212     if (isr != cps->isr) return; /* Killed intentionally */
12213     if (count <= 0) {
12214         if (count == 0) {
12215             sprintf(buf,
12216                     _("Error: %s chess program (%s) exited unexpectedly"),
12217                     cps->which, cps->program);
12218         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12219                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12220                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12221                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12222                 } else {
12223                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12224                 }
12225                 gameInfo.resultDetails = buf;
12226             }
12227             RemoveInputSource(cps->isr);
12228             DisplayFatalError(buf, 0, 1);
12229         } else {
12230             sprintf(buf,
12231                     _("Error reading from %s chess program (%s)"),
12232                     cps->which, cps->program);
12233             RemoveInputSource(cps->isr);
12234
12235             /* [AS] Program is misbehaving badly... kill it */
12236             if( count == -2 ) {
12237                 DestroyChildProcess( cps->pr, 9 );
12238                 cps->pr = NoProc;
12239             }
12240
12241             DisplayFatalError(buf, error, 1);
12242         }
12243         return;
12244     }
12245
12246     if ((end_str = strchr(message, '\r')) != NULL)
12247       *end_str = NULLCHAR;
12248     if ((end_str = strchr(message, '\n')) != NULL)
12249       *end_str = NULLCHAR;
12250
12251     if (appData.debugMode) {
12252         TimeMark now; int print = 1;
12253         char *quote = ""; char c; int i;
12254
12255         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12256                 char start = message[0];
12257                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12258                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12259                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12260                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12261                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12262                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12263                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12264                         { quote = "# "; print = (appData.engineComments == 2); }
12265                 message[0] = start; // restore original message
12266         }
12267         if(print) {
12268                 GetTimeMark(&now);
12269                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12270                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12271                         quote,
12272                         message);
12273         }
12274     }
12275
12276     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12277     if (appData.icsEngineAnalyze) {
12278         if (strstr(message, "whisper") != NULL ||
12279              strstr(message, "kibitz") != NULL ||
12280             strstr(message, "tellics") != NULL) return;
12281     }
12282
12283     HandleMachineMove(message, cps);
12284 }
12285
12286
12287 void
12288 SendTimeControl(cps, mps, tc, inc, sd, st)
12289      ChessProgramState *cps;
12290      int mps, inc, sd, st;
12291      long tc;
12292 {
12293     char buf[MSG_SIZ];
12294     int seconds;
12295
12296     if( timeControl_2 > 0 ) {
12297         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12298             tc = timeControl_2;
12299         }
12300     }
12301     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12302     inc /= cps->timeOdds;
12303     st  /= cps->timeOdds;
12304
12305     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12306
12307     if (st > 0) {
12308       /* Set exact time per move, normally using st command */
12309       if (cps->stKludge) {
12310         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12311         seconds = st % 60;
12312         if (seconds == 0) {
12313           sprintf(buf, "level 1 %d\n", st/60);
12314         } else {
12315           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12316         }
12317       } else {
12318         sprintf(buf, "st %d\n", st);
12319       }
12320     } else {
12321       /* Set conventional or incremental time control, using level command */
12322       if (seconds == 0) {
12323         /* Note old gnuchess bug -- minutes:seconds used to not work.
12324            Fixed in later versions, but still avoid :seconds
12325            when seconds is 0. */
12326         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12327       } else {
12328         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12329                 seconds, inc/1000);
12330       }
12331     }
12332     SendToProgram(buf, cps);
12333
12334     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12335     /* Orthogonally, limit search to given depth */
12336     if (sd > 0) {
12337       if (cps->sdKludge) {
12338         sprintf(buf, "depth\n%d\n", sd);
12339       } else {
12340         sprintf(buf, "sd %d\n", sd);
12341       }
12342       SendToProgram(buf, cps);
12343     }
12344
12345     if(cps->nps > 0) { /* [HGM] nps */
12346         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12347         else {
12348                 sprintf(buf, "nps %d\n", cps->nps);
12349               SendToProgram(buf, cps);
12350         }
12351     }
12352 }
12353
12354 ChessProgramState *WhitePlayer()
12355 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12356 {
12357     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12358        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12359         return &second;
12360     return &first;
12361 }
12362
12363 void
12364 SendTimeRemaining(cps, machineWhite)
12365      ChessProgramState *cps;
12366      int /*boolean*/ machineWhite;
12367 {
12368     char message[MSG_SIZ];
12369     long time, otime;
12370
12371     /* Note: this routine must be called when the clocks are stopped
12372        or when they have *just* been set or switched; otherwise
12373        it will be off by the time since the current tick started.
12374     */
12375     if (machineWhite) {
12376         time = whiteTimeRemaining / 10;
12377         otime = blackTimeRemaining / 10;
12378     } else {
12379         time = blackTimeRemaining / 10;
12380         otime = whiteTimeRemaining / 10;
12381     }
12382     /* [HGM] translate opponent's time by time-odds factor */
12383     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12384     if (appData.debugMode) {
12385         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12386     }
12387
12388     if (time <= 0) time = 1;
12389     if (otime <= 0) otime = 1;
12390
12391     sprintf(message, "time %ld\n", time);
12392     SendToProgram(message, cps);
12393
12394     sprintf(message, "otim %ld\n", otime);
12395     SendToProgram(message, cps);
12396 }
12397
12398 int
12399 BoolFeature(p, name, loc, cps)
12400      char **p;
12401      char *name;
12402      int *loc;
12403      ChessProgramState *cps;
12404 {
12405   char buf[MSG_SIZ];
12406   int len = strlen(name);
12407   int val;
12408   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12409     (*p) += len + 1;
12410     sscanf(*p, "%d", &val);
12411     *loc = (val != 0);
12412     while (**p && **p != ' ') (*p)++;
12413     sprintf(buf, "accepted %s\n", name);
12414     SendToProgram(buf, cps);
12415     return TRUE;
12416   }
12417   return FALSE;
12418 }
12419
12420 int
12421 IntFeature(p, name, loc, cps)
12422      char **p;
12423      char *name;
12424      int *loc;
12425      ChessProgramState *cps;
12426 {
12427   char buf[MSG_SIZ];
12428   int len = strlen(name);
12429   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12430     (*p) += len + 1;
12431     sscanf(*p, "%d", loc);
12432     while (**p && **p != ' ') (*p)++;
12433     sprintf(buf, "accepted %s\n", name);
12434     SendToProgram(buf, cps);
12435     return TRUE;
12436   }
12437   return FALSE;
12438 }
12439
12440 int
12441 StringFeature(p, name, loc, cps)
12442      char **p;
12443      char *name;
12444      char loc[];
12445      ChessProgramState *cps;
12446 {
12447   char buf[MSG_SIZ];
12448   int len = strlen(name);
12449   if (strncmp((*p), name, len) == 0
12450       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12451     (*p) += len + 2;
12452     sscanf(*p, "%[^\"]", loc);
12453     while (**p && **p != '\"') (*p)++;
12454     if (**p == '\"') (*p)++;
12455     sprintf(buf, "accepted %s\n", name);
12456     SendToProgram(buf, cps);
12457     return TRUE;
12458   }
12459   return FALSE;
12460 }
12461
12462 int
12463 ParseOption(Option *opt, ChessProgramState *cps)
12464 // [HGM] options: process the string that defines an engine option, and determine
12465 // name, type, default value, and allowed value range
12466 {
12467         char *p, *q, buf[MSG_SIZ];
12468         int n, min = (-1)<<31, max = 1<<31, def;
12469
12470         if(p = strstr(opt->name, " -spin ")) {
12471             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12472             if(max < min) max = min; // enforce consistency
12473             if(def < min) def = min;
12474             if(def > max) def = max;
12475             opt->value = def;
12476             opt->min = min;
12477             opt->max = max;
12478             opt->type = Spin;
12479         } else if((p = strstr(opt->name, " -slider "))) {
12480             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12481             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12482             if(max < min) max = min; // enforce consistency
12483             if(def < min) def = min;
12484             if(def > max) def = max;
12485             opt->value = def;
12486             opt->min = min;
12487             opt->max = max;
12488             opt->type = Spin; // Slider;
12489         } else if((p = strstr(opt->name, " -string "))) {
12490             opt->textValue = p+9;
12491             opt->type = TextBox;
12492         } else if((p = strstr(opt->name, " -file "))) {
12493             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12494             opt->textValue = p+7;
12495             opt->type = TextBox; // FileName;
12496         } else if((p = strstr(opt->name, " -path "))) {
12497             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12498             opt->textValue = p+7;
12499             opt->type = TextBox; // PathName;
12500         } else if(p = strstr(opt->name, " -check ")) {
12501             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12502             opt->value = (def != 0);
12503             opt->type = CheckBox;
12504         } else if(p = strstr(opt->name, " -combo ")) {
12505             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12506             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12507             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12508             opt->value = n = 0;
12509             while(q = StrStr(q, " /// ")) {
12510                 n++; *q = 0;    // count choices, and null-terminate each of them
12511                 q += 5;
12512                 if(*q == '*') { // remember default, which is marked with * prefix
12513                     q++;
12514                     opt->value = n;
12515                 }
12516                 cps->comboList[cps->comboCnt++] = q;
12517             }
12518             cps->comboList[cps->comboCnt++] = NULL;
12519             opt->max = n + 1;
12520             opt->type = ComboBox;
12521         } else if(p = strstr(opt->name, " -button")) {
12522             opt->type = Button;
12523         } else if(p = strstr(opt->name, " -save")) {
12524             opt->type = SaveButton;
12525         } else return FALSE;
12526         *p = 0; // terminate option name
12527         // now look if the command-line options define a setting for this engine option.
12528         if(cps->optionSettings && cps->optionSettings[0])
12529             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12530         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12531                 sprintf(buf, "option %s", p);
12532                 if(p = strstr(buf, ",")) *p = 0;
12533                 strcat(buf, "\n");
12534                 SendToProgram(buf, cps);
12535         }
12536         return TRUE;
12537 }
12538
12539 void
12540 FeatureDone(cps, val)
12541      ChessProgramState* cps;
12542      int val;
12543 {
12544   DelayedEventCallback cb = GetDelayedEvent();
12545   if ((cb == InitBackEnd3 && cps == &first) ||
12546       (cb == TwoMachinesEventIfReady && cps == &second)) {
12547     CancelDelayedEvent();
12548     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12549   }
12550   cps->initDone = val;
12551 }
12552
12553 /* Parse feature command from engine */
12554 void
12555 ParseFeatures(args, cps)
12556      char* args;
12557      ChessProgramState *cps;
12558 {
12559   char *p = args;
12560   char *q;
12561   int val;
12562   char buf[MSG_SIZ];
12563
12564   for (;;) {
12565     while (*p == ' ') p++;
12566     if (*p == NULLCHAR) return;
12567
12568     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12569     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12570     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12571     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12572     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12573     if (BoolFeature(&p, "reuse", &val, cps)) {
12574       /* Engine can disable reuse, but can't enable it if user said no */
12575       if (!val) cps->reuse = FALSE;
12576       continue;
12577     }
12578     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12579     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12580       if (gameMode == TwoMachinesPlay) {
12581         DisplayTwoMachinesTitle();
12582       } else {
12583         DisplayTitle("");
12584       }
12585       continue;
12586     }
12587     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12588     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12589     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12590     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12591     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12592     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12593     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12594     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12595     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12596     if (IntFeature(&p, "done", &val, cps)) {
12597       FeatureDone(cps, val);
12598       continue;
12599     }
12600     /* Added by Tord: */
12601     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12602     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12603     /* End of additions by Tord */
12604
12605     /* [HGM] added features: */
12606     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12607     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12608     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12609     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12610     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12611     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12612     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12613         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12614             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12615             SendToProgram(buf, cps);
12616             continue;
12617         }
12618         if(cps->nrOptions >= MAX_OPTIONS) {
12619             cps->nrOptions--;
12620             sprintf(buf, "%s engine has too many options\n", cps->which);
12621             DisplayError(buf, 0);
12622         }
12623         continue;
12624     }
12625     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12626     /* End of additions by HGM */
12627
12628     /* unknown feature: complain and skip */
12629     q = p;
12630     while (*q && *q != '=') q++;
12631     sprintf(buf, "rejected %.*s\n", q-p, p);
12632     SendToProgram(buf, cps);
12633     p = q;
12634     if (*p == '=') {
12635       p++;
12636       if (*p == '\"') {
12637         p++;
12638         while (*p && *p != '\"') p++;
12639         if (*p == '\"') p++;
12640       } else {
12641         while (*p && *p != ' ') p++;
12642       }
12643     }
12644   }
12645
12646 }
12647
12648 void
12649 PeriodicUpdatesEvent(newState)
12650      int newState;
12651 {
12652     if (newState == appData.periodicUpdates)
12653       return;
12654
12655     appData.periodicUpdates=newState;
12656
12657     /* Display type changes, so update it now */
12658     DisplayAnalysis();
12659
12660     /* Get the ball rolling again... */
12661     if (newState) {
12662         AnalysisPeriodicEvent(1);
12663         StartAnalysisClock();
12664     }
12665 }
12666
12667 void
12668 PonderNextMoveEvent(newState)
12669      int newState;
12670 {
12671     if (newState == appData.ponderNextMove) return;
12672     if (gameMode == EditPosition) EditPositionDone();
12673     if (newState) {
12674         SendToProgram("hard\n", &first);
12675         if (gameMode == TwoMachinesPlay) {
12676             SendToProgram("hard\n", &second);
12677         }
12678     } else {
12679         SendToProgram("easy\n", &first);
12680         thinkOutput[0] = NULLCHAR;
12681         if (gameMode == TwoMachinesPlay) {
12682             SendToProgram("easy\n", &second);
12683         }
12684     }
12685     appData.ponderNextMove = newState;
12686 }
12687
12688 void
12689 NewSettingEvent(option, command, value)
12690      char *command;
12691      int option, value;
12692 {
12693     char buf[MSG_SIZ];
12694
12695     if (gameMode == EditPosition) EditPositionDone();
12696     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12697     SendToProgram(buf, &first);
12698     if (gameMode == TwoMachinesPlay) {
12699         SendToProgram(buf, &second);
12700     }
12701 }
12702
12703 void
12704 ShowThinkingEvent()
12705 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12706 {
12707     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12708     int newState = appData.showThinking
12709         // [HGM] thinking: other features now need thinking output as well
12710         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12711
12712     if (oldState == newState) return;
12713     oldState = newState;
12714     if (gameMode == EditPosition) EditPositionDone();
12715     if (oldState) {
12716         SendToProgram("post\n", &first);
12717         if (gameMode == TwoMachinesPlay) {
12718             SendToProgram("post\n", &second);
12719         }
12720     } else {
12721         SendToProgram("nopost\n", &first);
12722         thinkOutput[0] = NULLCHAR;
12723         if (gameMode == TwoMachinesPlay) {
12724             SendToProgram("nopost\n", &second);
12725         }
12726     }
12727 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12728 }
12729
12730 void
12731 AskQuestionEvent(title, question, replyPrefix, which)
12732      char *title; char *question; char *replyPrefix; char *which;
12733 {
12734   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12735   if (pr == NoProc) return;
12736   AskQuestion(title, question, replyPrefix, pr);
12737 }
12738
12739 void
12740 DisplayMove(moveNumber)
12741      int moveNumber;
12742 {
12743     char message[MSG_SIZ];
12744     char res[MSG_SIZ];
12745     char cpThinkOutput[MSG_SIZ];
12746
12747     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12748
12749     if (moveNumber == forwardMostMove - 1 ||
12750         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12751
12752         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12753
12754         if (strchr(cpThinkOutput, '\n')) {
12755             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12756         }
12757     } else {
12758         *cpThinkOutput = NULLCHAR;
12759     }
12760
12761     /* [AS] Hide thinking from human user */
12762     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12763         *cpThinkOutput = NULLCHAR;
12764         if( thinkOutput[0] != NULLCHAR ) {
12765             int i;
12766
12767             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12768                 cpThinkOutput[i] = '.';
12769             }
12770             cpThinkOutput[i] = NULLCHAR;
12771             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12772         }
12773     }
12774
12775     if (moveNumber == forwardMostMove - 1 &&
12776         gameInfo.resultDetails != NULL) {
12777         if (gameInfo.resultDetails[0] == NULLCHAR) {
12778             sprintf(res, " %s", PGNResult(gameInfo.result));
12779         } else {
12780             sprintf(res, " {%s} %s",
12781                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12782         }
12783     } else {
12784         res[0] = NULLCHAR;
12785     }
12786
12787     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12788         DisplayMessage(res, cpThinkOutput);
12789     } else {
12790         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12791                 WhiteOnMove(moveNumber) ? " " : ".. ",
12792                 parseList[moveNumber], res);
12793         DisplayMessage(message, cpThinkOutput);
12794     }
12795 }
12796
12797 void
12798 DisplayAnalysisText(text)
12799      char *text;
12800 {
12801     char buf[MSG_SIZ];
12802
12803     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12804                || appData.icsEngineAnalyze) {
12805         sprintf(buf, "Analysis (%s)", first.tidy);
12806         AnalysisPopUp(buf, text);
12807     }
12808 }
12809
12810 static int
12811 only_one_move(str)
12812      char *str;
12813 {
12814     while (*str && isspace(*str)) ++str;
12815     while (*str && !isspace(*str)) ++str;
12816     if (!*str) return 1;
12817     while (*str && isspace(*str)) ++str;
12818     if (!*str) return 1;
12819     return 0;
12820 }
12821
12822 void
12823 DisplayAnalysis()
12824 {
12825     char buf[MSG_SIZ];
12826     char lst[MSG_SIZ / 2];
12827     double nps;
12828     static char *xtra[] = { "", " (--)", " (++)" };
12829     int h, m, s, cs;
12830
12831     if (programStats.time == 0) {
12832         programStats.time = 1;
12833     }
12834
12835     if (programStats.got_only_move) {
12836         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12837     } else {
12838         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12839
12840         nps = (u64ToDouble(programStats.nodes) /
12841              ((double)programStats.time /100.0));
12842
12843         cs = programStats.time % 100;
12844         s = programStats.time / 100;
12845         h = (s / (60*60));
12846         s = s - h*60*60;
12847         m = (s/60);
12848         s = s - m*60;
12849
12850         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12851           if (programStats.move_name[0] != NULLCHAR) {
12852             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12853                     programStats.depth,
12854                     programStats.nr_moves-programStats.moves_left,
12855                     programStats.nr_moves, programStats.move_name,
12856                     ((float)programStats.score)/100.0, lst,
12857                     only_one_move(lst)?
12858                     xtra[programStats.got_fail] : "",
12859                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12860           } else {
12861             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12862                     programStats.depth,
12863                     programStats.nr_moves-programStats.moves_left,
12864                     programStats.nr_moves, ((float)programStats.score)/100.0,
12865                     lst,
12866                     only_one_move(lst)?
12867                     xtra[programStats.got_fail] : "",
12868                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12869           }
12870         } else {
12871             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12872                     programStats.depth,
12873                     ((float)programStats.score)/100.0,
12874                     lst,
12875                     only_one_move(lst)?
12876                     xtra[programStats.got_fail] : "",
12877                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12878         }
12879     }
12880     DisplayAnalysisText(buf);
12881 }
12882
12883 void
12884 DisplayComment(moveNumber, text)
12885      int moveNumber;
12886      char *text;
12887 {
12888     char title[MSG_SIZ];
12889     char buf[8000]; // comment can be long!
12890     int score, depth;
12891
12892     if( appData.autoDisplayComment ) {
12893         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12894             strcpy(title, "Comment");
12895         } else {
12896             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12897                     WhiteOnMove(moveNumber) ? " " : ".. ",
12898                     parseList[moveNumber]);
12899         }
12900         // [HGM] PV info: display PV info together with (or as) comment
12901         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12902             if(text == NULL) text = "";
12903             score = pvInfoList[moveNumber].score;
12904             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12905                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12906             text = buf;
12907         }
12908     } else title[0] = 0;
12909
12910     if (text != NULL)
12911         CommentPopUp(title, text);
12912 }
12913
12914 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12915  * might be busy thinking or pondering.  It can be omitted if your
12916  * gnuchess is configured to stop thinking immediately on any user
12917  * input.  However, that gnuchess feature depends on the FIONREAD
12918  * ioctl, which does not work properly on some flavors of Unix.
12919  */
12920 void
12921 Attention(cps)
12922      ChessProgramState *cps;
12923 {
12924 #if ATTENTION
12925     if (!cps->useSigint) return;
12926     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12927     switch (gameMode) {
12928       case MachinePlaysWhite:
12929       case MachinePlaysBlack:
12930       case TwoMachinesPlay:
12931       case IcsPlayingWhite:
12932       case IcsPlayingBlack:
12933       case AnalyzeMode:
12934       case AnalyzeFile:
12935         /* Skip if we know it isn't thinking */
12936         if (!cps->maybeThinking) return;
12937         if (appData.debugMode)
12938           fprintf(debugFP, "Interrupting %s\n", cps->which);
12939         InterruptChildProcess(cps->pr);
12940         cps->maybeThinking = FALSE;
12941         break;
12942       default:
12943         break;
12944     }
12945 #endif /*ATTENTION*/
12946 }
12947
12948 int
12949 CheckFlags()
12950 {
12951     if (whiteTimeRemaining <= 0) {
12952         if (!whiteFlag) {
12953             whiteFlag = TRUE;
12954             if (appData.icsActive) {
12955                 if (appData.autoCallFlag &&
12956                     gameMode == IcsPlayingBlack && !blackFlag) {
12957                   SendToICS(ics_prefix);
12958                   SendToICS("flag\n");
12959                 }
12960             } else {
12961                 if (blackFlag) {
12962                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12963                 } else {
12964                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12965                     if (appData.autoCallFlag) {
12966                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12967                         return TRUE;
12968                     }
12969                 }
12970             }
12971         }
12972     }
12973     if (blackTimeRemaining <= 0) {
12974         if (!blackFlag) {
12975             blackFlag = TRUE;
12976             if (appData.icsActive) {
12977                 if (appData.autoCallFlag &&
12978                     gameMode == IcsPlayingWhite && !whiteFlag) {
12979                   SendToICS(ics_prefix);
12980                   SendToICS("flag\n");
12981                 }
12982             } else {
12983                 if (whiteFlag) {
12984                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12985                 } else {
12986                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12987                     if (appData.autoCallFlag) {
12988                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12989                         return TRUE;
12990                     }
12991                 }
12992             }
12993         }
12994     }
12995     return FALSE;
12996 }
12997
12998 void
12999 CheckTimeControl()
13000 {
13001     if (!appData.clockMode || appData.icsActive ||
13002         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13003
13004     /*
13005      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13006      */
13007     if ( !WhiteOnMove(forwardMostMove) )
13008         /* White made time control */
13009         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13010         /* [HGM] time odds: correct new time quota for time odds! */
13011                                             / WhitePlayer()->timeOdds;
13012       else
13013         /* Black made time control */
13014         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13015                                             / WhitePlayer()->other->timeOdds;
13016 }
13017
13018 void
13019 DisplayBothClocks()
13020 {
13021     int wom = gameMode == EditPosition ?
13022       !blackPlaysFirst : WhiteOnMove(currentMove);
13023     DisplayWhiteClock(whiteTimeRemaining, wom);
13024     DisplayBlackClock(blackTimeRemaining, !wom);
13025 }
13026
13027
13028 /* Timekeeping seems to be a portability nightmare.  I think everyone
13029    has ftime(), but I'm really not sure, so I'm including some ifdefs
13030    to use other calls if you don't.  Clocks will be less accurate if
13031    you have neither ftime nor gettimeofday.
13032 */
13033
13034 /* VS 2008 requires the #include outside of the function */
13035 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13036 #include <sys/timeb.h>
13037 #endif
13038
13039 /* Get the current time as a TimeMark */
13040 void
13041 GetTimeMark(tm)
13042      TimeMark *tm;
13043 {
13044 #if HAVE_GETTIMEOFDAY
13045
13046     struct timeval timeVal;
13047     struct timezone timeZone;
13048
13049     gettimeofday(&timeVal, &timeZone);
13050     tm->sec = (long) timeVal.tv_sec;
13051     tm->ms = (int) (timeVal.tv_usec / 1000L);
13052
13053 #else /*!HAVE_GETTIMEOFDAY*/
13054 #if HAVE_FTIME
13055
13056 // include <sys/timeb.h> / moved to just above start of function
13057     struct timeb timeB;
13058
13059     ftime(&timeB);
13060     tm->sec = (long) timeB.time;
13061     tm->ms = (int) timeB.millitm;
13062
13063 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13064     tm->sec = (long) time(NULL);
13065     tm->ms = 0;
13066 #endif
13067 #endif
13068 }
13069
13070 /* Return the difference in milliseconds between two
13071    time marks.  We assume the difference will fit in a long!
13072 */
13073 long
13074 SubtractTimeMarks(tm2, tm1)
13075      TimeMark *tm2, *tm1;
13076 {
13077     return 1000L*(tm2->sec - tm1->sec) +
13078            (long) (tm2->ms - tm1->ms);
13079 }
13080
13081
13082 /*
13083  * Code to manage the game clocks.
13084  *
13085  * In tournament play, black starts the clock and then white makes a move.
13086  * We give the human user a slight advantage if he is playing white---the
13087  * clocks don't run until he makes his first move, so it takes zero time.
13088  * Also, we don't account for network lag, so we could get out of sync
13089  * with GNU Chess's clock -- but then, referees are always right.
13090  */
13091
13092 static TimeMark tickStartTM;
13093 static long intendedTickLength;
13094
13095 long
13096 NextTickLength(timeRemaining)
13097      long timeRemaining;
13098 {
13099     long nominalTickLength, nextTickLength;
13100
13101     if (timeRemaining > 0L && timeRemaining <= 10000L)
13102       nominalTickLength = 100L;
13103     else
13104       nominalTickLength = 1000L;
13105     nextTickLength = timeRemaining % nominalTickLength;
13106     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13107
13108     return nextTickLength;
13109 }
13110
13111 /* Adjust clock one minute up or down */
13112 void
13113 AdjustClock(Boolean which, int dir)
13114 {
13115     if(which) blackTimeRemaining += 60000*dir;
13116     else      whiteTimeRemaining += 60000*dir;
13117     DisplayBothClocks();
13118 }
13119
13120 /* Stop clocks and reset to a fresh time control */
13121 void
13122 ResetClocks()
13123 {
13124     (void) StopClockTimer();
13125     if (appData.icsActive) {
13126         whiteTimeRemaining = blackTimeRemaining = 0;
13127     } else { /* [HGM] correct new time quote for time odds */
13128         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13129         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13130     }
13131     if (whiteFlag || blackFlag) {
13132         DisplayTitle("");
13133         whiteFlag = blackFlag = FALSE;
13134     }
13135     DisplayBothClocks();
13136 }
13137
13138 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13139
13140 /* Decrement running clock by amount of time that has passed */
13141 void
13142 DecrementClocks()
13143 {
13144     long timeRemaining;
13145     long lastTickLength, fudge;
13146     TimeMark now;
13147
13148     if (!appData.clockMode) return;
13149     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13150
13151     GetTimeMark(&now);
13152
13153     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13154
13155     /* Fudge if we woke up a little too soon */
13156     fudge = intendedTickLength - lastTickLength;
13157     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13158
13159     if (WhiteOnMove(forwardMostMove)) {
13160         if(whiteNPS >= 0) lastTickLength = 0;
13161         timeRemaining = whiteTimeRemaining -= lastTickLength;
13162         DisplayWhiteClock(whiteTimeRemaining - fudge,
13163                           WhiteOnMove(currentMove));
13164     } else {
13165         if(blackNPS >= 0) lastTickLength = 0;
13166         timeRemaining = blackTimeRemaining -= lastTickLength;
13167         DisplayBlackClock(blackTimeRemaining - fudge,
13168                           !WhiteOnMove(currentMove));
13169     }
13170
13171     if (CheckFlags()) return;
13172
13173     tickStartTM = now;
13174     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13175     StartClockTimer(intendedTickLength);
13176
13177     /* if the time remaining has fallen below the alarm threshold, sound the
13178      * alarm. if the alarm has sounded and (due to a takeback or time control
13179      * with increment) the time remaining has increased to a level above the
13180      * threshold, reset the alarm so it can sound again.
13181      */
13182
13183     if (appData.icsActive && appData.icsAlarm) {
13184
13185         /* make sure we are dealing with the user's clock */
13186         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13187                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13188            )) return;
13189
13190         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13191             alarmSounded = FALSE;
13192         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13193             PlayAlarmSound();
13194             alarmSounded = TRUE;
13195         }
13196     }
13197 }
13198
13199
13200 /* A player has just moved, so stop the previously running
13201    clock and (if in clock mode) start the other one.
13202    We redisplay both clocks in case we're in ICS mode, because
13203    ICS gives us an update to both clocks after every move.
13204    Note that this routine is called *after* forwardMostMove
13205    is updated, so the last fractional tick must be subtracted
13206    from the color that is *not* on move now.
13207 */
13208 void
13209 SwitchClocks()
13210 {
13211     long lastTickLength;
13212     TimeMark now;
13213     int flagged = FALSE;
13214
13215     GetTimeMark(&now);
13216
13217     if (StopClockTimer() && appData.clockMode) {
13218         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13219         if (WhiteOnMove(forwardMostMove)) {
13220             if(blackNPS >= 0) lastTickLength = 0;
13221             blackTimeRemaining -= lastTickLength;
13222            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13223 //         if(pvInfoList[forwardMostMove-1].time == -1)
13224                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13225                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13226         } else {
13227            if(whiteNPS >= 0) lastTickLength = 0;
13228            whiteTimeRemaining -= lastTickLength;
13229            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13230 //         if(pvInfoList[forwardMostMove-1].time == -1)
13231                  pvInfoList[forwardMostMove-1].time =
13232                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13233         }
13234         flagged = CheckFlags();
13235     }
13236     CheckTimeControl();
13237
13238     if (flagged || !appData.clockMode) return;
13239
13240     switch (gameMode) {
13241       case MachinePlaysBlack:
13242       case MachinePlaysWhite:
13243       case BeginningOfGame:
13244         if (pausing) return;
13245         break;
13246
13247       case EditGame:
13248       case PlayFromGameFile:
13249       case IcsExamining:
13250         return;
13251
13252       default:
13253         break;
13254     }
13255
13256     tickStartTM = now;
13257     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13258       whiteTimeRemaining : blackTimeRemaining);
13259     StartClockTimer(intendedTickLength);
13260 }
13261
13262
13263 /* Stop both clocks */
13264 void
13265 StopClocks()
13266 {
13267     long lastTickLength;
13268     TimeMark now;
13269
13270     if (!StopClockTimer()) return;
13271     if (!appData.clockMode) return;
13272
13273     GetTimeMark(&now);
13274
13275     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13276     if (WhiteOnMove(forwardMostMove)) {
13277         if(whiteNPS >= 0) lastTickLength = 0;
13278         whiteTimeRemaining -= lastTickLength;
13279         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13280     } else {
13281         if(blackNPS >= 0) lastTickLength = 0;
13282         blackTimeRemaining -= lastTickLength;
13283         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13284     }
13285     CheckFlags();
13286 }
13287
13288 /* Start clock of player on move.  Time may have been reset, so
13289    if clock is already running, stop and restart it. */
13290 void
13291 StartClocks()
13292 {
13293     (void) StopClockTimer(); /* in case it was running already */
13294     DisplayBothClocks();
13295     if (CheckFlags()) return;
13296
13297     if (!appData.clockMode) return;
13298     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13299
13300     GetTimeMark(&tickStartTM);
13301     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13302       whiteTimeRemaining : blackTimeRemaining);
13303
13304    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13305     whiteNPS = blackNPS = -1;
13306     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13307        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13308         whiteNPS = first.nps;
13309     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13310        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13311         blackNPS = first.nps;
13312     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13313         whiteNPS = second.nps;
13314     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13315         blackNPS = second.nps;
13316     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13317
13318     StartClockTimer(intendedTickLength);
13319 }
13320
13321 char *
13322 TimeString(ms)
13323      long ms;
13324 {
13325     long second, minute, hour, day;
13326     char *sign = "";
13327     static char buf[32];
13328
13329     if (ms > 0 && ms <= 9900) {
13330       /* convert milliseconds to tenths, rounding up */
13331       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13332
13333       sprintf(buf, " %03.1f ", tenths/10.0);
13334       return buf;
13335     }
13336
13337     /* convert milliseconds to seconds, rounding up */
13338     /* use floating point to avoid strangeness of integer division
13339        with negative dividends on many machines */
13340     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13341
13342     if (second < 0) {
13343         sign = "-";
13344         second = -second;
13345     }
13346
13347     day = second / (60 * 60 * 24);
13348     second = second % (60 * 60 * 24);
13349     hour = second / (60 * 60);
13350     second = second % (60 * 60);
13351     minute = second / 60;
13352     second = second % 60;
13353
13354     if (day > 0)
13355       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13356               sign, day, hour, minute, second);
13357     else if (hour > 0)
13358       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13359     else
13360       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13361
13362     return buf;
13363 }
13364
13365
13366 /*
13367  * This is necessary because some C libraries aren't ANSI C compliant yet.
13368  */
13369 char *
13370 StrStr(string, match)
13371      char *string, *match;
13372 {
13373     int i, length;
13374
13375     length = strlen(match);
13376
13377     for (i = strlen(string) - length; i >= 0; i--, string++)
13378       if (!strncmp(match, string, length))
13379         return string;
13380
13381     return NULL;
13382 }
13383
13384 char *
13385 StrCaseStr(string, match)
13386      char *string, *match;
13387 {
13388     int i, j, length;
13389
13390     length = strlen(match);
13391
13392     for (i = strlen(string) - length; i >= 0; i--, string++) {
13393         for (j = 0; j < length; j++) {
13394             if (ToLower(match[j]) != ToLower(string[j]))
13395               break;
13396         }
13397         if (j == length) return string;
13398     }
13399
13400     return NULL;
13401 }
13402
13403 #ifndef _amigados
13404 int
13405 StrCaseCmp(s1, s2)
13406      char *s1, *s2;
13407 {
13408     char c1, c2;
13409
13410     for (;;) {
13411         c1 = ToLower(*s1++);
13412         c2 = ToLower(*s2++);
13413         if (c1 > c2) return 1;
13414         if (c1 < c2) return -1;
13415         if (c1 == NULLCHAR) return 0;
13416     }
13417 }
13418
13419
13420 int
13421 ToLower(c)
13422      int c;
13423 {
13424     return isupper(c) ? tolower(c) : c;
13425 }
13426
13427
13428 int
13429 ToUpper(c)
13430      int c;
13431 {
13432     return islower(c) ? toupper(c) : c;
13433 }
13434 #endif /* !_amigados    */
13435
13436 char *
13437 StrSave(s)
13438      char *s;
13439 {
13440     char *ret;
13441
13442     if ((ret = (char *) malloc(strlen(s) + 1))) {
13443         strcpy(ret, s);
13444     }
13445     return ret;
13446 }
13447
13448 char *
13449 StrSavePtr(s, savePtr)
13450      char *s, **savePtr;
13451 {
13452     if (*savePtr) {
13453         free(*savePtr);
13454     }
13455     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13456         strcpy(*savePtr, s);
13457     }
13458     return(*savePtr);
13459 }
13460
13461 char *
13462 PGNDate()
13463 {
13464     time_t clock;
13465     struct tm *tm;
13466     char buf[MSG_SIZ];
13467
13468     clock = time((time_t *)NULL);
13469     tm = localtime(&clock);
13470     sprintf(buf, "%04d.%02d.%02d",
13471             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13472     return StrSave(buf);
13473 }
13474
13475
13476 char *
13477 PositionToFEN(move, overrideCastling)
13478      int move;
13479      char *overrideCastling;
13480 {
13481     int i, j, fromX, fromY, toX, toY;
13482     int whiteToPlay;
13483     char buf[128];
13484     char *p, *q;
13485     int emptycount;
13486     ChessSquare piece;
13487
13488     whiteToPlay = (gameMode == EditPosition) ?
13489       !blackPlaysFirst : (move % 2 == 0);
13490     p = buf;
13491
13492     /* Piece placement data */
13493     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13494         emptycount = 0;
13495         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13496             if (boards[move][i][j] == EmptySquare) {
13497                 emptycount++;
13498             } else { ChessSquare piece = boards[move][i][j];
13499                 if (emptycount > 0) {
13500                     if(emptycount<10) /* [HGM] can be >= 10 */
13501                         *p++ = '0' + emptycount;
13502                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13503                     emptycount = 0;
13504                 }
13505                 if(PieceToChar(piece) == '+') {
13506                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13507                     *p++ = '+';
13508                     piece = (ChessSquare)(DEMOTED piece);
13509                 }
13510                 *p++ = PieceToChar(piece);
13511                 if(p[-1] == '~') {
13512                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13513                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13514                     *p++ = '~';
13515                 }
13516             }
13517         }
13518         if (emptycount > 0) {
13519             if(emptycount<10) /* [HGM] can be >= 10 */
13520                 *p++ = '0' + emptycount;
13521             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13522             emptycount = 0;
13523         }
13524         *p++ = '/';
13525     }
13526     *(p - 1) = ' ';
13527
13528     /* [HGM] print Crazyhouse or Shogi holdings */
13529     if( gameInfo.holdingsWidth ) {
13530         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13531         q = p;
13532         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13533             piece = boards[move][i][BOARD_WIDTH-1];
13534             if( piece != EmptySquare )
13535               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13536                   *p++ = PieceToChar(piece);
13537         }
13538         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13539             piece = boards[move][BOARD_HEIGHT-i-1][0];
13540             if( piece != EmptySquare )
13541               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13542                   *p++ = PieceToChar(piece);
13543         }
13544
13545         if( q == p ) *p++ = '-';
13546         *p++ = ']';
13547         *p++ = ' ';
13548     }
13549
13550     /* Active color */
13551     *p++ = whiteToPlay ? 'w' : 'b';
13552     *p++ = ' ';
13553
13554   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13555     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13556   } else {
13557   if(nrCastlingRights) {
13558      q = p;
13559      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13560        /* [HGM] write directly from rights */
13561            if(castlingRights[move][2] >= 0 &&
13562               castlingRights[move][0] >= 0   )
13563                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13564            if(castlingRights[move][2] >= 0 &&
13565               castlingRights[move][1] >= 0   )
13566                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13567            if(castlingRights[move][5] >= 0 &&
13568               castlingRights[move][3] >= 0   )
13569                 *p++ = castlingRights[move][3] + AAA;
13570            if(castlingRights[move][5] >= 0 &&
13571               castlingRights[move][4] >= 0   )
13572                 *p++ = castlingRights[move][4] + AAA;
13573      } else {
13574
13575         /* [HGM] write true castling rights */
13576         if( nrCastlingRights == 6 ) {
13577             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13578                castlingRights[move][2] >= 0  ) *p++ = 'K';
13579             if(castlingRights[move][1] == BOARD_LEFT &&
13580                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13581             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13582                castlingRights[move][5] >= 0  ) *p++ = 'k';
13583             if(castlingRights[move][4] == BOARD_LEFT &&
13584                castlingRights[move][5] >= 0  ) *p++ = 'q';
13585         }
13586      }
13587      if (q == p) *p++ = '-'; /* No castling rights */
13588      *p++ = ' ';
13589   }
13590
13591   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13592      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13593     /* En passant target square */
13594     if (move > backwardMostMove) {
13595         fromX = moveList[move - 1][0] - AAA;
13596         fromY = moveList[move - 1][1] - ONE;
13597         toX = moveList[move - 1][2] - AAA;
13598         toY = moveList[move - 1][3] - ONE;
13599         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13600             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13601             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13602             fromX == toX) {
13603             /* 2-square pawn move just happened */
13604             *p++ = toX + AAA;
13605             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13606         } else {
13607             *p++ = '-';
13608         }
13609     } else {
13610         *p++ = '-';
13611     }
13612     *p++ = ' ';
13613   }
13614   }
13615
13616     /* [HGM] find reversible plies */
13617     {   int i = 0, j=move;
13618
13619         if (appData.debugMode) { int k;
13620             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13621             for(k=backwardMostMove; k<=forwardMostMove; k++)
13622                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13623
13624         }
13625
13626         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13627         if( j == backwardMostMove ) i += initialRulePlies;
13628         sprintf(p, "%d ", i);
13629         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13630     }
13631     /* Fullmove number */
13632     sprintf(p, "%d", (move / 2) + 1);
13633
13634     return StrSave(buf);
13635 }
13636
13637 Boolean
13638 ParseFEN(board, blackPlaysFirst, fen)
13639     Board board;
13640      int *blackPlaysFirst;
13641      char *fen;
13642 {
13643     int i, j;
13644     char *p;
13645     int emptycount;
13646     ChessSquare piece;
13647
13648     p = fen;
13649
13650     /* [HGM] by default clear Crazyhouse holdings, if present */
13651     if(gameInfo.holdingsWidth) {
13652        for(i=0; i<BOARD_HEIGHT; i++) {
13653            board[i][0]             = EmptySquare; /* black holdings */
13654            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13655            board[i][1]             = (ChessSquare) 0; /* black counts */
13656            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13657        }
13658     }
13659
13660     /* Piece placement data */
13661     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13662         j = 0;
13663         for (;;) {
13664             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13665                 if (*p == '/') p++;
13666                 emptycount = gameInfo.boardWidth - j;
13667                 while (emptycount--)
13668                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13669                 break;
13670 #if(BOARD_SIZE >= 10)
13671             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13672                 p++; emptycount=10;
13673                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13674                 while (emptycount--)
13675                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13676 #endif
13677             } else if (isdigit(*p)) {
13678                 emptycount = *p++ - '0';
13679                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13680                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13681                 while (emptycount--)
13682                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13683             } else if (*p == '+' || isalpha(*p)) {
13684                 if (j >= gameInfo.boardWidth) return FALSE;
13685                 if(*p=='+') {
13686                     piece = CharToPiece(*++p);
13687                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13688                     piece = (ChessSquare) (PROMOTED piece ); p++;
13689                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13690                 } else piece = CharToPiece(*p++);
13691
13692                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13693                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13694                     piece = (ChessSquare) (PROMOTED piece);
13695                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13696                     p++;
13697                 }
13698                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13699             } else {
13700                 return FALSE;
13701             }
13702         }
13703     }
13704     while (*p == '/' || *p == ' ') p++;
13705
13706     /* [HGM] look for Crazyhouse holdings here */
13707     while(*p==' ') p++;
13708     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13709         if(*p == '[') p++;
13710         if(*p == '-' ) *p++; /* empty holdings */ else {
13711             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13712             /* if we would allow FEN reading to set board size, we would   */
13713             /* have to add holdings and shift the board read so far here   */
13714             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13715                 *p++;
13716                 if((int) piece >= (int) BlackPawn ) {
13717                     i = (int)piece - (int)BlackPawn;
13718                     i = PieceToNumber((ChessSquare)i);
13719                     if( i >= gameInfo.holdingsSize ) return FALSE;
13720                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13721                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13722                 } else {
13723                     i = (int)piece - (int)WhitePawn;
13724                     i = PieceToNumber((ChessSquare)i);
13725                     if( i >= gameInfo.holdingsSize ) return FALSE;
13726                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13727                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13728                 }
13729             }
13730         }
13731         if(*p == ']') *p++;
13732     }
13733
13734     while(*p == ' ') p++;
13735
13736     /* Active color */
13737     switch (*p++) {
13738       case 'w':
13739         *blackPlaysFirst = FALSE;
13740         break;
13741       case 'b':
13742         *blackPlaysFirst = TRUE;
13743         break;
13744       default:
13745         return FALSE;
13746     }
13747
13748     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13749     /* return the extra info in global variiables             */
13750
13751     /* set defaults in case FEN is incomplete */
13752     FENepStatus = EP_UNKNOWN;
13753     for(i=0; i<nrCastlingRights; i++ ) {
13754         FENcastlingRights[i] =
13755             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13756     }   /* assume possible unless obviously impossible */
13757     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13758     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13759     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13760     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13761     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13762     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13763     FENrulePlies = 0;
13764
13765     while(*p==' ') p++;
13766     if(nrCastlingRights) {
13767       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13768           /* castling indicator present, so default becomes no castlings */
13769           for(i=0; i<nrCastlingRights; i++ ) {
13770                  FENcastlingRights[i] = -1;
13771           }
13772       }
13773       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13774              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13775              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13776              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13777         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13778
13779         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13780             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13781             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13782         }
13783         switch(c) {
13784           case'K':
13785               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13786               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13787               FENcastlingRights[2] = whiteKingFile;
13788               break;
13789           case'Q':
13790               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13791               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13792               FENcastlingRights[2] = whiteKingFile;
13793               break;
13794           case'k':
13795               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13796               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13797               FENcastlingRights[5] = blackKingFile;
13798               break;
13799           case'q':
13800               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13801               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13802               FENcastlingRights[5] = blackKingFile;
13803           case '-':
13804               break;
13805           default: /* FRC castlings */
13806               if(c >= 'a') { /* black rights */
13807                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13808                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13809                   if(i == BOARD_RGHT) break;
13810                   FENcastlingRights[5] = i;
13811                   c -= AAA;
13812                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13813                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13814                   if(c > i)
13815                       FENcastlingRights[3] = c;
13816                   else
13817                       FENcastlingRights[4] = c;
13818               } else { /* white rights */
13819                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13820                     if(board[0][i] == WhiteKing) break;
13821                   if(i == BOARD_RGHT) break;
13822                   FENcastlingRights[2] = i;
13823                   c -= AAA - 'a' + 'A';
13824                   if(board[0][c] >= WhiteKing) break;
13825                   if(c > i)
13826                       FENcastlingRights[0] = c;
13827                   else
13828                       FENcastlingRights[1] = c;
13829               }
13830         }
13831       }
13832     if (appData.debugMode) {
13833         fprintf(debugFP, "FEN castling rights:");
13834         for(i=0; i<nrCastlingRights; i++)
13835         fprintf(debugFP, " %d", FENcastlingRights[i]);
13836         fprintf(debugFP, "\n");
13837     }
13838
13839       while(*p==' ') p++;
13840     }
13841
13842     /* read e.p. field in games that know e.p. capture */
13843     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13844        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13845       if(*p=='-') {
13846         p++; FENepStatus = EP_NONE;
13847       } else {
13848          char c = *p++ - AAA;
13849
13850          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13851          if(*p >= '0' && *p <='9') *p++;
13852          FENepStatus = c;
13853       }
13854     }
13855
13856
13857     if(sscanf(p, "%d", &i) == 1) {
13858         FENrulePlies = i; /* 50-move ply counter */
13859         /* (The move number is still ignored)    */
13860     }
13861
13862     return TRUE;
13863 }
13864
13865 void
13866 EditPositionPasteFEN(char *fen)
13867 {
13868   if (fen != NULL) {
13869     Board initial_position;
13870
13871     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13872       DisplayError(_("Bad FEN position in clipboard"), 0);
13873       return ;
13874     } else {
13875       int savedBlackPlaysFirst = blackPlaysFirst;
13876       EditPositionEvent();
13877       blackPlaysFirst = savedBlackPlaysFirst;
13878       CopyBoard(boards[0], initial_position);
13879           /* [HGM] copy FEN attributes as well */
13880           {   int i;
13881               initialRulePlies = FENrulePlies;
13882               epStatus[0] = FENepStatus;
13883               for( i=0; i<nrCastlingRights; i++ )
13884                   castlingRights[0][i] = FENcastlingRights[i];
13885           }
13886       EditPositionDone();
13887       DisplayBothClocks();
13888       DrawPosition(FALSE, boards[currentMove]);
13889     }
13890   }
13891 }