Merge commit 'v4.4.0' 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     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10977
10978     startedFromSetupPosition = TRUE;
10979     InitChessProgram(&first, FALSE);
10980     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10981     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10982         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10983         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10984     } else castlingRights[0][2] = -1;
10985     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10986         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10987         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10988     } else castlingRights[0][5] = -1;
10989     SendToProgram("force\n", &first);
10990     if (blackPlaysFirst) {
10991         strcpy(moveList[0], "");
10992         strcpy(parseList[0], "");
10993         currentMove = forwardMostMove = backwardMostMove = 1;
10994         CopyBoard(boards[1], boards[0]);
10995         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10996         { int i;
10997           epStatus[1] = epStatus[0];
10998           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10999         }
11000     } else {
11001         currentMove = forwardMostMove = backwardMostMove = 0;
11002     }
11003     SendBoard(&first, forwardMostMove);
11004     if (appData.debugMode) {
11005         fprintf(debugFP, "EditPosDone\n");
11006     }
11007     DisplayTitle("");
11008     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11009     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11010     gameMode = EditGame;
11011     ModeHighlight();
11012     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11013     ClearHighlights(); /* [AS] */
11014 }
11015
11016 /* Pause for `ms' milliseconds */
11017 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11018 void
11019 TimeDelay(ms)
11020      long ms;
11021 {
11022     TimeMark m1, m2;
11023
11024     GetTimeMark(&m1);
11025     do {
11026         GetTimeMark(&m2);
11027     } while (SubtractTimeMarks(&m2, &m1) < ms);
11028 }
11029
11030 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11031 void
11032 SendMultiLineToICS(buf)
11033      char *buf;
11034 {
11035     char temp[MSG_SIZ+1], *p;
11036     int len;
11037
11038     len = strlen(buf);
11039     if (len > MSG_SIZ)
11040       len = MSG_SIZ;
11041
11042     strncpy(temp, buf, len);
11043     temp[len] = 0;
11044
11045     p = temp;
11046     while (*p) {
11047         if (*p == '\n' || *p == '\r')
11048           *p = ' ';
11049         ++p;
11050     }
11051
11052     strcat(temp, "\n");
11053     SendToICS(temp);
11054     SendToPlayer(temp, strlen(temp));
11055 }
11056
11057 void
11058 SetWhiteToPlayEvent()
11059 {
11060     if (gameMode == EditPosition) {
11061         blackPlaysFirst = FALSE;
11062         DisplayBothClocks();    /* works because currentMove is 0 */
11063     } else if (gameMode == IcsExamining) {
11064         SendToICS(ics_prefix);
11065         SendToICS("tomove white\n");
11066     }
11067 }
11068
11069 void
11070 SetBlackToPlayEvent()
11071 {
11072     if (gameMode == EditPosition) {
11073         blackPlaysFirst = TRUE;
11074         currentMove = 1;        /* kludge */
11075         DisplayBothClocks();
11076         currentMove = 0;
11077     } else if (gameMode == IcsExamining) {
11078         SendToICS(ics_prefix);
11079         SendToICS("tomove black\n");
11080     }
11081 }
11082
11083 void
11084 EditPositionMenuEvent(selection, x, y)
11085      ChessSquare selection;
11086      int x, y;
11087 {
11088     char buf[MSG_SIZ];
11089     ChessSquare piece = boards[0][y][x];
11090
11091     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11092
11093     switch (selection) {
11094       case ClearBoard:
11095         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11096             SendToICS(ics_prefix);
11097             SendToICS("bsetup clear\n");
11098         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11099             SendToICS(ics_prefix);
11100             SendToICS("clearboard\n");
11101         } else {
11102             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11103                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11104                 for (y = 0; y < BOARD_HEIGHT; y++) {
11105                     if (gameMode == IcsExamining) {
11106                         if (boards[currentMove][y][x] != EmptySquare) {
11107                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11108                                     AAA + x, ONE + y);
11109                             SendToICS(buf);
11110                         }
11111                     } else {
11112                         boards[0][y][x] = p;
11113                     }
11114                 }
11115             }
11116         }
11117         if (gameMode == EditPosition) {
11118             DrawPosition(FALSE, boards[0]);
11119         }
11120         break;
11121
11122       case WhitePlay:
11123         SetWhiteToPlayEvent();
11124         break;
11125
11126       case BlackPlay:
11127         SetBlackToPlayEvent();
11128         break;
11129
11130       case EmptySquare:
11131         if (gameMode == IcsExamining) {
11132             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11133             SendToICS(buf);
11134         } else {
11135             boards[0][y][x] = EmptySquare;
11136             DrawPosition(FALSE, boards[0]);
11137         }
11138         break;
11139
11140       case PromotePiece:
11141         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11142            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11143             selection = (ChessSquare) (PROMOTED piece);
11144         } else if(piece == EmptySquare) selection = WhiteSilver;
11145         else selection = (ChessSquare)((int)piece - 1);
11146         goto defaultlabel;
11147
11148       case DemotePiece:
11149         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11150            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11151             selection = (ChessSquare) (DEMOTED piece);
11152         } else if(piece == EmptySquare) selection = BlackSilver;
11153         else selection = (ChessSquare)((int)piece + 1);
11154         goto defaultlabel;
11155
11156       case WhiteQueen:
11157       case BlackQueen:
11158         if(gameInfo.variant == VariantShatranj ||
11159            gameInfo.variant == VariantXiangqi  ||
11160            gameInfo.variant == VariantCourier    )
11161             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11162         goto defaultlabel;
11163
11164       case WhiteKing:
11165       case BlackKing:
11166         if(gameInfo.variant == VariantXiangqi)
11167             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11168         if(gameInfo.variant == VariantKnightmate)
11169             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11170       default:
11171         defaultlabel:
11172         if (gameMode == IcsExamining) {
11173             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11174                     PieceToChar(selection), AAA + x, ONE + y);
11175             SendToICS(buf);
11176         } else {
11177             boards[0][y][x] = selection;
11178             DrawPosition(FALSE, boards[0]);
11179         }
11180         break;
11181     }
11182 }
11183
11184
11185 void
11186 DropMenuEvent(selection, x, y)
11187      ChessSquare selection;
11188      int x, y;
11189 {
11190     ChessMove moveType;
11191
11192     switch (gameMode) {
11193       case IcsPlayingWhite:
11194       case MachinePlaysBlack:
11195         if (!WhiteOnMove(currentMove)) {
11196             DisplayMoveError(_("It is Black's turn"));
11197             return;
11198         }
11199         moveType = WhiteDrop;
11200         break;
11201       case IcsPlayingBlack:
11202       case MachinePlaysWhite:
11203         if (WhiteOnMove(currentMove)) {
11204             DisplayMoveError(_("It is White's turn"));
11205             return;
11206         }
11207         moveType = BlackDrop;
11208         break;
11209       case EditGame:
11210         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11211         break;
11212       default:
11213         return;
11214     }
11215
11216     if (moveType == BlackDrop && selection < BlackPawn) {
11217       selection = (ChessSquare) ((int) selection
11218                                  + (int) BlackPawn - (int) WhitePawn);
11219     }
11220     if (boards[currentMove][y][x] != EmptySquare) {
11221         DisplayMoveError(_("That square is occupied"));
11222         return;
11223     }
11224
11225     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11226 }
11227
11228 void
11229 AcceptEvent()
11230 {
11231     /* Accept a pending offer of any kind from opponent */
11232
11233     if (appData.icsActive) {
11234         SendToICS(ics_prefix);
11235         SendToICS("accept\n");
11236     } else if (cmailMsgLoaded) {
11237         if (currentMove == cmailOldMove &&
11238             commentList[cmailOldMove] != NULL &&
11239             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11240                    "Black offers a draw" : "White offers a draw")) {
11241             TruncateGame();
11242             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11243             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11244         } else {
11245             DisplayError(_("There is no pending offer on this move"), 0);
11246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11247         }
11248     } else {
11249         /* Not used for offers from chess program */
11250     }
11251 }
11252
11253 void
11254 DeclineEvent()
11255 {
11256     /* Decline a pending offer of any kind from opponent */
11257
11258     if (appData.icsActive) {
11259         SendToICS(ics_prefix);
11260         SendToICS("decline\n");
11261     } else if (cmailMsgLoaded) {
11262         if (currentMove == cmailOldMove &&
11263             commentList[cmailOldMove] != NULL &&
11264             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11265                    "Black offers a draw" : "White offers a draw")) {
11266 #ifdef NOTDEF
11267             AppendComment(cmailOldMove, "Draw declined");
11268             DisplayComment(cmailOldMove - 1, "Draw declined");
11269 #endif /*NOTDEF*/
11270         } else {
11271             DisplayError(_("There is no pending offer on this move"), 0);
11272         }
11273     } else {
11274         /* Not used for offers from chess program */
11275     }
11276 }
11277
11278 void
11279 RematchEvent()
11280 {
11281     /* Issue ICS rematch command */
11282     if (appData.icsActive) {
11283         SendToICS(ics_prefix);
11284         SendToICS("rematch\n");
11285     }
11286 }
11287
11288 void
11289 CallFlagEvent()
11290 {
11291     /* Call your opponent's flag (claim a win on time) */
11292     if (appData.icsActive) {
11293         SendToICS(ics_prefix);
11294         SendToICS("flag\n");
11295     } else {
11296         switch (gameMode) {
11297           default:
11298             return;
11299           case MachinePlaysWhite:
11300             if (whiteFlag) {
11301                 if (blackFlag)
11302                   GameEnds(GameIsDrawn, "Both players ran out of time",
11303                            GE_PLAYER);
11304                 else
11305                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11306             } else {
11307                 DisplayError(_("Your opponent is not out of time"), 0);
11308             }
11309             break;
11310           case MachinePlaysBlack:
11311             if (blackFlag) {
11312                 if (whiteFlag)
11313                   GameEnds(GameIsDrawn, "Both players ran out of time",
11314                            GE_PLAYER);
11315                 else
11316                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11317             } else {
11318                 DisplayError(_("Your opponent is not out of time"), 0);
11319             }
11320             break;
11321         }
11322     }
11323 }
11324
11325 void
11326 DrawEvent()
11327 {
11328     /* Offer draw or accept pending draw offer from opponent */
11329
11330     if (appData.icsActive) {
11331         /* Note: tournament rules require draw offers to be
11332            made after you make your move but before you punch
11333            your clock.  Currently ICS doesn't let you do that;
11334            instead, you immediately punch your clock after making
11335            a move, but you can offer a draw at any time. */
11336
11337         SendToICS(ics_prefix);
11338         SendToICS("draw\n");
11339     } else if (cmailMsgLoaded) {
11340         if (currentMove == cmailOldMove &&
11341             commentList[cmailOldMove] != NULL &&
11342             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11343                    "Black offers a draw" : "White offers a draw")) {
11344             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11345             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11346         } else if (currentMove == cmailOldMove + 1) {
11347             char *offer = WhiteOnMove(cmailOldMove) ?
11348               "White offers a draw" : "Black offers a draw";
11349             AppendComment(currentMove, offer);
11350             DisplayComment(currentMove - 1, offer);
11351             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11352         } else {
11353             DisplayError(_("You must make your move before offering a draw"), 0);
11354             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11355         }
11356     } else if (first.offeredDraw) {
11357         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11358     } else {
11359         if (first.sendDrawOffers) {
11360             SendToProgram("draw\n", &first);
11361             userOfferedDraw = TRUE;
11362         }
11363     }
11364 }
11365
11366 void
11367 AdjournEvent()
11368 {
11369     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11370
11371     if (appData.icsActive) {
11372         SendToICS(ics_prefix);
11373         SendToICS("adjourn\n");
11374     } else {
11375         /* Currently GNU Chess doesn't offer or accept Adjourns */
11376     }
11377 }
11378
11379
11380 void
11381 AbortEvent()
11382 {
11383     /* Offer Abort or accept pending Abort offer from opponent */
11384
11385     if (appData.icsActive) {
11386         SendToICS(ics_prefix);
11387         SendToICS("abort\n");
11388     } else {
11389         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11390     }
11391 }
11392
11393 void
11394 ResignEvent()
11395 {
11396     /* Resign.  You can do this even if it's not your turn. */
11397
11398     if (appData.icsActive) {
11399         SendToICS(ics_prefix);
11400         SendToICS("resign\n");
11401     } else {
11402         switch (gameMode) {
11403           case MachinePlaysWhite:
11404             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11405             break;
11406           case MachinePlaysBlack:
11407             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11408             break;
11409           case EditGame:
11410             if (cmailMsgLoaded) {
11411                 TruncateGame();
11412                 if (WhiteOnMove(cmailOldMove)) {
11413                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11414                 } else {
11415                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11416                 }
11417                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11418             }
11419             break;
11420           default:
11421             break;
11422         }
11423     }
11424 }
11425
11426
11427 void
11428 StopObservingEvent()
11429 {
11430     /* Stop observing current games */
11431     SendToICS(ics_prefix);
11432     SendToICS("unobserve\n");
11433 }
11434
11435 void
11436 StopExaminingEvent()
11437 {
11438     /* Stop observing current game */
11439     SendToICS(ics_prefix);
11440     SendToICS("unexamine\n");
11441 }
11442
11443 void
11444 ForwardInner(target)
11445      int target;
11446 {
11447     int limit;
11448
11449     if (appData.debugMode)
11450         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11451                 target, currentMove, forwardMostMove);
11452
11453     if (gameMode == EditPosition)
11454       return;
11455
11456     if (gameMode == PlayFromGameFile && !pausing)
11457       PauseEvent();
11458
11459     if (gameMode == IcsExamining && pausing)
11460       limit = pauseExamForwardMostMove;
11461     else
11462       limit = forwardMostMove;
11463
11464     if (target > limit) target = limit;
11465
11466     if (target > 0 && moveList[target - 1][0]) {
11467         int fromX, fromY, toX, toY;
11468         toX = moveList[target - 1][2] - AAA;
11469         toY = moveList[target - 1][3] - ONE;
11470         if (moveList[target - 1][1] == '@') {
11471             if (appData.highlightLastMove) {
11472                 SetHighlights(-1, -1, toX, toY);
11473             }
11474         } else {
11475             fromX = moveList[target - 1][0] - AAA;
11476             fromY = moveList[target - 1][1] - ONE;
11477             if (target == currentMove + 1) {
11478                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11479             }
11480             if (appData.highlightLastMove) {
11481                 SetHighlights(fromX, fromY, toX, toY);
11482             }
11483         }
11484     }
11485     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11486         gameMode == Training || gameMode == PlayFromGameFile ||
11487         gameMode == AnalyzeFile) {
11488         while (currentMove < target) {
11489             SendMoveToProgram(currentMove++, &first);
11490         }
11491     } else {
11492         currentMove = target;
11493     }
11494
11495     if (gameMode == EditGame || gameMode == EndOfGame) {
11496         whiteTimeRemaining = timeRemaining[0][currentMove];
11497         blackTimeRemaining = timeRemaining[1][currentMove];
11498     }
11499     DisplayBothClocks();
11500     DisplayMove(currentMove - 1);
11501     DrawPosition(FALSE, boards[currentMove]);
11502     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11503     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11504         DisplayComment(currentMove - 1, commentList[currentMove]);
11505     }
11506 }
11507
11508
11509 void
11510 ForwardEvent()
11511 {
11512     if (gameMode == IcsExamining && !pausing) {
11513         SendToICS(ics_prefix);
11514         SendToICS("forward\n");
11515     } else {
11516         ForwardInner(currentMove + 1);
11517     }
11518 }
11519
11520 void
11521 ToEndEvent()
11522 {
11523     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11524         /* to optimze, we temporarily turn off analysis mode while we feed
11525          * the remaining moves to the engine. Otherwise we get analysis output
11526          * after each move.
11527          */
11528         if (first.analysisSupport) {
11529           SendToProgram("exit\nforce\n", &first);
11530           first.analyzing = FALSE;
11531         }
11532     }
11533
11534     if (gameMode == IcsExamining && !pausing) {
11535         SendToICS(ics_prefix);
11536         SendToICS("forward 999999\n");
11537     } else {
11538         ForwardInner(forwardMostMove);
11539     }
11540
11541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11542         /* we have fed all the moves, so reactivate analysis mode */
11543         SendToProgram("analyze\n", &first);
11544         first.analyzing = TRUE;
11545         /*first.maybeThinking = TRUE;*/
11546         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11547     }
11548 }
11549
11550 void
11551 BackwardInner(target)
11552      int target;
11553 {
11554     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11555
11556     if (appData.debugMode)
11557         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11558                 target, currentMove, forwardMostMove);
11559
11560     if (gameMode == EditPosition) return;
11561     if (currentMove <= backwardMostMove) {
11562         ClearHighlights();
11563         DrawPosition(full_redraw, boards[currentMove]);
11564         return;
11565     }
11566     if (gameMode == PlayFromGameFile && !pausing)
11567       PauseEvent();
11568
11569     if (moveList[target][0]) {
11570         int fromX, fromY, toX, toY;
11571         toX = moveList[target][2] - AAA;
11572         toY = moveList[target][3] - ONE;
11573         if (moveList[target][1] == '@') {
11574             if (appData.highlightLastMove) {
11575                 SetHighlights(-1, -1, toX, toY);
11576             }
11577         } else {
11578             fromX = moveList[target][0] - AAA;
11579             fromY = moveList[target][1] - ONE;
11580             if (target == currentMove - 1) {
11581                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11582             }
11583             if (appData.highlightLastMove) {
11584                 SetHighlights(fromX, fromY, toX, toY);
11585             }
11586         }
11587     }
11588     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11589         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11590         while (currentMove > target) {
11591             SendToProgram("undo\n", &first);
11592             currentMove--;
11593         }
11594     } else {
11595         currentMove = target;
11596     }
11597
11598     if (gameMode == EditGame || gameMode == EndOfGame) {
11599         whiteTimeRemaining = timeRemaining[0][currentMove];
11600         blackTimeRemaining = timeRemaining[1][currentMove];
11601     }
11602     DisplayBothClocks();
11603     DisplayMove(currentMove - 1);
11604     DrawPosition(full_redraw, boards[currentMove]);
11605     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11606     // [HGM] PV info: routine tests if comment empty
11607     DisplayComment(currentMove - 1, commentList[currentMove]);
11608 }
11609
11610 void
11611 BackwardEvent()
11612 {
11613     if (gameMode == IcsExamining && !pausing) {
11614         SendToICS(ics_prefix);
11615         SendToICS("backward\n");
11616     } else {
11617         BackwardInner(currentMove - 1);
11618     }
11619 }
11620
11621 void
11622 ToStartEvent()
11623 {
11624     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11625         /* to optimze, we temporarily turn off analysis mode while we undo
11626          * all the moves. Otherwise we get analysis output after each undo.
11627          */
11628         if (first.analysisSupport) {
11629           SendToProgram("exit\nforce\n", &first);
11630           first.analyzing = FALSE;
11631         }
11632     }
11633
11634     if (gameMode == IcsExamining && !pausing) {
11635         SendToICS(ics_prefix);
11636         SendToICS("backward 999999\n");
11637     } else {
11638         BackwardInner(backwardMostMove);
11639     }
11640
11641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11642         /* we have fed all the moves, so reactivate analysis mode */
11643         SendToProgram("analyze\n", &first);
11644         first.analyzing = TRUE;
11645         /*first.maybeThinking = TRUE;*/
11646         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11647     }
11648 }
11649
11650 void
11651 ToNrEvent(int to)
11652 {
11653   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11654   if (to >= forwardMostMove) to = forwardMostMove;
11655   if (to <= backwardMostMove) to = backwardMostMove;
11656   if (to < currentMove) {
11657     BackwardInner(to);
11658   } else {
11659     ForwardInner(to);
11660   }
11661 }
11662
11663 void
11664 RevertEvent()
11665 {
11666     if (gameMode != IcsExamining) {
11667         DisplayError(_("You are not examining a game"), 0);
11668         return;
11669     }
11670     if (pausing) {
11671         DisplayError(_("You can't revert while pausing"), 0);
11672         return;
11673     }
11674     SendToICS(ics_prefix);
11675     SendToICS("revert\n");
11676 }
11677
11678 void
11679 RetractMoveEvent()
11680 {
11681     switch (gameMode) {
11682       case MachinePlaysWhite:
11683       case MachinePlaysBlack:
11684         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11685             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11686             return;
11687         }
11688         if (forwardMostMove < 2) return;
11689         currentMove = forwardMostMove = forwardMostMove - 2;
11690         whiteTimeRemaining = timeRemaining[0][currentMove];
11691         blackTimeRemaining = timeRemaining[1][currentMove];
11692         DisplayBothClocks();
11693         DisplayMove(currentMove - 1);
11694         ClearHighlights();/*!! could figure this out*/
11695         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11696         SendToProgram("remove\n", &first);
11697         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11698         break;
11699
11700       case BeginningOfGame:
11701       default:
11702         break;
11703
11704       case IcsPlayingWhite:
11705       case IcsPlayingBlack:
11706         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11707             SendToICS(ics_prefix);
11708             SendToICS("takeback 2\n");
11709         } else {
11710             SendToICS(ics_prefix);
11711             SendToICS("takeback 1\n");
11712         }
11713         break;
11714     }
11715 }
11716
11717 void
11718 MoveNowEvent()
11719 {
11720     ChessProgramState *cps;
11721
11722     switch (gameMode) {
11723       case MachinePlaysWhite:
11724         if (!WhiteOnMove(forwardMostMove)) {
11725             DisplayError(_("It is your turn"), 0);
11726             return;
11727         }
11728         cps = &first;
11729         break;
11730       case MachinePlaysBlack:
11731         if (WhiteOnMove(forwardMostMove)) {
11732             DisplayError(_("It is your turn"), 0);
11733             return;
11734         }
11735         cps = &first;
11736         break;
11737       case TwoMachinesPlay:
11738         if (WhiteOnMove(forwardMostMove) ==
11739             (first.twoMachinesColor[0] == 'w')) {
11740             cps = &first;
11741         } else {
11742             cps = &second;
11743         }
11744         break;
11745       case BeginningOfGame:
11746       default:
11747         return;
11748     }
11749     SendToProgram("?\n", cps);
11750 }
11751
11752 void
11753 TruncateGameEvent()
11754 {
11755     EditGameEvent();
11756     if (gameMode != EditGame) return;
11757     TruncateGame();
11758 }
11759
11760 void
11761 TruncateGame()
11762 {
11763     if (forwardMostMove > currentMove) {
11764         if (gameInfo.resultDetails != NULL) {
11765             free(gameInfo.resultDetails);
11766             gameInfo.resultDetails = NULL;
11767             gameInfo.result = GameUnfinished;
11768         }
11769         forwardMostMove = currentMove;
11770         HistorySet(parseList, backwardMostMove, forwardMostMove,
11771                    currentMove-1);
11772     }
11773 }
11774
11775 void
11776 HintEvent()
11777 {
11778     if (appData.noChessProgram) return;
11779     switch (gameMode) {
11780       case MachinePlaysWhite:
11781         if (WhiteOnMove(forwardMostMove)) {
11782             DisplayError(_("Wait until your turn"), 0);
11783             return;
11784         }
11785         break;
11786       case BeginningOfGame:
11787       case MachinePlaysBlack:
11788         if (!WhiteOnMove(forwardMostMove)) {
11789             DisplayError(_("Wait until your turn"), 0);
11790             return;
11791         }
11792         break;
11793       default:
11794         DisplayError(_("No hint available"), 0);
11795         return;
11796     }
11797     SendToProgram("hint\n", &first);
11798     hintRequested = TRUE;
11799 }
11800
11801 void
11802 BookEvent()
11803 {
11804     if (appData.noChessProgram) return;
11805     switch (gameMode) {
11806       case MachinePlaysWhite:
11807         if (WhiteOnMove(forwardMostMove)) {
11808             DisplayError(_("Wait until your turn"), 0);
11809             return;
11810         }
11811         break;
11812       case BeginningOfGame:
11813       case MachinePlaysBlack:
11814         if (!WhiteOnMove(forwardMostMove)) {
11815             DisplayError(_("Wait until your turn"), 0);
11816             return;
11817         }
11818         break;
11819       case EditPosition:
11820         EditPositionDone();
11821         break;
11822       case TwoMachinesPlay:
11823         return;
11824       default:
11825         break;
11826     }
11827     SendToProgram("bk\n", &first);
11828     bookOutput[0] = NULLCHAR;
11829     bookRequested = TRUE;
11830 }
11831
11832 void
11833 AboutGameEvent()
11834 {
11835     char *tags = PGNTags(&gameInfo);
11836     TagsPopUp(tags, CmailMsg());
11837     free(tags);
11838 }
11839
11840 /* end button procedures */
11841
11842 void
11843 PrintPosition(fp, move)
11844      FILE *fp;
11845      int move;
11846 {
11847     int i, j;
11848
11849     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11850         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11851             char c = PieceToChar(boards[move][i][j]);
11852             fputc(c == 'x' ? '.' : c, fp);
11853             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11854         }
11855     }
11856     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11857       fprintf(fp, "white to play\n");
11858     else
11859       fprintf(fp, "black to play\n");
11860 }
11861
11862 void
11863 PrintOpponents(fp)
11864      FILE *fp;
11865 {
11866     if (gameInfo.white != NULL) {
11867         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11868     } else {
11869         fprintf(fp, "\n");
11870     }
11871 }
11872
11873 /* Find last component of program's own name, using some heuristics */
11874 void
11875 TidyProgramName(prog, host, buf)
11876      char *prog, *host, buf[MSG_SIZ];
11877 {
11878     char *p, *q;
11879     int local = (strcmp(host, "localhost") == 0);
11880     while (!local && (p = strchr(prog, ';')) != NULL) {
11881         p++;
11882         while (*p == ' ') p++;
11883         prog = p;
11884     }
11885     if (*prog == '"' || *prog == '\'') {
11886         q = strchr(prog + 1, *prog);
11887     } else {
11888         q = strchr(prog, ' ');
11889     }
11890     if (q == NULL) q = prog + strlen(prog);
11891     p = q;
11892     while (p >= prog && *p != '/' && *p != '\\') p--;
11893     p++;
11894     if(p == prog && *p == '"') p++;
11895     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11896     memcpy(buf, p, q - p);
11897     buf[q - p] = NULLCHAR;
11898     if (!local) {
11899         strcat(buf, "@");
11900         strcat(buf, host);
11901     }
11902 }
11903
11904 char *
11905 TimeControlTagValue()
11906 {
11907     char buf[MSG_SIZ];
11908     if (!appData.clockMode) {
11909         strcpy(buf, "-");
11910     } else if (movesPerSession > 0) {
11911         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11912     } else if (timeIncrement == 0) {
11913         sprintf(buf, "%ld", timeControl/1000);
11914     } else {
11915         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11916     }
11917     return StrSave(buf);
11918 }
11919
11920 void
11921 SetGameInfo()
11922 {
11923     /* This routine is used only for certain modes */
11924     VariantClass v = gameInfo.variant;
11925     ClearGameInfo(&gameInfo);
11926     gameInfo.variant = v;
11927
11928     switch (gameMode) {
11929       case MachinePlaysWhite:
11930         gameInfo.event = StrSave( appData.pgnEventHeader );
11931         gameInfo.site = StrSave(HostName());
11932         gameInfo.date = PGNDate();
11933         gameInfo.round = StrSave("-");
11934         gameInfo.white = StrSave(first.tidy);
11935         gameInfo.black = StrSave(UserName());
11936         gameInfo.timeControl = TimeControlTagValue();
11937         break;
11938
11939       case MachinePlaysBlack:
11940         gameInfo.event = StrSave( appData.pgnEventHeader );
11941         gameInfo.site = StrSave(HostName());
11942         gameInfo.date = PGNDate();
11943         gameInfo.round = StrSave("-");
11944         gameInfo.white = StrSave(UserName());
11945         gameInfo.black = StrSave(first.tidy);
11946         gameInfo.timeControl = TimeControlTagValue();
11947         break;
11948
11949       case TwoMachinesPlay:
11950         gameInfo.event = StrSave( appData.pgnEventHeader );
11951         gameInfo.site = StrSave(HostName());
11952         gameInfo.date = PGNDate();
11953         if (matchGame > 0) {
11954             char buf[MSG_SIZ];
11955             sprintf(buf, "%d", matchGame);
11956             gameInfo.round = StrSave(buf);
11957         } else {
11958             gameInfo.round = StrSave("-");
11959         }
11960         if (first.twoMachinesColor[0] == 'w') {
11961             gameInfo.white = StrSave(first.tidy);
11962             gameInfo.black = StrSave(second.tidy);
11963         } else {
11964             gameInfo.white = StrSave(second.tidy);
11965             gameInfo.black = StrSave(first.tidy);
11966         }
11967         gameInfo.timeControl = TimeControlTagValue();
11968         break;
11969
11970       case EditGame:
11971         gameInfo.event = StrSave("Edited game");
11972         gameInfo.site = StrSave(HostName());
11973         gameInfo.date = PGNDate();
11974         gameInfo.round = StrSave("-");
11975         gameInfo.white = StrSave("-");
11976         gameInfo.black = StrSave("-");
11977         break;
11978
11979       case EditPosition:
11980         gameInfo.event = StrSave("Edited position");
11981         gameInfo.site = StrSave(HostName());
11982         gameInfo.date = PGNDate();
11983         gameInfo.round = StrSave("-");
11984         gameInfo.white = StrSave("-");
11985         gameInfo.black = StrSave("-");
11986         break;
11987
11988       case IcsPlayingWhite:
11989       case IcsPlayingBlack:
11990       case IcsObserving:
11991       case IcsExamining:
11992         break;
11993
11994       case PlayFromGameFile:
11995         gameInfo.event = StrSave("Game from non-PGN file");
11996         gameInfo.site = StrSave(HostName());
11997         gameInfo.date = PGNDate();
11998         gameInfo.round = StrSave("-");
11999         gameInfo.white = StrSave("?");
12000         gameInfo.black = StrSave("?");
12001         break;
12002
12003       default:
12004         break;
12005     }
12006 }
12007
12008 void
12009 ReplaceComment(index, text)
12010      int index;
12011      char *text;
12012 {
12013     int len;
12014
12015     while (*text == '\n') text++;
12016     len = strlen(text);
12017     while (len > 0 && text[len - 1] == '\n') len--;
12018
12019     if (commentList[index] != NULL)
12020       free(commentList[index]);
12021
12022     if (len == 0) {
12023         commentList[index] = NULL;
12024         return;
12025     }
12026     commentList[index] = (char *) malloc(len + 2);
12027     strncpy(commentList[index], text, len);
12028     commentList[index][len] = '\n';
12029     commentList[index][len + 1] = NULLCHAR;
12030 }
12031
12032 void
12033 CrushCRs(text)
12034      char *text;
12035 {
12036   char *p = text;
12037   char *q = text;
12038   char ch;
12039
12040   do {
12041     ch = *p++;
12042     if (ch == '\r') continue;
12043     *q++ = ch;
12044   } while (ch != '\0');
12045 }
12046
12047 void
12048 AppendComment(index, text)
12049      int index;
12050      char *text;
12051 {
12052     int oldlen, len;
12053     char *old;
12054
12055     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12056
12057     CrushCRs(text);
12058     while (*text == '\n') text++;
12059     len = strlen(text);
12060     while (len > 0 && text[len - 1] == '\n') len--;
12061
12062     if (len == 0) return;
12063
12064     if (commentList[index] != NULL) {
12065         old = commentList[index];
12066         oldlen = strlen(old);
12067         commentList[index] = (char *) malloc(oldlen + len + 2);
12068         strcpy(commentList[index], old);
12069         free(old);
12070         strncpy(&commentList[index][oldlen], text, len);
12071         commentList[index][oldlen + len] = '\n';
12072         commentList[index][oldlen + len + 1] = NULLCHAR;
12073     } else {
12074         commentList[index] = (char *) malloc(len + 2);
12075         strncpy(commentList[index], text, len);
12076         commentList[index][len] = '\n';
12077         commentList[index][len + 1] = NULLCHAR;
12078     }
12079 }
12080
12081 static char * FindStr( char * text, char * sub_text )
12082 {
12083     char * result = strstr( text, sub_text );
12084
12085     if( result != NULL ) {
12086         result += strlen( sub_text );
12087     }
12088
12089     return result;
12090 }
12091
12092 /* [AS] Try to extract PV info from PGN comment */
12093 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12094 char *GetInfoFromComment( int index, char * text )
12095 {
12096     char * sep = text;
12097
12098     if( text != NULL && index > 0 ) {
12099         int score = 0;
12100         int depth = 0;
12101         int time = -1, sec = 0, deci;
12102         char * s_eval = FindStr( text, "[%eval " );
12103         char * s_emt = FindStr( text, "[%emt " );
12104
12105         if( s_eval != NULL || s_emt != NULL ) {
12106             /* New style */
12107             char delim;
12108
12109             if( s_eval != NULL ) {
12110                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12111                     return text;
12112                 }
12113
12114                 if( delim != ']' ) {
12115                     return text;
12116                 }
12117             }
12118
12119             if( s_emt != NULL ) {
12120             }
12121         }
12122         else {
12123             /* We expect something like: [+|-]nnn.nn/dd */
12124             int score_lo = 0;
12125
12126             sep = strchr( text, '/' );
12127             if( sep == NULL || sep < (text+4) ) {
12128                 return text;
12129             }
12130
12131             time = -1; sec = -1; deci = -1;
12132             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12133                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12134                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12135                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12136                 return text;
12137             }
12138
12139             if( score_lo < 0 || score_lo >= 100 ) {
12140                 return text;
12141             }
12142
12143             if(sec >= 0) time = 600*time + 10*sec; else
12144             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12145
12146             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12147
12148             /* [HGM] PV time: now locate end of PV info */
12149             while( *++sep >= '0' && *sep <= '9'); // strip depth
12150             if(time >= 0)
12151             while( *++sep >= '0' && *sep <= '9'); // strip time
12152             if(sec >= 0)
12153             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12154             if(deci >= 0)
12155             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12156             while(*sep == ' ') sep++;
12157         }
12158
12159         if( depth <= 0 ) {
12160             return text;
12161         }
12162
12163         if( time < 0 ) {
12164             time = -1;
12165         }
12166
12167         pvInfoList[index-1].depth = depth;
12168         pvInfoList[index-1].score = score;
12169         pvInfoList[index-1].time  = 10*time; // centi-sec
12170     }
12171     return sep;
12172 }
12173
12174 void
12175 SendToProgram(message, cps)
12176      char *message;
12177      ChessProgramState *cps;
12178 {
12179     int count, outCount, error;
12180     char buf[MSG_SIZ];
12181
12182     if (cps->pr == NULL) return;
12183     Attention(cps);
12184
12185     if (appData.debugMode) {
12186         TimeMark now;
12187         GetTimeMark(&now);
12188         fprintf(debugFP, "%ld >%-6s: %s",
12189                 SubtractTimeMarks(&now, &programStartTime),
12190                 cps->which, message);
12191     }
12192
12193     count = strlen(message);
12194     outCount = OutputToProcess(cps->pr, message, count, &error);
12195     if (outCount < count && !exiting
12196                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12197         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12198         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12199             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12200                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12201                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12202             } else {
12203                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12204             }
12205             gameInfo.resultDetails = buf;
12206         }
12207         DisplayFatalError(buf, error, 1);
12208     }
12209 }
12210
12211 void
12212 ReceiveFromProgram(isr, closure, message, count, error)
12213      InputSourceRef isr;
12214      VOIDSTAR closure;
12215      char *message;
12216      int count;
12217      int error;
12218 {
12219     char *end_str;
12220     char buf[MSG_SIZ];
12221     ChessProgramState *cps = (ChessProgramState *)closure;
12222
12223     if (isr != cps->isr) return; /* Killed intentionally */
12224     if (count <= 0) {
12225         if (count == 0) {
12226             sprintf(buf,
12227                     _("Error: %s chess program (%s) exited unexpectedly"),
12228                     cps->which, cps->program);
12229         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12230                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12231                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12232                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12233                 } else {
12234                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12235                 }
12236                 gameInfo.resultDetails = buf;
12237             }
12238             RemoveInputSource(cps->isr);
12239             DisplayFatalError(buf, 0, 1);
12240         } else {
12241             sprintf(buf,
12242                     _("Error reading from %s chess program (%s)"),
12243                     cps->which, cps->program);
12244             RemoveInputSource(cps->isr);
12245
12246             /* [AS] Program is misbehaving badly... kill it */
12247             if( count == -2 ) {
12248                 DestroyChildProcess( cps->pr, 9 );
12249                 cps->pr = NoProc;
12250             }
12251
12252             DisplayFatalError(buf, error, 1);
12253         }
12254         return;
12255     }
12256
12257     if ((end_str = strchr(message, '\r')) != NULL)
12258       *end_str = NULLCHAR;
12259     if ((end_str = strchr(message, '\n')) != NULL)
12260       *end_str = NULLCHAR;
12261
12262     if (appData.debugMode) {
12263         TimeMark now; int print = 1;
12264         char *quote = ""; char c; int i;
12265
12266         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12267                 char start = message[0];
12268                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12269                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12270                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12271                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12272                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12273                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12274                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12275                         { quote = "# "; print = (appData.engineComments == 2); }
12276                 message[0] = start; // restore original message
12277         }
12278         if(print) {
12279                 GetTimeMark(&now);
12280                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12281                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12282                         quote,
12283                         message);
12284         }
12285     }
12286
12287     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12288     if (appData.icsEngineAnalyze) {
12289         if (strstr(message, "whisper") != NULL ||
12290              strstr(message, "kibitz") != NULL ||
12291             strstr(message, "tellics") != NULL) return;
12292     }
12293
12294     HandleMachineMove(message, cps);
12295 }
12296
12297
12298 void
12299 SendTimeControl(cps, mps, tc, inc, sd, st)
12300      ChessProgramState *cps;
12301      int mps, inc, sd, st;
12302      long tc;
12303 {
12304     char buf[MSG_SIZ];
12305     int seconds;
12306
12307     if( timeControl_2 > 0 ) {
12308         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12309             tc = timeControl_2;
12310         }
12311     }
12312     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12313     inc /= cps->timeOdds;
12314     st  /= cps->timeOdds;
12315
12316     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12317
12318     if (st > 0) {
12319       /* Set exact time per move, normally using st command */
12320       if (cps->stKludge) {
12321         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12322         seconds = st % 60;
12323         if (seconds == 0) {
12324           sprintf(buf, "level 1 %d\n", st/60);
12325         } else {
12326           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12327         }
12328       } else {
12329         sprintf(buf, "st %d\n", st);
12330       }
12331     } else {
12332       /* Set conventional or incremental time control, using level command */
12333       if (seconds == 0) {
12334         /* Note old gnuchess bug -- minutes:seconds used to not work.
12335            Fixed in later versions, but still avoid :seconds
12336            when seconds is 0. */
12337         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12338       } else {
12339         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12340                 seconds, inc/1000);
12341       }
12342     }
12343     SendToProgram(buf, cps);
12344
12345     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12346     /* Orthogonally, limit search to given depth */
12347     if (sd > 0) {
12348       if (cps->sdKludge) {
12349         sprintf(buf, "depth\n%d\n", sd);
12350       } else {
12351         sprintf(buf, "sd %d\n", sd);
12352       }
12353       SendToProgram(buf, cps);
12354     }
12355
12356     if(cps->nps > 0) { /* [HGM] nps */
12357         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12358         else {
12359                 sprintf(buf, "nps %d\n", cps->nps);
12360               SendToProgram(buf, cps);
12361         }
12362     }
12363 }
12364
12365 ChessProgramState *WhitePlayer()
12366 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12367 {
12368     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12369        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12370         return &second;
12371     return &first;
12372 }
12373
12374 void
12375 SendTimeRemaining(cps, machineWhite)
12376      ChessProgramState *cps;
12377      int /*boolean*/ machineWhite;
12378 {
12379     char message[MSG_SIZ];
12380     long time, otime;
12381
12382     /* Note: this routine must be called when the clocks are stopped
12383        or when they have *just* been set or switched; otherwise
12384        it will be off by the time since the current tick started.
12385     */
12386     if (machineWhite) {
12387         time = whiteTimeRemaining / 10;
12388         otime = blackTimeRemaining / 10;
12389     } else {
12390         time = blackTimeRemaining / 10;
12391         otime = whiteTimeRemaining / 10;
12392     }
12393     /* [HGM] translate opponent's time by time-odds factor */
12394     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12395     if (appData.debugMode) {
12396         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12397     }
12398
12399     if (time <= 0) time = 1;
12400     if (otime <= 0) otime = 1;
12401
12402     sprintf(message, "time %ld\n", time);
12403     SendToProgram(message, cps);
12404
12405     sprintf(message, "otim %ld\n", otime);
12406     SendToProgram(message, cps);
12407 }
12408
12409 int
12410 BoolFeature(p, name, loc, cps)
12411      char **p;
12412      char *name;
12413      int *loc;
12414      ChessProgramState *cps;
12415 {
12416   char buf[MSG_SIZ];
12417   int len = strlen(name);
12418   int val;
12419   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12420     (*p) += len + 1;
12421     sscanf(*p, "%d", &val);
12422     *loc = (val != 0);
12423     while (**p && **p != ' ') (*p)++;
12424     sprintf(buf, "accepted %s\n", name);
12425     SendToProgram(buf, cps);
12426     return TRUE;
12427   }
12428   return FALSE;
12429 }
12430
12431 int
12432 IntFeature(p, name, loc, cps)
12433      char **p;
12434      char *name;
12435      int *loc;
12436      ChessProgramState *cps;
12437 {
12438   char buf[MSG_SIZ];
12439   int len = strlen(name);
12440   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12441     (*p) += len + 1;
12442     sscanf(*p, "%d", loc);
12443     while (**p && **p != ' ') (*p)++;
12444     sprintf(buf, "accepted %s\n", name);
12445     SendToProgram(buf, cps);
12446     return TRUE;
12447   }
12448   return FALSE;
12449 }
12450
12451 int
12452 StringFeature(p, name, loc, cps)
12453      char **p;
12454      char *name;
12455      char loc[];
12456      ChessProgramState *cps;
12457 {
12458   char buf[MSG_SIZ];
12459   int len = strlen(name);
12460   if (strncmp((*p), name, len) == 0
12461       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12462     (*p) += len + 2;
12463     sscanf(*p, "%[^\"]", loc);
12464     while (**p && **p != '\"') (*p)++;
12465     if (**p == '\"') (*p)++;
12466     sprintf(buf, "accepted %s\n", name);
12467     SendToProgram(buf, cps);
12468     return TRUE;
12469   }
12470   return FALSE;
12471 }
12472
12473 int
12474 ParseOption(Option *opt, ChessProgramState *cps)
12475 // [HGM] options: process the string that defines an engine option, and determine
12476 // name, type, default value, and allowed value range
12477 {
12478         char *p, *q, buf[MSG_SIZ];
12479         int n, min = (-1)<<31, max = 1<<31, def;
12480
12481         if(p = strstr(opt->name, " -spin ")) {
12482             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12483             if(max < min) max = min; // enforce consistency
12484             if(def < min) def = min;
12485             if(def > max) def = max;
12486             opt->value = def;
12487             opt->min = min;
12488             opt->max = max;
12489             opt->type = Spin;
12490         } else if((p = strstr(opt->name, " -slider "))) {
12491             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12492             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12493             if(max < min) max = min; // enforce consistency
12494             if(def < min) def = min;
12495             if(def > max) def = max;
12496             opt->value = def;
12497             opt->min = min;
12498             opt->max = max;
12499             opt->type = Spin; // Slider;
12500         } else if((p = strstr(opt->name, " -string "))) {
12501             opt->textValue = p+9;
12502             opt->type = TextBox;
12503         } else if((p = strstr(opt->name, " -file "))) {
12504             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12505             opt->textValue = p+7;
12506             opt->type = TextBox; // FileName;
12507         } else if((p = strstr(opt->name, " -path "))) {
12508             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12509             opt->textValue = p+7;
12510             opt->type = TextBox; // PathName;
12511         } else if(p = strstr(opt->name, " -check ")) {
12512             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12513             opt->value = (def != 0);
12514             opt->type = CheckBox;
12515         } else if(p = strstr(opt->name, " -combo ")) {
12516             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12517             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12518             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12519             opt->value = n = 0;
12520             while(q = StrStr(q, " /// ")) {
12521                 n++; *q = 0;    // count choices, and null-terminate each of them
12522                 q += 5;
12523                 if(*q == '*') { // remember default, which is marked with * prefix
12524                     q++;
12525                     opt->value = n;
12526                 }
12527                 cps->comboList[cps->comboCnt++] = q;
12528             }
12529             cps->comboList[cps->comboCnt++] = NULL;
12530             opt->max = n + 1;
12531             opt->type = ComboBox;
12532         } else if(p = strstr(opt->name, " -button")) {
12533             opt->type = Button;
12534         } else if(p = strstr(opt->name, " -save")) {
12535             opt->type = SaveButton;
12536         } else return FALSE;
12537         *p = 0; // terminate option name
12538         // now look if the command-line options define a setting for this engine option.
12539         if(cps->optionSettings && cps->optionSettings[0])
12540             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12541         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12542                 sprintf(buf, "option %s", p);
12543                 if(p = strstr(buf, ",")) *p = 0;
12544                 strcat(buf, "\n");
12545                 SendToProgram(buf, cps);
12546         }
12547         return TRUE;
12548 }
12549
12550 void
12551 FeatureDone(cps, val)
12552      ChessProgramState* cps;
12553      int val;
12554 {
12555   DelayedEventCallback cb = GetDelayedEvent();
12556   if ((cb == InitBackEnd3 && cps == &first) ||
12557       (cb == TwoMachinesEventIfReady && cps == &second)) {
12558     CancelDelayedEvent();
12559     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12560   }
12561   cps->initDone = val;
12562 }
12563
12564 /* Parse feature command from engine */
12565 void
12566 ParseFeatures(args, cps)
12567      char* args;
12568      ChessProgramState *cps;
12569 {
12570   char *p = args;
12571   char *q;
12572   int val;
12573   char buf[MSG_SIZ];
12574
12575   for (;;) {
12576     while (*p == ' ') p++;
12577     if (*p == NULLCHAR) return;
12578
12579     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12580     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12581     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12582     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12583     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12584     if (BoolFeature(&p, "reuse", &val, cps)) {
12585       /* Engine can disable reuse, but can't enable it if user said no */
12586       if (!val) cps->reuse = FALSE;
12587       continue;
12588     }
12589     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12590     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12591       if (gameMode == TwoMachinesPlay) {
12592         DisplayTwoMachinesTitle();
12593       } else {
12594         DisplayTitle("");
12595       }
12596       continue;
12597     }
12598     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12599     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12600     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12601     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12602     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12603     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12604     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12605     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12606     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12607     if (IntFeature(&p, "done", &val, cps)) {
12608       FeatureDone(cps, val);
12609       continue;
12610     }
12611     /* Added by Tord: */
12612     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12613     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12614     /* End of additions by Tord */
12615
12616     /* [HGM] added features: */
12617     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12618     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12619     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12620     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12621     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12622     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12623     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12624         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12625             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12626             SendToProgram(buf, cps);
12627             continue;
12628         }
12629         if(cps->nrOptions >= MAX_OPTIONS) {
12630             cps->nrOptions--;
12631             sprintf(buf, "%s engine has too many options\n", cps->which);
12632             DisplayError(buf, 0);
12633         }
12634         continue;
12635     }
12636     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12637     /* End of additions by HGM */
12638
12639     /* unknown feature: complain and skip */
12640     q = p;
12641     while (*q && *q != '=') q++;
12642     sprintf(buf, "rejected %.*s\n", q-p, p);
12643     SendToProgram(buf, cps);
12644     p = q;
12645     if (*p == '=') {
12646       p++;
12647       if (*p == '\"') {
12648         p++;
12649         while (*p && *p != '\"') p++;
12650         if (*p == '\"') p++;
12651       } else {
12652         while (*p && *p != ' ') p++;
12653       }
12654     }
12655   }
12656
12657 }
12658
12659 void
12660 PeriodicUpdatesEvent(newState)
12661      int newState;
12662 {
12663     if (newState == appData.periodicUpdates)
12664       return;
12665
12666     appData.periodicUpdates=newState;
12667
12668     /* Display type changes, so update it now */
12669     DisplayAnalysis();
12670
12671     /* Get the ball rolling again... */
12672     if (newState) {
12673         AnalysisPeriodicEvent(1);
12674         StartAnalysisClock();
12675     }
12676 }
12677
12678 void
12679 PonderNextMoveEvent(newState)
12680      int newState;
12681 {
12682     if (newState == appData.ponderNextMove) return;
12683     if (gameMode == EditPosition) EditPositionDone();
12684     if (newState) {
12685         SendToProgram("hard\n", &first);
12686         if (gameMode == TwoMachinesPlay) {
12687             SendToProgram("hard\n", &second);
12688         }
12689     } else {
12690         SendToProgram("easy\n", &first);
12691         thinkOutput[0] = NULLCHAR;
12692         if (gameMode == TwoMachinesPlay) {
12693             SendToProgram("easy\n", &second);
12694         }
12695     }
12696     appData.ponderNextMove = newState;
12697 }
12698
12699 void
12700 NewSettingEvent(option, command, value)
12701      char *command;
12702      int option, value;
12703 {
12704     char buf[MSG_SIZ];
12705
12706     if (gameMode == EditPosition) EditPositionDone();
12707     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12708     SendToProgram(buf, &first);
12709     if (gameMode == TwoMachinesPlay) {
12710         SendToProgram(buf, &second);
12711     }
12712 }
12713
12714 void
12715 ShowThinkingEvent()
12716 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12717 {
12718     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12719     int newState = appData.showThinking
12720         // [HGM] thinking: other features now need thinking output as well
12721         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12722
12723     if (oldState == newState) return;
12724     oldState = newState;
12725     if (gameMode == EditPosition) EditPositionDone();
12726     if (oldState) {
12727         SendToProgram("post\n", &first);
12728         if (gameMode == TwoMachinesPlay) {
12729             SendToProgram("post\n", &second);
12730         }
12731     } else {
12732         SendToProgram("nopost\n", &first);
12733         thinkOutput[0] = NULLCHAR;
12734         if (gameMode == TwoMachinesPlay) {
12735             SendToProgram("nopost\n", &second);
12736         }
12737     }
12738 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12739 }
12740
12741 void
12742 AskQuestionEvent(title, question, replyPrefix, which)
12743      char *title; char *question; char *replyPrefix; char *which;
12744 {
12745   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12746   if (pr == NoProc) return;
12747   AskQuestion(title, question, replyPrefix, pr);
12748 }
12749
12750 void
12751 DisplayMove(moveNumber)
12752      int moveNumber;
12753 {
12754     char message[MSG_SIZ];
12755     char res[MSG_SIZ];
12756     char cpThinkOutput[MSG_SIZ];
12757
12758     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12759
12760     if (moveNumber == forwardMostMove - 1 ||
12761         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12762
12763         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12764
12765         if (strchr(cpThinkOutput, '\n')) {
12766             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12767         }
12768     } else {
12769         *cpThinkOutput = NULLCHAR;
12770     }
12771
12772     /* [AS] Hide thinking from human user */
12773     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12774         *cpThinkOutput = NULLCHAR;
12775         if( thinkOutput[0] != NULLCHAR ) {
12776             int i;
12777
12778             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12779                 cpThinkOutput[i] = '.';
12780             }
12781             cpThinkOutput[i] = NULLCHAR;
12782             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12783         }
12784     }
12785
12786     if (moveNumber == forwardMostMove - 1 &&
12787         gameInfo.resultDetails != NULL) {
12788         if (gameInfo.resultDetails[0] == NULLCHAR) {
12789             sprintf(res, " %s", PGNResult(gameInfo.result));
12790         } else {
12791             sprintf(res, " {%s} %s",
12792                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12793         }
12794     } else {
12795         res[0] = NULLCHAR;
12796     }
12797
12798     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12799         DisplayMessage(res, cpThinkOutput);
12800     } else {
12801         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12802                 WhiteOnMove(moveNumber) ? " " : ".. ",
12803                 parseList[moveNumber], res);
12804         DisplayMessage(message, cpThinkOutput);
12805     }
12806 }
12807
12808 void
12809 DisplayAnalysisText(text)
12810      char *text;
12811 {
12812     char buf[MSG_SIZ];
12813
12814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12815                || appData.icsEngineAnalyze) {
12816         sprintf(buf, "Analysis (%s)", first.tidy);
12817         AnalysisPopUp(buf, text);
12818     }
12819 }
12820
12821 static int
12822 only_one_move(str)
12823      char *str;
12824 {
12825     while (*str && isspace(*str)) ++str;
12826     while (*str && !isspace(*str)) ++str;
12827     if (!*str) return 1;
12828     while (*str && isspace(*str)) ++str;
12829     if (!*str) return 1;
12830     return 0;
12831 }
12832
12833 void
12834 DisplayAnalysis()
12835 {
12836     char buf[MSG_SIZ];
12837     char lst[MSG_SIZ / 2];
12838     double nps;
12839     static char *xtra[] = { "", " (--)", " (++)" };
12840     int h, m, s, cs;
12841
12842     if (programStats.time == 0) {
12843         programStats.time = 1;
12844     }
12845
12846     if (programStats.got_only_move) {
12847         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12848     } else {
12849         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12850
12851         nps = (u64ToDouble(programStats.nodes) /
12852              ((double)programStats.time /100.0));
12853
12854         cs = programStats.time % 100;
12855         s = programStats.time / 100;
12856         h = (s / (60*60));
12857         s = s - h*60*60;
12858         m = (s/60);
12859         s = s - m*60;
12860
12861         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12862           if (programStats.move_name[0] != NULLCHAR) {
12863             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12864                     programStats.depth,
12865                     programStats.nr_moves-programStats.moves_left,
12866                     programStats.nr_moves, programStats.move_name,
12867                     ((float)programStats.score)/100.0, lst,
12868                     only_one_move(lst)?
12869                     xtra[programStats.got_fail] : "",
12870                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12871           } else {
12872             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12873                     programStats.depth,
12874                     programStats.nr_moves-programStats.moves_left,
12875                     programStats.nr_moves, ((float)programStats.score)/100.0,
12876                     lst,
12877                     only_one_move(lst)?
12878                     xtra[programStats.got_fail] : "",
12879                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12880           }
12881         } else {
12882             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12883                     programStats.depth,
12884                     ((float)programStats.score)/100.0,
12885                     lst,
12886                     only_one_move(lst)?
12887                     xtra[programStats.got_fail] : "",
12888                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12889         }
12890     }
12891     DisplayAnalysisText(buf);
12892 }
12893
12894 void
12895 DisplayComment(moveNumber, text)
12896      int moveNumber;
12897      char *text;
12898 {
12899     char title[MSG_SIZ];
12900     char buf[8000]; // comment can be long!
12901     int score, depth;
12902
12903     if( appData.autoDisplayComment ) {
12904         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12905             strcpy(title, "Comment");
12906         } else {
12907             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12908                     WhiteOnMove(moveNumber) ? " " : ".. ",
12909                     parseList[moveNumber]);
12910         }
12911         // [HGM] PV info: display PV info together with (or as) comment
12912         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12913             if(text == NULL) text = "";
12914             score = pvInfoList[moveNumber].score;
12915             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12916                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12917             text = buf;
12918         }
12919     } else title[0] = 0;
12920
12921     if (text != NULL)
12922         CommentPopUp(title, text);
12923 }
12924
12925 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12926  * might be busy thinking or pondering.  It can be omitted if your
12927  * gnuchess is configured to stop thinking immediately on any user
12928  * input.  However, that gnuchess feature depends on the FIONREAD
12929  * ioctl, which does not work properly on some flavors of Unix.
12930  */
12931 void
12932 Attention(cps)
12933      ChessProgramState *cps;
12934 {
12935 #if ATTENTION
12936     if (!cps->useSigint) return;
12937     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12938     switch (gameMode) {
12939       case MachinePlaysWhite:
12940       case MachinePlaysBlack:
12941       case TwoMachinesPlay:
12942       case IcsPlayingWhite:
12943       case IcsPlayingBlack:
12944       case AnalyzeMode:
12945       case AnalyzeFile:
12946         /* Skip if we know it isn't thinking */
12947         if (!cps->maybeThinking) return;
12948         if (appData.debugMode)
12949           fprintf(debugFP, "Interrupting %s\n", cps->which);
12950         InterruptChildProcess(cps->pr);
12951         cps->maybeThinking = FALSE;
12952         break;
12953       default:
12954         break;
12955     }
12956 #endif /*ATTENTION*/
12957 }
12958
12959 int
12960 CheckFlags()
12961 {
12962     if (whiteTimeRemaining <= 0) {
12963         if (!whiteFlag) {
12964             whiteFlag = TRUE;
12965             if (appData.icsActive) {
12966                 if (appData.autoCallFlag &&
12967                     gameMode == IcsPlayingBlack && !blackFlag) {
12968                   SendToICS(ics_prefix);
12969                   SendToICS("flag\n");
12970                 }
12971             } else {
12972                 if (blackFlag) {
12973                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12974                 } else {
12975                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12976                     if (appData.autoCallFlag) {
12977                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12978                         return TRUE;
12979                     }
12980                 }
12981             }
12982         }
12983     }
12984     if (blackTimeRemaining <= 0) {
12985         if (!blackFlag) {
12986             blackFlag = TRUE;
12987             if (appData.icsActive) {
12988                 if (appData.autoCallFlag &&
12989                     gameMode == IcsPlayingWhite && !whiteFlag) {
12990                   SendToICS(ics_prefix);
12991                   SendToICS("flag\n");
12992                 }
12993             } else {
12994                 if (whiteFlag) {
12995                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12996                 } else {
12997                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12998                     if (appData.autoCallFlag) {
12999                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13000                         return TRUE;
13001                     }
13002                 }
13003             }
13004         }
13005     }
13006     return FALSE;
13007 }
13008
13009 void
13010 CheckTimeControl()
13011 {
13012     if (!appData.clockMode || appData.icsActive ||
13013         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13014
13015     /*
13016      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13017      */
13018     if ( !WhiteOnMove(forwardMostMove) )
13019         /* White made time control */
13020         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13021         /* [HGM] time odds: correct new time quota for time odds! */
13022                                             / WhitePlayer()->timeOdds;
13023       else
13024         /* Black made time control */
13025         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13026                                             / WhitePlayer()->other->timeOdds;
13027 }
13028
13029 void
13030 DisplayBothClocks()
13031 {
13032     int wom = gameMode == EditPosition ?
13033       !blackPlaysFirst : WhiteOnMove(currentMove);
13034     DisplayWhiteClock(whiteTimeRemaining, wom);
13035     DisplayBlackClock(blackTimeRemaining, !wom);
13036 }
13037
13038
13039 /* Timekeeping seems to be a portability nightmare.  I think everyone
13040    has ftime(), but I'm really not sure, so I'm including some ifdefs
13041    to use other calls if you don't.  Clocks will be less accurate if
13042    you have neither ftime nor gettimeofday.
13043 */
13044
13045 /* VS 2008 requires the #include outside of the function */
13046 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13047 #include <sys/timeb.h>
13048 #endif
13049
13050 /* Get the current time as a TimeMark */
13051 void
13052 GetTimeMark(tm)
13053      TimeMark *tm;
13054 {
13055 #if HAVE_GETTIMEOFDAY
13056
13057     struct timeval timeVal;
13058     struct timezone timeZone;
13059
13060     gettimeofday(&timeVal, &timeZone);
13061     tm->sec = (long) timeVal.tv_sec;
13062     tm->ms = (int) (timeVal.tv_usec / 1000L);
13063
13064 #else /*!HAVE_GETTIMEOFDAY*/
13065 #if HAVE_FTIME
13066
13067 // include <sys/timeb.h> / moved to just above start of function
13068     struct timeb timeB;
13069
13070     ftime(&timeB);
13071     tm->sec = (long) timeB.time;
13072     tm->ms = (int) timeB.millitm;
13073
13074 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13075     tm->sec = (long) time(NULL);
13076     tm->ms = 0;
13077 #endif
13078 #endif
13079 }
13080
13081 /* Return the difference in milliseconds between two
13082    time marks.  We assume the difference will fit in a long!
13083 */
13084 long
13085 SubtractTimeMarks(tm2, tm1)
13086      TimeMark *tm2, *tm1;
13087 {
13088     return 1000L*(tm2->sec - tm1->sec) +
13089            (long) (tm2->ms - tm1->ms);
13090 }
13091
13092
13093 /*
13094  * Code to manage the game clocks.
13095  *
13096  * In tournament play, black starts the clock and then white makes a move.
13097  * We give the human user a slight advantage if he is playing white---the
13098  * clocks don't run until he makes his first move, so it takes zero time.
13099  * Also, we don't account for network lag, so we could get out of sync
13100  * with GNU Chess's clock -- but then, referees are always right.
13101  */
13102
13103 static TimeMark tickStartTM;
13104 static long intendedTickLength;
13105
13106 long
13107 NextTickLength(timeRemaining)
13108      long timeRemaining;
13109 {
13110     long nominalTickLength, nextTickLength;
13111
13112     if (timeRemaining > 0L && timeRemaining <= 10000L)
13113       nominalTickLength = 100L;
13114     else
13115       nominalTickLength = 1000L;
13116     nextTickLength = timeRemaining % nominalTickLength;
13117     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13118
13119     return nextTickLength;
13120 }
13121
13122 /* Adjust clock one minute up or down */
13123 void
13124 AdjustClock(Boolean which, int dir)
13125 {
13126     if(which) blackTimeRemaining += 60000*dir;
13127     else      whiteTimeRemaining += 60000*dir;
13128     DisplayBothClocks();
13129 }
13130
13131 /* Stop clocks and reset to a fresh time control */
13132 void
13133 ResetClocks()
13134 {
13135     (void) StopClockTimer();
13136     if (appData.icsActive) {
13137         whiteTimeRemaining = blackTimeRemaining = 0;
13138     } else { /* [HGM] correct new time quote for time odds */
13139         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13140         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13141     }
13142     if (whiteFlag || blackFlag) {
13143         DisplayTitle("");
13144         whiteFlag = blackFlag = FALSE;
13145     }
13146     DisplayBothClocks();
13147 }
13148
13149 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13150
13151 /* Decrement running clock by amount of time that has passed */
13152 void
13153 DecrementClocks()
13154 {
13155     long timeRemaining;
13156     long lastTickLength, fudge;
13157     TimeMark now;
13158
13159     if (!appData.clockMode) return;
13160     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13161
13162     GetTimeMark(&now);
13163
13164     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13165
13166     /* Fudge if we woke up a little too soon */
13167     fudge = intendedTickLength - lastTickLength;
13168     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13169
13170     if (WhiteOnMove(forwardMostMove)) {
13171         if(whiteNPS >= 0) lastTickLength = 0;
13172         timeRemaining = whiteTimeRemaining -= lastTickLength;
13173         DisplayWhiteClock(whiteTimeRemaining - fudge,
13174                           WhiteOnMove(currentMove));
13175     } else {
13176         if(blackNPS >= 0) lastTickLength = 0;
13177         timeRemaining = blackTimeRemaining -= lastTickLength;
13178         DisplayBlackClock(blackTimeRemaining - fudge,
13179                           !WhiteOnMove(currentMove));
13180     }
13181
13182     if (CheckFlags()) return;
13183
13184     tickStartTM = now;
13185     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13186     StartClockTimer(intendedTickLength);
13187
13188     /* if the time remaining has fallen below the alarm threshold, sound the
13189      * alarm. if the alarm has sounded and (due to a takeback or time control
13190      * with increment) the time remaining has increased to a level above the
13191      * threshold, reset the alarm so it can sound again.
13192      */
13193
13194     if (appData.icsActive && appData.icsAlarm) {
13195
13196         /* make sure we are dealing with the user's clock */
13197         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13198                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13199            )) return;
13200
13201         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13202             alarmSounded = FALSE;
13203         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13204             PlayAlarmSound();
13205             alarmSounded = TRUE;
13206         }
13207     }
13208 }
13209
13210
13211 /* A player has just moved, so stop the previously running
13212    clock and (if in clock mode) start the other one.
13213    We redisplay both clocks in case we're in ICS mode, because
13214    ICS gives us an update to both clocks after every move.
13215    Note that this routine is called *after* forwardMostMove
13216    is updated, so the last fractional tick must be subtracted
13217    from the color that is *not* on move now.
13218 */
13219 void
13220 SwitchClocks()
13221 {
13222     long lastTickLength;
13223     TimeMark now;
13224     int flagged = FALSE;
13225
13226     GetTimeMark(&now);
13227
13228     if (StopClockTimer() && appData.clockMode) {
13229         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13230         if (WhiteOnMove(forwardMostMove)) {
13231             if(blackNPS >= 0) lastTickLength = 0;
13232             blackTimeRemaining -= lastTickLength;
13233            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13234 //         if(pvInfoList[forwardMostMove-1].time == -1)
13235                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13236                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13237         } else {
13238            if(whiteNPS >= 0) lastTickLength = 0;
13239            whiteTimeRemaining -= lastTickLength;
13240            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13241 //         if(pvInfoList[forwardMostMove-1].time == -1)
13242                  pvInfoList[forwardMostMove-1].time =
13243                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13244         }
13245         flagged = CheckFlags();
13246     }
13247     CheckTimeControl();
13248
13249     if (flagged || !appData.clockMode) return;
13250
13251     switch (gameMode) {
13252       case MachinePlaysBlack:
13253       case MachinePlaysWhite:
13254       case BeginningOfGame:
13255         if (pausing) return;
13256         break;
13257
13258       case EditGame:
13259       case PlayFromGameFile:
13260       case IcsExamining:
13261         return;
13262
13263       default:
13264         break;
13265     }
13266
13267     tickStartTM = now;
13268     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13269       whiteTimeRemaining : blackTimeRemaining);
13270     StartClockTimer(intendedTickLength);
13271 }
13272
13273
13274 /* Stop both clocks */
13275 void
13276 StopClocks()
13277 {
13278     long lastTickLength;
13279     TimeMark now;
13280
13281     if (!StopClockTimer()) return;
13282     if (!appData.clockMode) return;
13283
13284     GetTimeMark(&now);
13285
13286     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13287     if (WhiteOnMove(forwardMostMove)) {
13288         if(whiteNPS >= 0) lastTickLength = 0;
13289         whiteTimeRemaining -= lastTickLength;
13290         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13291     } else {
13292         if(blackNPS >= 0) lastTickLength = 0;
13293         blackTimeRemaining -= lastTickLength;
13294         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13295     }
13296     CheckFlags();
13297 }
13298
13299 /* Start clock of player on move.  Time may have been reset, so
13300    if clock is already running, stop and restart it. */
13301 void
13302 StartClocks()
13303 {
13304     (void) StopClockTimer(); /* in case it was running already */
13305     DisplayBothClocks();
13306     if (CheckFlags()) return;
13307
13308     if (!appData.clockMode) return;
13309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13310
13311     GetTimeMark(&tickStartTM);
13312     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13313       whiteTimeRemaining : blackTimeRemaining);
13314
13315    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13316     whiteNPS = blackNPS = -1;
13317     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13318        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13319         whiteNPS = first.nps;
13320     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13321        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13322         blackNPS = first.nps;
13323     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13324         whiteNPS = second.nps;
13325     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13326         blackNPS = second.nps;
13327     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13328
13329     StartClockTimer(intendedTickLength);
13330 }
13331
13332 char *
13333 TimeString(ms)
13334      long ms;
13335 {
13336     long second, minute, hour, day;
13337     char *sign = "";
13338     static char buf[32];
13339
13340     if (ms > 0 && ms <= 9900) {
13341       /* convert milliseconds to tenths, rounding up */
13342       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13343
13344       sprintf(buf, " %03.1f ", tenths/10.0);
13345       return buf;
13346     }
13347
13348     /* convert milliseconds to seconds, rounding up */
13349     /* use floating point to avoid strangeness of integer division
13350        with negative dividends on many machines */
13351     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13352
13353     if (second < 0) {
13354         sign = "-";
13355         second = -second;
13356     }
13357
13358     day = second / (60 * 60 * 24);
13359     second = second % (60 * 60 * 24);
13360     hour = second / (60 * 60);
13361     second = second % (60 * 60);
13362     minute = second / 60;
13363     second = second % 60;
13364
13365     if (day > 0)
13366       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13367               sign, day, hour, minute, second);
13368     else if (hour > 0)
13369       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13370     else
13371       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13372
13373     return buf;
13374 }
13375
13376
13377 /*
13378  * This is necessary because some C libraries aren't ANSI C compliant yet.
13379  */
13380 char *
13381 StrStr(string, match)
13382      char *string, *match;
13383 {
13384     int i, length;
13385
13386     length = strlen(match);
13387
13388     for (i = strlen(string) - length; i >= 0; i--, string++)
13389       if (!strncmp(match, string, length))
13390         return string;
13391
13392     return NULL;
13393 }
13394
13395 char *
13396 StrCaseStr(string, match)
13397      char *string, *match;
13398 {
13399     int i, j, length;
13400
13401     length = strlen(match);
13402
13403     for (i = strlen(string) - length; i >= 0; i--, string++) {
13404         for (j = 0; j < length; j++) {
13405             if (ToLower(match[j]) != ToLower(string[j]))
13406               break;
13407         }
13408         if (j == length) return string;
13409     }
13410
13411     return NULL;
13412 }
13413
13414 #ifndef _amigados
13415 int
13416 StrCaseCmp(s1, s2)
13417      char *s1, *s2;
13418 {
13419     char c1, c2;
13420
13421     for (;;) {
13422         c1 = ToLower(*s1++);
13423         c2 = ToLower(*s2++);
13424         if (c1 > c2) return 1;
13425         if (c1 < c2) return -1;
13426         if (c1 == NULLCHAR) return 0;
13427     }
13428 }
13429
13430
13431 int
13432 ToLower(c)
13433      int c;
13434 {
13435     return isupper(c) ? tolower(c) : c;
13436 }
13437
13438
13439 int
13440 ToUpper(c)
13441      int c;
13442 {
13443     return islower(c) ? toupper(c) : c;
13444 }
13445 #endif /* !_amigados    */
13446
13447 char *
13448 StrSave(s)
13449      char *s;
13450 {
13451     char *ret;
13452
13453     if ((ret = (char *) malloc(strlen(s) + 1))) {
13454         strcpy(ret, s);
13455     }
13456     return ret;
13457 }
13458
13459 char *
13460 StrSavePtr(s, savePtr)
13461      char *s, **savePtr;
13462 {
13463     if (*savePtr) {
13464         free(*savePtr);
13465     }
13466     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13467         strcpy(*savePtr, s);
13468     }
13469     return(*savePtr);
13470 }
13471
13472 char *
13473 PGNDate()
13474 {
13475     time_t clock;
13476     struct tm *tm;
13477     char buf[MSG_SIZ];
13478
13479     clock = time((time_t *)NULL);
13480     tm = localtime(&clock);
13481     sprintf(buf, "%04d.%02d.%02d",
13482             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13483     return StrSave(buf);
13484 }
13485
13486
13487 char *
13488 PositionToFEN(move, overrideCastling)
13489      int move;
13490      char *overrideCastling;
13491 {
13492     int i, j, fromX, fromY, toX, toY;
13493     int whiteToPlay;
13494     char buf[128];
13495     char *p, *q;
13496     int emptycount;
13497     ChessSquare piece;
13498
13499     whiteToPlay = (gameMode == EditPosition) ?
13500       !blackPlaysFirst : (move % 2 == 0);
13501     p = buf;
13502
13503     /* Piece placement data */
13504     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13505         emptycount = 0;
13506         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13507             if (boards[move][i][j] == EmptySquare) {
13508                 emptycount++;
13509             } else { ChessSquare piece = boards[move][i][j];
13510                 if (emptycount > 0) {
13511                     if(emptycount<10) /* [HGM] can be >= 10 */
13512                         *p++ = '0' + emptycount;
13513                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13514                     emptycount = 0;
13515                 }
13516                 if(PieceToChar(piece) == '+') {
13517                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13518                     *p++ = '+';
13519                     piece = (ChessSquare)(DEMOTED piece);
13520                 }
13521                 *p++ = PieceToChar(piece);
13522                 if(p[-1] == '~') {
13523                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13524                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13525                     *p++ = '~';
13526                 }
13527             }
13528         }
13529         if (emptycount > 0) {
13530             if(emptycount<10) /* [HGM] can be >= 10 */
13531                 *p++ = '0' + emptycount;
13532             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13533             emptycount = 0;
13534         }
13535         *p++ = '/';
13536     }
13537     *(p - 1) = ' ';
13538
13539     /* [HGM] print Crazyhouse or Shogi holdings */
13540     if( gameInfo.holdingsWidth ) {
13541         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13542         q = p;
13543         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13544             piece = boards[move][i][BOARD_WIDTH-1];
13545             if( piece != EmptySquare )
13546               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13547                   *p++ = PieceToChar(piece);
13548         }
13549         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13550             piece = boards[move][BOARD_HEIGHT-i-1][0];
13551             if( piece != EmptySquare )
13552               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13553                   *p++ = PieceToChar(piece);
13554         }
13555
13556         if( q == p ) *p++ = '-';
13557         *p++ = ']';
13558         *p++ = ' ';
13559     }
13560
13561     /* Active color */
13562     *p++ = whiteToPlay ? 'w' : 'b';
13563     *p++ = ' ';
13564
13565   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13566     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13567   } else {
13568   if(nrCastlingRights) {
13569      q = p;
13570      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13571        /* [HGM] write directly from rights */
13572            if(castlingRights[move][2] >= 0 &&
13573               castlingRights[move][0] >= 0   )
13574                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13575            if(castlingRights[move][2] >= 0 &&
13576               castlingRights[move][1] >= 0   )
13577                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13578            if(castlingRights[move][5] >= 0 &&
13579               castlingRights[move][3] >= 0   )
13580                 *p++ = castlingRights[move][3] + AAA;
13581            if(castlingRights[move][5] >= 0 &&
13582               castlingRights[move][4] >= 0   )
13583                 *p++ = castlingRights[move][4] + AAA;
13584      } else {
13585
13586         /* [HGM] write true castling rights */
13587         if( nrCastlingRights == 6 ) {
13588             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13589                castlingRights[move][2] >= 0  ) *p++ = 'K';
13590             if(castlingRights[move][1] == BOARD_LEFT &&
13591                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13592             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13593                castlingRights[move][5] >= 0  ) *p++ = 'k';
13594             if(castlingRights[move][4] == BOARD_LEFT &&
13595                castlingRights[move][5] >= 0  ) *p++ = 'q';
13596         }
13597      }
13598      if (q == p) *p++ = '-'; /* No castling rights */
13599      *p++ = ' ';
13600   }
13601
13602   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13603      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13604     /* En passant target square */
13605     if (move > backwardMostMove) {
13606         fromX = moveList[move - 1][0] - AAA;
13607         fromY = moveList[move - 1][1] - ONE;
13608         toX = moveList[move - 1][2] - AAA;
13609         toY = moveList[move - 1][3] - ONE;
13610         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13611             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13612             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13613             fromX == toX) {
13614             /* 2-square pawn move just happened */
13615             *p++ = toX + AAA;
13616             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13617         } else {
13618             *p++ = '-';
13619         }
13620     } else {
13621         *p++ = '-';
13622     }
13623     *p++ = ' ';
13624   }
13625   }
13626
13627     /* [HGM] find reversible plies */
13628     {   int i = 0, j=move;
13629
13630         if (appData.debugMode) { int k;
13631             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13632             for(k=backwardMostMove; k<=forwardMostMove; k++)
13633                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13634
13635         }
13636
13637         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13638         if( j == backwardMostMove ) i += initialRulePlies;
13639         sprintf(p, "%d ", i);
13640         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13641     }
13642     /* Fullmove number */
13643     sprintf(p, "%d", (move / 2) + 1);
13644
13645     return StrSave(buf);
13646 }
13647
13648 Boolean
13649 ParseFEN(board, blackPlaysFirst, fen)
13650     Board board;
13651      int *blackPlaysFirst;
13652      char *fen;
13653 {
13654     int i, j;
13655     char *p;
13656     int emptycount;
13657     ChessSquare piece;
13658
13659     p = fen;
13660
13661     /* [HGM] by default clear Crazyhouse holdings, if present */
13662     if(gameInfo.holdingsWidth) {
13663        for(i=0; i<BOARD_HEIGHT; i++) {
13664            board[i][0]             = EmptySquare; /* black holdings */
13665            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13666            board[i][1]             = (ChessSquare) 0; /* black counts */
13667            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13668        }
13669     }
13670
13671     /* Piece placement data */
13672     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13673         j = 0;
13674         for (;;) {
13675             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13676                 if (*p == '/') p++;
13677                 emptycount = gameInfo.boardWidth - j;
13678                 while (emptycount--)
13679                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13680                 break;
13681 #if(BOARD_SIZE >= 10)
13682             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13683                 p++; emptycount=10;
13684                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13685                 while (emptycount--)
13686                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13687 #endif
13688             } else if (isdigit(*p)) {
13689                 emptycount = *p++ - '0';
13690                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13691                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13692                 while (emptycount--)
13693                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13694             } else if (*p == '+' || isalpha(*p)) {
13695                 if (j >= gameInfo.boardWidth) return FALSE;
13696                 if(*p=='+') {
13697                     piece = CharToPiece(*++p);
13698                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13699                     piece = (ChessSquare) (PROMOTED piece ); p++;
13700                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13701                 } else piece = CharToPiece(*p++);
13702
13703                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13704                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13705                     piece = (ChessSquare) (PROMOTED piece);
13706                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13707                     p++;
13708                 }
13709                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13710             } else {
13711                 return FALSE;
13712             }
13713         }
13714     }
13715     while (*p == '/' || *p == ' ') p++;
13716
13717     /* [HGM] look for Crazyhouse holdings here */
13718     while(*p==' ') p++;
13719     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13720         if(*p == '[') p++;
13721         if(*p == '-' ) *p++; /* empty holdings */ else {
13722             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13723             /* if we would allow FEN reading to set board size, we would   */
13724             /* have to add holdings and shift the board read so far here   */
13725             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13726                 *p++;
13727                 if((int) piece >= (int) BlackPawn ) {
13728                     i = (int)piece - (int)BlackPawn;
13729                     i = PieceToNumber((ChessSquare)i);
13730                     if( i >= gameInfo.holdingsSize ) return FALSE;
13731                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13732                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13733                 } else {
13734                     i = (int)piece - (int)WhitePawn;
13735                     i = PieceToNumber((ChessSquare)i);
13736                     if( i >= gameInfo.holdingsSize ) return FALSE;
13737                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13738                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13739                 }
13740             }
13741         }
13742         if(*p == ']') *p++;
13743     }
13744
13745     while(*p == ' ') p++;
13746
13747     /* Active color */
13748     switch (*p++) {
13749       case 'w':
13750         *blackPlaysFirst = FALSE;
13751         break;
13752       case 'b':
13753         *blackPlaysFirst = TRUE;
13754         break;
13755       default:
13756         return FALSE;
13757     }
13758
13759     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13760     /* return the extra info in global variiables             */
13761
13762     /* set defaults in case FEN is incomplete */
13763     FENepStatus = EP_UNKNOWN;
13764     for(i=0; i<nrCastlingRights; i++ ) {
13765         FENcastlingRights[i] =
13766             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13767     }   /* assume possible unless obviously impossible */
13768     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13769     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13770     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13771     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13772     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13773     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13774     FENrulePlies = 0;
13775
13776     while(*p==' ') p++;
13777     if(nrCastlingRights) {
13778       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13779           /* castling indicator present, so default becomes no castlings */
13780           for(i=0; i<nrCastlingRights; i++ ) {
13781                  FENcastlingRights[i] = -1;
13782           }
13783       }
13784       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13785              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13786              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13787              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13788         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13789
13790         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13791             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13792             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13793         }
13794         switch(c) {
13795           case'K':
13796               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13797               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13798               FENcastlingRights[2] = whiteKingFile;
13799               break;
13800           case'Q':
13801               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13802               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13803               FENcastlingRights[2] = whiteKingFile;
13804               break;
13805           case'k':
13806               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13807               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13808               FENcastlingRights[5] = blackKingFile;
13809               break;
13810           case'q':
13811               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13812               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13813               FENcastlingRights[5] = blackKingFile;
13814           case '-':
13815               break;
13816           default: /* FRC castlings */
13817               if(c >= 'a') { /* black rights */
13818                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13819                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13820                   if(i == BOARD_RGHT) break;
13821                   FENcastlingRights[5] = i;
13822                   c -= AAA;
13823                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13824                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13825                   if(c > i)
13826                       FENcastlingRights[3] = c;
13827                   else
13828                       FENcastlingRights[4] = c;
13829               } else { /* white rights */
13830                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13831                     if(board[0][i] == WhiteKing) break;
13832                   if(i == BOARD_RGHT) break;
13833                   FENcastlingRights[2] = i;
13834                   c -= AAA - 'a' + 'A';
13835                   if(board[0][c] >= WhiteKing) break;
13836                   if(c > i)
13837                       FENcastlingRights[0] = c;
13838                   else
13839                       FENcastlingRights[1] = c;
13840               }
13841         }
13842       }
13843     if (appData.debugMode) {
13844         fprintf(debugFP, "FEN castling rights:");
13845         for(i=0; i<nrCastlingRights; i++)
13846         fprintf(debugFP, " %d", FENcastlingRights[i]);
13847         fprintf(debugFP, "\n");
13848     }
13849
13850       while(*p==' ') p++;
13851     }
13852
13853     /* read e.p. field in games that know e.p. capture */
13854     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13855        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13856       if(*p=='-') {
13857         p++; FENepStatus = EP_NONE;
13858       } else {
13859          char c = *p++ - AAA;
13860
13861          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13862          if(*p >= '0' && *p <='9') *p++;
13863          FENepStatus = c;
13864       }
13865     }
13866
13867
13868     if(sscanf(p, "%d", &i) == 1) {
13869         FENrulePlies = i; /* 50-move ply counter */
13870         /* (The move number is still ignored)    */
13871     }
13872
13873     return TRUE;
13874 }
13875
13876 void
13877 EditPositionPasteFEN(char *fen)
13878 {
13879   if (fen != NULL) {
13880     Board initial_position;
13881
13882     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13883       DisplayError(_("Bad FEN position in clipboard"), 0);
13884       return ;
13885     } else {
13886       int savedBlackPlaysFirst = blackPlaysFirst;
13887       EditPositionEvent();
13888       blackPlaysFirst = savedBlackPlaysFirst;
13889       CopyBoard(boards[0], initial_position);
13890           /* [HGM] copy FEN attributes as well */
13891           {   int i;
13892               initialRulePlies = FENrulePlies;
13893               epStatus[0] = FENepStatus;
13894               for( i=0; i<nrCastlingRights; i++ )
13895                   castlingRights[0][i] = FENcastlingRights[i];
13896           }
13897       EditPositionDone();
13898       DisplayBothClocks();
13899       DrawPosition(FALSE, boards[currentMove]);
13900     }
13901   }
13902 }